Merge remote-tracking branch 'upstream/master' into 24-10-29-modern-hwid

This commit is contained in:
Pieter-Jan Briers
2024-11-21 01:26:01 +01:00
512 changed files with 22172 additions and 11146 deletions

2
.github/labeler.yml vendored
View File

@@ -16,7 +16,7 @@
- changed-files: - changed-files:
- any-glob-to-any-file: '**/*.swsl' - any-glob-to-any-file: '**/*.swsl'
"No C#": "Changes: No C#":
- changed-files: - changed-files:
# Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label. # Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label.
- all-globs-to-all-files: "!**/*.cs" - all-globs-to-all-files: "!**/*.cs"

View File

@@ -16,6 +16,6 @@ jobs:
- name: Check for Merge Conflicts - name: Check for Merge Conflicts
uses: eps1lon/actions-label-merge-conflict@v3.0.0 uses: eps1lon/actions-label-merge-conflict@v3.0.0
with: with:
dirtyLabel: "Merge Conflict" dirtyLabel: "S: Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}" repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
- uses: actions-ecosystem/action-add-labels@v1 - uses: actions-ecosystem/action-add-labels@v1
with: with:
labels: "Status: Needs Review" labels: "S: Needs Review"
- uses: actions-ecosystem/action-remove-labels@v1 - uses: actions-ecosystem/action-remove-labels@v1
with: with:
labels: "Status: Awaiting Changes" labels: "S: Awaiting Changes"

23
.github/workflows/labeler-review.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: "Labels: Approved"
on:
pull_request_review:
types: [submitted]
jobs:
add_label:
# Change the repository name after you've made sure the team name is correct for your fork!
if: ${{ (github.repository == 'space-wizards/space-station-14') && (github.event.review.state == 'APPROVED') }}
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: tspascoal/get-user-teams-membership@v3
id: checkUserMember
with:
username: ${{ github.actor }}
team: "content-maintainers,junior-maintainers" # CHANGE TEAM NAME HERE PLEASE <------
GITHUB_TOKEN: ${{ secrets.PAT }}
- if: ${{ steps.checkUserMember.outputs.isTeamMember == 'true' }}
uses: actions-ecosystem/action-add-labels@v1
with:
labels: "PR: Approved"

20
.github/workflows/labeler-size.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: "Labels: Size"
on: pull_request_target
jobs:
size-label:
runs-on: ubuntu-latest
steps:
- name: size-label
uses: "pascalgn/size-label-action@v0.5.5"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
with:
# Custom size configuration
sizes: >
{
"0": "XS",
"10": "S",
"30": "M",
"100": "L",
"1000": "XL"
}

View File

@@ -13,4 +13,4 @@ jobs:
steps: steps:
- uses: actions-ecosystem/action-add-labels@v1 - uses: actions-ecosystem/action-add-labels@v1
with: with:
labels: "Branch: stable" labels: "Branch: Stable"

View File

@@ -13,4 +13,4 @@ jobs:
steps: steps:
- uses: actions-ecosystem/action-add-labels@v1 - uses: actions-ecosystem/action-add-labels@v1
with: with:
labels: "Branch: staging" labels: "Branch: Staging"

View File

@@ -3,6 +3,8 @@
on: on:
issues: issues:
types: [opened] types: [opened]
pull_request_target:
types: [opened]
jobs: jobs:
add_label: add_label:
@@ -11,4 +13,4 @@ jobs:
- uses: actions-ecosystem/action-add-labels@v1 - uses: actions-ecosystem/action-add-labels@v1
if: join(github.event.issue.labels) == '' if: join(github.event.issue.labels) == ''
with: with:
labels: "Status: Untriaged" labels: "S: Untriaged"

View File

@@ -8,6 +8,7 @@
<Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" /> <Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}" <HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
Visible="False" HorizontalExpand="True" /> Visible="False" HorizontalExpand="True" />
<OptionButton Name="ExpiryLengthDropdown" Visible="False" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<OptionButton Name="TypeOption" HorizontalAlignment="Center" /> <OptionButton Name="TypeOption" HorizontalAlignment="Center" />

View File

@@ -17,6 +17,17 @@ public sealed partial class NoteEdit : FancyWindow
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientConsoleHost _console = default!; [Dependency] private readonly IClientConsoleHost _console = default!;
private enum Multipliers
{
Minutes,
Hours,
Days,
Weeks,
Months,
Years,
Centuries
}
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed; public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed;
public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit) public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit)
@@ -31,6 +42,20 @@ public sealed partial class NoteEdit : FancyWindow
ResetSubmitButton(); ResetSubmitButton();
// It's weird to use minutes as the IDs, but it works and makes sense kind of :)
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-minutes"), (int) Multipliers.Minutes);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-hours"), (int) Multipliers.Hours);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-days"), (int) Multipliers.Days);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-weeks"), (int) Multipliers.Weeks);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-months"), (int) Multipliers.Months);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-years"), (int) Multipliers.Years);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-centuries"), (int) Multipliers.Centuries);
ExpiryLengthDropdown.OnItemSelected += OnLengthChanged;
ExpiryLengthDropdown.SelectId((int) Multipliers.Weeks);
ExpiryLineEdit.OnTextChanged += OnTextChanged;
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-note"), (int) NoteType.Note); TypeOption.AddItem(Loc.GetString("admin-note-editor-type-note"), (int) NoteType.Note);
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-message"), (int) NoteType.Message); TypeOption.AddItem(Loc.GetString("admin-note-editor-type-message"), (int) NoteType.Message);
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-watchlist"), (int) NoteType.Watchlist); TypeOption.AddItem(Loc.GetString("admin-note-editor-type-watchlist"), (int) NoteType.Watchlist);
@@ -172,8 +197,9 @@ public sealed partial class NoteEdit : FancyWindow
{ {
ExpiryLabel.Visible = !PermanentCheckBox.Pressed; ExpiryLabel.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed; ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed;
ExpiryLengthDropdown.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") : string.Empty; ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? 1.ToString() : string.Empty;
} }
private void OnSecretPressed(BaseButton.ButtonEventArgs _) private void OnSecretPressed(BaseButton.ButtonEventArgs _)
@@ -187,6 +213,16 @@ public sealed partial class NoteEdit : FancyWindow
SeverityOption.SelectId(args.Id); SeverityOption.SelectId(args.Id);
} }
private void OnLengthChanged(OptionButton.ItemSelectedEventArgs args)
{
ExpiryLengthDropdown.SelectId(args.Id);
}
private void OnTextChanged(HistoryLineEdit.LineEditEventArgs args)
{
ParseExpiryTime();
}
private void OnSubmitButtonPressed(BaseButton.ButtonEventArgs args) private void OnSubmitButtonPressed(BaseButton.ButtonEventArgs args)
{ {
if (!ParseExpiryTime()) if (!ParseExpiryTime())
@@ -263,13 +299,24 @@ public sealed partial class NoteEdit : FancyWindow
return true; return true;
} }
if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !DateTime.TryParse(ExpiryLineEdit.Text, out var result) || DateTime.UtcNow > result) if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !uint.TryParse(ExpiryLineEdit.Text, out var inputInt))
{ {
ExpiryLineEdit.ModulateSelfOverride = Color.Red; ExpiryLineEdit.ModulateSelfOverride = Color.Red;
return false; return false;
} }
ExpiryTime = result.ToUniversalTime(); var mult = ExpiryLengthDropdown.SelectedId switch
{
(int) Multipliers.Minutes => TimeSpan.FromMinutes(1).TotalMinutes,
(int) Multipliers.Hours => TimeSpan.FromHours(1).TotalMinutes,
(int) Multipliers.Days => TimeSpan.FromDays(1).TotalMinutes,
(int) Multipliers.Weeks => TimeSpan.FromDays(7).TotalMinutes,
(int) Multipliers.Months => TimeSpan.FromDays(30).TotalMinutes,
(int) Multipliers.Years => TimeSpan.FromDays(365).TotalMinutes,
(int) Multipliers.Centuries => TimeSpan.FromDays(36525).TotalMinutes,
_ => throw new ArgumentOutOfRangeException(nameof(ExpiryLengthDropdown.SelectedId), "Multiplier out of range :(")
};
ExpiryTime = DateTime.UtcNow.AddMinutes(inputInt * mult);
ExpiryLineEdit.ModulateSelfOverride = null; ExpiryLineEdit.ModulateSelfOverride = null;
return true; return true;
} }

View File

@@ -58,6 +58,7 @@ public sealed class ClientClothingSystem : ClothingSystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals); SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals);
SubscribeLocalEvent<ClothingComponent, InventoryTemplateUpdated>(OnInventoryTemplateUpdated);
SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged); SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip); SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip);
@@ -70,11 +71,7 @@ public sealed class ClientClothingSystem : ClothingSystem
if (args.Sprite == null) if (args.Sprite == null)
return; return;
var enumerator = _inventorySystem.GetSlotEnumerator((uid, component)); UpdateAllSlots(uid, component);
while (enumerator.NextItem(out var item, out var slot))
{
RenderEquipment(uid, item, slot.Name, component);
}
// No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip. // No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip.
if (args.Sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer)) if (args.Sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
@@ -84,6 +81,23 @@ public sealed class ClientClothingSystem : ClothingSystem
} }
} }
private void OnInventoryTemplateUpdated(Entity<ClothingComponent> ent, ref InventoryTemplateUpdated args)
{
UpdateAllSlots(ent.Owner, clothing: ent.Comp);
}
private void UpdateAllSlots(
EntityUid uid,
InventoryComponent? inventoryComponent = null,
ClothingComponent? clothing = null)
{
var enumerator = _inventorySystem.GetSlotEnumerator((uid, inventoryComponent));
while (enumerator.NextItem(out var item, out var slot))
{
RenderEquipment(uid, item, slot.Name, inventoryComponent, clothingComponent: clothing);
}
}
private void OnGetVisuals(EntityUid uid, ClothingComponent item, GetEquipmentVisualsEvent args) private void OnGetVisuals(EntityUid uid, ClothingComponent item, GetEquipmentVisualsEvent args)
{ {
if (!TryComp(args.Equipee, out InventoryComponent? inventory)) if (!TryComp(args.Equipee, out InventoryComponent? inventory))

View File

@@ -1,15 +1,20 @@
<DefaultWindow xmlns="https://spacestation14.io"> <DefaultWindow xmlns="https://spacestation14.io">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.4" Margin="0 0 5 0"> <BoxContainer Orientation="Vertical" MinWidth="243" Margin="0 0 5 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5">
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/> <LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/>
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/> <OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
</BoxContainer> </BoxContainer>
<ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/> <ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/>
<ScrollContainer Name="RecipesGridScrollContainer" VerticalExpand="True" Access="Public" Visible="False">
<GridContainer Name="RecipesGrid" Columns="5" Access="Public"/>
</ScrollContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal">
<Button Name="MenuGridViewButton" ToggleMode="True" Text="{Loc construction-menu-grid-view}"/>
<Button Name="FavoriteButton" Visible="false"/>
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.6">
<Button Name="FavoriteButton" Visible="false" HorizontalExpand="False"
HorizontalAlignment="Right" Margin="0 0 0 15"/>
<Control> <Control>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5"> <BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5">
<BoxContainer Orientation="Horizontal" Align="Center"> <BoxContainer Orientation="Horizontal" Align="Center">

View File

@@ -25,11 +25,16 @@ namespace Content.Client.Construction.UI
OptionButton OptionCategories { get; } OptionButton OptionCategories { get; }
bool EraseButtonPressed { get; set; } bool EraseButtonPressed { get; set; }
bool GridViewButtonPressed { get; set; }
bool BuildButtonPressed { get; set; } bool BuildButtonPressed { get; set; }
ItemList Recipes { get; } ItemList Recipes { get; }
ItemList RecipeStepList { get; } ItemList RecipeStepList { get; }
ScrollContainer RecipesGridScrollContainer { get; }
GridContainer RecipesGrid { get; }
event EventHandler<(string search, string catagory)> PopulateRecipes; event EventHandler<(string search, string catagory)> PopulateRecipes;
event EventHandler<ItemList.Item?> RecipeSelected; event EventHandler<ItemList.Item?> RecipeSelected;
event EventHandler RecipeFavorited; event EventHandler RecipeFavorited;
@@ -72,9 +77,16 @@ namespace Content.Client.Construction.UI
set => EraseButton.Pressed = value; set => EraseButton.Pressed = value;
} }
public bool GridViewButtonPressed
{
get => MenuGridViewButton.Pressed;
set => MenuGridViewButton.Pressed = value;
}
public ConstructionMenu() public ConstructionMenu()
{ {
SetSize = MinSize = new Vector2(720, 320); SetSize = new Vector2(560, 450);
MinSize = new Vector2(560, 320);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -102,6 +114,9 @@ namespace Content.Client.Construction.UI
EraseButton.OnToggled += args => EraseButtonToggled?.Invoke(this, args.Pressed); EraseButton.OnToggled += args => EraseButtonToggled?.Invoke(this, args.Pressed);
FavoriteButton.OnPressed += args => RecipeFavorited?.Invoke(this, EventArgs.Empty); FavoriteButton.OnPressed += args => RecipeFavorited?.Invoke(this, EventArgs.Empty);
MenuGridViewButton.OnPressed += _ =>
PopulateRecipes?.Invoke(this, (SearchBar.Text, Categories[OptionCategories.SelectedId]));
} }
public event EventHandler? ClearAllGhosts; public event EventHandler? ClearAllGhosts;

View File

@@ -1,7 +1,8 @@
using System.Linq; using System.Linq;
using System.Numerics;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.MenuBar.Widgets; using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Prototypes;
using Content.Shared.Tag;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -11,7 +12,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility; using Robust.Client.Utility;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton; using static Robust.Client.UserInterface.Controls.BaseButton;
@@ -33,10 +33,12 @@ namespace Content.Client.Construction.UI
private readonly IConstructionMenuView _constructionView; private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem; private readonly EntityWhitelistSystem _whitelistSystem;
private readonly SpriteSystem _spriteSystem;
private ConstructionSystem? _constructionSystem; private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected; private ConstructionPrototype? _selected;
private List<ConstructionPrototype> _favoritedRecipes = []; private List<ConstructionPrototype> _favoritedRecipes = [];
private Dictionary<string, TextureButton> _recipeButtons = new();
private string _selectedCategory = string.Empty; private string _selectedCategory = string.Empty;
private string _favoriteCatName = "construction-category-favorites"; private string _favoriteCatName = "construction-category-favorites";
private string _forAllCategoryName = "construction-category-all"; private string _forAllCategoryName = "construction-category-all";
@@ -85,6 +87,7 @@ namespace Content.Client.Construction.UI
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_constructionView = new ConstructionMenu(); _constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>(); _whitelistSystem = _entManager.System<EntityWhitelistSystem>();
_spriteSystem = _entManager.System<SpriteSystem>();
// This is required so that if we load after the system is initialized, we can bind to it immediately // This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem)) if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
@@ -150,12 +153,24 @@ namespace Content.Client.Construction.UI
PopulateInfo(_selected); PopulateInfo(_selected);
} }
private void OnGridViewRecipeSelected(object? sender, ConstructionPrototype? recipe)
{
if (recipe is null)
{
_selected = null;
_constructionView.ClearRecipeInfo();
return;
}
_selected = recipe;
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
PopulateInfo(_selected);
}
private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args) private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args)
{ {
var (search, category) = args; var (search, category) = args;
var recipesList = _constructionView.Recipes;
recipesList.Clear();
var recipes = new List<ConstructionPrototype>(); var recipes = new List<ConstructionPrototype>();
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName; var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;
@@ -201,12 +216,73 @@ namespace Content.Client.Construction.UI
recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture)); recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
var recipesList = _constructionView.Recipes;
recipesList.Clear();
var recipesGrid = _constructionView.RecipesGrid;
recipesGrid.RemoveAllChildren();
_constructionView.RecipesGridScrollContainer.Visible = _constructionView.GridViewButtonPressed;
_constructionView.Recipes.Visible = !_constructionView.GridViewButtonPressed;
if (_constructionView.GridViewButtonPressed)
{
foreach (var recipe in recipes)
{
var itemButton = new TextureButton
{
TextureNormal = _spriteSystem.Frame0(recipe.Icon),
VerticalAlignment = Control.VAlignment.Center,
Name = recipe.Name,
ToolTip = recipe.Name,
Scale = new Vector2(1.35f),
ToggleMode = true,
};
var itemButtonPanelContainer = new PanelContainer
{
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
Children = { itemButton },
};
itemButton.OnToggled += buttonToggledEventArgs =>
{
SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
if (buttonToggledEventArgs.Pressed &&
_selected != null &&
_recipeButtons.TryGetValue(_selected.Name, out var oldButton))
{
oldButton.Pressed = false;
SelectGridButton(oldButton, false);
}
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe : null);
};
recipesGrid.AddChild(itemButtonPanelContainer);
_recipeButtons[recipe.Name] = itemButton;
var isCurrentButtonSelected = _selected == recipe;
itemButton.Pressed = isCurrentButtonSelected;
SelectGridButton(itemButton, isCurrentButtonSelected);
}
}
else
{
foreach (var recipe in recipes) foreach (var recipe in recipes)
{ {
recipesList.Add(GetItem(recipe, recipesList)); recipesList.Add(GetItem(recipe, recipesList));
} }
}
}
// There is apparently no way to set which private void SelectGridButton(TextureButton button, bool select)
{
if (button.Parent is not PanelContainer buttonPanel)
return;
button.Modulate = select ? Color.Green : Color.White;
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
} }
private void PopulateCategories(string? selectCategory = null) private void PopulateCategories(string? selectCategory = null)
@@ -257,11 +333,10 @@ namespace Content.Client.Construction.UI
private void PopulateInfo(ConstructionPrototype prototype) private void PopulateInfo(ConstructionPrototype prototype)
{ {
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
_constructionView.ClearRecipeInfo(); _constructionView.ClearRecipeInfo();
_constructionView.SetRecipeInfo( _constructionView.SetRecipeInfo(
prototype.Name, prototype.Description, spriteSys.Frame0(prototype.Icon), prototype.Name, prototype.Description, _spriteSystem.Frame0(prototype.Icon),
prototype.Type != ConstructionType.Item, prototype.Type != ConstructionType.Item,
!_favoritedRecipes.Contains(prototype)); !_favoritedRecipes.Contains(prototype));
@@ -274,7 +349,6 @@ namespace Content.Client.Construction.UI
if (_constructionSystem?.GetGuide(prototype) is not { } guide) if (_constructionSystem?.GetGuide(prototype) is not { } guide)
return; return;
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
foreach (var entry in guide.Entries) foreach (var entry in guide.Entries)
{ {
@@ -290,20 +364,20 @@ namespace Content.Client.Construction.UI
// The padding needs to be applied regardless of text length... (See PadLeft documentation) // The padding needs to be applied regardless of text length... (See PadLeft documentation)
text = text.PadLeft(text.Length + entry.Padding); text = text.PadLeft(text.Length + entry.Padding);
var icon = entry.Icon != null ? spriteSys.Frame0(entry.Icon) : Texture.Transparent; var icon = entry.Icon != null ? _spriteSystem.Frame0(entry.Icon) : Texture.Transparent;
stepList.AddItem(text, icon, false); stepList.AddItem(text, icon, false);
} }
} }
private static ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList) private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
{ {
return new(itemList) return new(itemList)
{ {
Metadata = recipe, Metadata = recipe,
Text = recipe.Name, Text = recipe.Name,
Icon = recipe.Icon.Frame0(), Icon = _spriteSystem.Frame0(recipe.Icon),
TooltipEnabled = true, TooltipEnabled = true,
TooltipText = recipe.Description TooltipText = recipe.Description,
}; };
} }

View File

@@ -31,7 +31,7 @@ namespace Content.Client.Crayon.UI
private void PopulateCrayons() private void PopulateCrayons()
{ {
var crayonDecals = _protoManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon")); var crayonDecals = _protoManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon"));
_menu?.Populate(crayonDecals); _menu?.Populate(crayonDecals.ToList());
} }
public override void OnProtoReload(PrototypesReloadedEventArgs args) public override void OnProtoReload(PrototypesReloadedEventArgs args)
@@ -44,6 +44,16 @@ namespace Content.Client.Crayon.UI
PopulateCrayons(); PopulateCrayons();
} }
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
base.ReceiveMessage(message);
if (_menu is null || message is not CrayonUsedMessage crayonMessage)
return;
_menu.AdvanceState(crayonMessage.DrawnDecal);
}
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)
{ {
base.UpdateState(state); base.UpdateState(state);

View File

@@ -1,14 +1,13 @@
<DefaultWindow xmlns="https://spacestation14.io" <DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'crayon-window-title'}" Title="{Loc 'crayon-window-title'}"
MinSize="250 300" MinSize="450 500"
SetSize="250 300"> SetSize="450 500">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<ColorSelectorSliders Name="ColorSelector" Visible="False" /> <ColorSelectorSliders Name="ColorSelector" Visible="False" />
<LineEdit Name="Search" /> <LineEdit Name="Search" Margin="0 0 0 8" PlaceHolder="{Loc 'crayon-window-placeholder'}" />
<ScrollContainer VerticalExpand="True"> <ScrollContainer VerticalExpand="True">
<GridContainer Name="Grid" Columns="6"> <BoxContainer Name="Grids" Orientation="Vertical">
<!-- Crayon decals get added here by code --> </BoxContainer>
</GridContainer>
</ScrollContainer> </ScrollContainer>
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Shared.Crayon; using Content.Shared.Crayon;
using Content.Shared.Decals; using Content.Shared.Decals;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
@@ -18,7 +20,12 @@ namespace Content.Client.Crayon.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class CrayonWindow : DefaultWindow public sealed partial class CrayonWindow : DefaultWindow
{ {
private Dictionary<string, Texture>? _decals; [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly SpriteSystem _spriteSystem = default!;
private Dictionary<string, List<(string Name, Texture Texture)>>? _decals;
private List<string>? _allDecals;
private string? _autoSelected;
private string? _selected; private string? _selected;
private Color _color; private Color _color;
@@ -28,8 +35,10 @@ namespace Content.Client.Crayon.UI
public CrayonWindow() public CrayonWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
Search.OnTextChanged += _ => RefreshList(); Search.OnTextChanged += SearchChanged;
ColorSelector.OnColorChanged += SelectColor; ColorSelector.OnColorChanged += SelectColor;
} }
@@ -44,25 +53,60 @@ namespace Content.Client.Crayon.UI
private void RefreshList() private void RefreshList()
{ {
// Clear // Clear
Grid.DisposeAllChildren(); Grids.DisposeAllChildren();
if (_decals == null)
if (_decals == null || _allDecals == null)
return; return;
var filter = Search.Text; var filter = Search.Text;
foreach (var (decal, tex) in _decals) var comma = filter.IndexOf(',');
var first = (comma == -1 ? filter : filter[..comma]).Trim();
var names = _decals.Keys.ToList();
names.Sort((a, b) => a == "random" ? 1 : b == "random" ? -1 : a.CompareTo(b));
if (_autoSelected != null && first != _autoSelected && _allDecals.Contains(first))
{ {
if (!decal.Contains(filter)) _selected = first;
_autoSelected = _selected;
OnSelected?.Invoke(_selected);
}
foreach (var categoryName in names)
{
var locName = Loc.GetString("crayon-category-" + categoryName);
var category = _decals[categoryName].Where(d => locName.Contains(first) || d.Name.Contains(first)).ToList();
if (category.Count == 0)
continue; continue;
var label = new Label
{
Text = locName
};
var grid = new GridContainer
{
Columns = 6,
Margin = new Thickness(0, 0, 0, 16)
};
Grids.AddChild(label);
Grids.AddChild(grid);
foreach (var (name, texture) in category)
{
var button = new TextureButton() var button = new TextureButton()
{ {
TextureNormal = tex, TextureNormal = texture,
Name = decal, Name = name,
ToolTip = decal, ToolTip = name,
Modulate = _color, Modulate = _color,
Scale = new System.Numerics.Vector2(2, 2)
}; };
button.OnPressed += ButtonOnPressed; button.OnPressed += ButtonOnPressed;
if (_selected == decal)
if (_selected == name)
{ {
var panelContainer = new PanelContainer() var panelContainer = new PanelContainer()
{ {
@@ -75,20 +119,28 @@ namespace Content.Client.Crayon.UI
button, button,
}, },
}; };
Grid.AddChild(panelContainer); grid.AddChild(panelContainer);
} }
else else
{ {
Grid.AddChild(button); grid.AddChild(button);
} }
} }
} }
}
private void SearchChanged(LineEdit.LineEditEventArgs obj)
{
_autoSelected = ""; // Placeholder to kick off the auto-select in refreshlist()
RefreshList();
}
private void ButtonOnPressed(ButtonEventArgs obj) private void ButtonOnPressed(ButtonEventArgs obj)
{ {
if (obj.Button.Name == null) return; if (obj.Button.Name == null) return;
_selected = obj.Button.Name; _selected = obj.Button.Name;
_autoSelected = null;
OnSelected?.Invoke(_selected); OnSelected?.Invoke(_selected);
RefreshList(); RefreshList();
} }
@@ -107,12 +159,38 @@ namespace Content.Client.Crayon.UI
RefreshList(); RefreshList();
} }
public void Populate(IEnumerable<DecalPrototype> prototypes) public void AdvanceState(string drawnDecal)
{ {
_decals = new Dictionary<string, Texture>(); var filter = Search.Text;
if (!filter.Contains(',') || !filter.Contains(drawnDecal))
return;
var first = filter[..filter.IndexOf(',')].Trim();
if (first.Equals(drawnDecal, StringComparison.InvariantCultureIgnoreCase))
{
Search.Text = filter[(filter.IndexOf(',') + 1)..].Trim();
_autoSelected = first;
}
RefreshList();
}
public void Populate(List<DecalPrototype> prototypes)
{
_decals = [];
_allDecals = [];
prototypes.Sort((a, b) => a.ID.CompareTo(b.ID));
foreach (var decalPrototype in prototypes) foreach (var decalPrototype in prototypes)
{ {
_decals.Add(decalPrototype.ID, decalPrototype.Sprite.Frame0()); var category = "random";
if (decalPrototype.Tags.Count > 1 && decalPrototype.Tags[1].StartsWith("crayon-"))
category = decalPrototype.Tags[1].Replace("crayon-", "");
var list = _decals.GetOrNew(category);
list.Add((decalPrototype.ID, _spriteSystem.Frame0(decalPrototype.Sprite)));
_allDecals.Add(decalPrototype.ID);
} }
RefreshList(); RefreshList();

View File

@@ -235,9 +235,23 @@ namespace Content.Client.Inventory
EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true)); EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true));
} }
protected override void UpdateInventoryTemplate(Entity<InventoryComponent> ent)
{
base.UpdateInventoryTemplate(ent);
if (TryComp(ent, out InventorySlotsComponent? inventorySlots))
{
foreach (var slot in ent.Comp.Slots)
{
if (inventorySlots.SlotData.TryGetValue(slot.Name, out var slotData))
slotData.SlotDef = slot;
}
}
}
public sealed class SlotData public sealed class SlotData
{ {
public readonly SlotDefinition SlotDef; public SlotDefinition SlotDef;
public EntityUid? HeldEntity => Container?.ContainedEntity; public EntityUid? HeldEntity => Container?.ContainedEntity;
public bool Blocked; public bool Blocked;
public bool Highlighted; public bool Highlighted;

View File

@@ -279,7 +279,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_profileEditor.OnOpenGuidebook += _guide.OpenHelp; _profileEditor.OnOpenGuidebook += _guide.OpenHelp;
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor); _characterSetup = new CharacterSetupGui(_profileEditor);
_characterSetup.CloseButton.OnPressed += _ => _characterSetup.CloseButton.OnPressed += _ =>
{ {

View File

@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client" xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:style="clr-namespace:Content.Client.Stylesheets" xmlns:style="clr-namespace:Content.Client.Stylesheets"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
VerticalExpand="True"> VerticalExpand="True">
<Control> <Control>
<PanelContainer Name="BackgroundPanel" /> <PanelContainer Name="BackgroundPanel" />
@@ -10,10 +11,15 @@
<Label Text="{Loc 'character-setup-gui-character-setup-label'}" <Label Text="{Loc 'character-setup-gui-character-setup-label'}"
Margin="8 0 0 0" VAlign="Center" Margin="8 0 0 0" VAlign="Center"
StyleClasses="LabelHeadingBigger" /> StyleClasses="LabelHeadingBigger" />
<Button Name="StatsButton" HorizontalExpand="True" <Button Name="StatsButton" HorizontalExpand="True"
Text="{Loc 'character-setup-gui-character-setup-stats-button'}" Text="{Loc 'character-setup-gui-character-setup-stats-button'}"
StyleClasses="ButtonBig" StyleClasses="ButtonBig"
HorizontalAlignment="Right" /> HorizontalAlignment="Right" />
<cc:CommandButton Name="AdminRemarksButton"
Command="adminremarks"
Text="{Loc 'character-setup-gui-character-setup-adminremarks-button'}"
StyleClasses="ButtonBig" />
<Button Name="RulesButton" <Button Name="RulesButton"
Text="{Loc 'character-setup-gui-character-setup-rules-button'}" Text="{Loc 'character-setup-gui-character-setup-rules-button'}"
StyleClasses="ButtonBig"/> StyleClasses="ButtonBig"/>

View File

@@ -1,6 +1,7 @@
using Content.Client.Info; using Content.Client.Info;
using Content.Client.Info.PlaytimeStats; using Content.Client.Info.PlaytimeStats;
using Content.Client.Resources; using Content.Client.Resources;
using Content.Shared.CCVar;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -8,6 +9,7 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Client.Lobby.UI namespace Content.Client.Lobby.UI
@@ -18,28 +20,23 @@ namespace Content.Client.Lobby.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class CharacterSetupGui : Control public sealed partial class CharacterSetupGui : Control
{ {
private readonly IClientPreferencesManager _preferencesManager; [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
private readonly IEntityManager _entManager; [Dependency] private readonly IEntityManager _entManager = default!;
private readonly IPrototypeManager _protomanager; [Dependency] private readonly IPrototypeManager _protomanager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly Button _createNewCharacterButton; private readonly Button _createNewCharacterButton;
public event Action<int>? SelectCharacter; public event Action<int>? SelectCharacter;
public event Action<int>? DeleteCharacter; public event Action<int>? DeleteCharacter;
public CharacterSetupGui( public CharacterSetupGui(HumanoidProfileEditor profileEditor)
IEntityManager entManager,
IPrototypeManager protoManager,
IResourceCache resourceCache,
IClientPreferencesManager preferencesManager,
HumanoidProfileEditor profileEditor)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
_preferencesManager = preferencesManager; IoCManager.InjectDependencies(this);
_entManager = entManager;
_protomanager = protoManager;
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture var back = new StyleBoxTexture
{ {
Texture = panelTex, Texture = panelTex,
@@ -56,7 +53,7 @@ namespace Content.Client.Lobby.UI
_createNewCharacterButton.OnPressed += args => _createNewCharacterButton.OnPressed += args =>
{ {
preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random()); _preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
ReloadCharacterPickers(); ReloadCharacterPickers();
args.Event.Handle(); args.Event.Handle();
}; };
@@ -65,6 +62,8 @@ namespace Content.Client.Lobby.UI
RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open(); RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered(); StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
_cfg.OnValueChanged(CCVars.SeeOwnNotes, p => AdminRemarksButton.Visible = p, true);
} }
/// <summary> /// <summary>

View File

@@ -2,7 +2,6 @@ using Content.Client.Message;
using Content.Client.UserInterface.Systems.EscapeMenu; using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;

View File

@@ -14,6 +14,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
{ {
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;
[ViewVariables]
protected bool IsActive; protected bool IsActive;
protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET; protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET;
@@ -102,7 +103,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
args.Components.Add(component); args.Components.Add(component);
} }
private void RefreshOverlay(EntityUid uid) protected void RefreshOverlay(EntityUid uid)
{ {
if (uid != _player.LocalSession?.AttachedEntity) if (uid != _player.LocalSession?.AttachedEntity)
return; return;

View File

@@ -21,9 +21,16 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ShowHealthBarsComponent, AfterAutoHandleStateEvent>(OnHandleState);
_overlay = new(EntityManager, _prototype); _overlay = new(EntityManager, _prototype);
} }
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
{
RefreshOverlay(ent);
}
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component) protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)
{ {
base.UpdateInternal(component); base.UpdateInternal(component);

View File

@@ -17,6 +17,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
{ {
[Dependency] private readonly IPrototypeManager _prototypeMan = default!; [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
[ViewVariables]
public HashSet<string> DamageContainers = new(); public HashSet<string> DamageContainers = new();
public override void Initialize() public override void Initialize()
@@ -24,6 +25,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
base.Initialize(); base.Initialize();
SubscribeLocalEvent<DamageableComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent); SubscribeLocalEvent<DamageableComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
SubscribeLocalEvent<ShowHealthIconsComponent, AfterAutoHandleStateEvent>(OnHandleState);
} }
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthIconsComponent> component) protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthIconsComponent> component)
@@ -43,6 +45,11 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
DamageContainers.Clear(); DamageContainers.Clear();
} }
private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args)
{
RefreshOverlay(ent);
}
private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args) private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)
{ {
if (!IsActive) if (!IsActive)

View File

@@ -131,7 +131,8 @@ public sealed partial class BorgMenu : FancyWindow
_modules.Clear(); _modules.Clear();
foreach (var module in chassis.ModuleContainer.ContainedEntities) foreach (var module in chassis.ModuleContainer.ContainedEntities)
{ {
var control = new BorgModuleControl(module, _entity); var moduleComponent = _entity.GetComponent<BorgModuleComponent>(module);
var control = new BorgModuleControl(module, _entity, !moduleComponent.DefaultModule);
control.RemoveButtonPressed += () => control.RemoveButtonPressed += () =>
{ {
RemoveModuleButtonPressed?.Invoke(module); RemoveModuleButtonPressed?.Invoke(module);

View File

@@ -9,7 +9,7 @@ public sealed partial class BorgModuleControl : PanelContainer
{ {
public Action? RemoveButtonPressed; public Action? RemoveButtonPressed;
public BorgModuleControl(EntityUid entity, IEntityManager entityManager) public BorgModuleControl(EntityUid entity, IEntityManager entityManager, bool canRemove)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -20,6 +20,7 @@ public sealed partial class BorgModuleControl : PanelContainer
{ {
RemoveButtonPressed?.Invoke(); RemoveButtonPressed?.Invoke();
}; };
RemoveButton.Visible = canRemove;
} }
} }

View File

@@ -0,0 +1,43 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc 'borg-select-type-menu-title'}"
SetSize="550 300">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
<!-- Left pane: selection of borg type -->
<BoxContainer Orientation="Vertical" MinWidth="200" Margin="2 0">
<Label Text="{Loc 'borg-select-type-menu-available'}" StyleClasses="LabelHeading" />
<ScrollContainer HScrollEnabled="False" VerticalExpand="True">
<BoxContainer Name="SelectionsContainer" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<customControls:VSeparator />
<!-- Right pane: information about selected borg module, confirm button. -->
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="2 0">
<Label Text="{Loc 'borg-select-type-menu-information'}" StyleClasses="LabelHeading" />
<Control VerticalExpand="True">
<controls:Placeholder Name="InfoPlaceholder" PlaceholderText="{Loc 'borg-select-type-menu-select-type'}" />
<BoxContainer Name="InfoContents" Orientation="Vertical" Visible="False">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 4">
<EntityPrototypeView Name="ChassisView" Scale="2,2" />
<Label Name="NameLabel" HorizontalExpand="True" />
</BoxContainer>
<RichTextLabel Name="DescriptionLabel" VerticalExpand="True" VerticalAlignment="Top" />
</BoxContainer>
</Control>
<controls:ConfirmButton Name="ConfirmTypeButton" Text="{Loc 'borg-select-type-menu-confirm'}"
Disabled="True" HorizontalAlignment="Right"
MinWidth="200" />
</BoxContainer>
</BoxContainer>
<controls:StripeBack Margin="0 0 0 4">
<Label Text="{Loc 'borg-select-type-menu-bottom-text'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/>
</controls:StripeBack>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,81 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.Guidebook;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Silicons.Borgs;
/// <summary>
/// Menu used by borgs to select their type.
/// </summary>
/// <seealso cref="BorgSelectTypeUserInterface"/>
/// <seealso cref="BorgSwitchableTypeComponent"/>
[GenerateTypedNameReferences]
public sealed partial class BorgSelectTypeMenu : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private BorgTypePrototype? _selectedBorgType;
public event Action<ProtoId<BorgTypePrototype>>? ConfirmedBorgType;
[ValidatePrototypeId<GuideEntryPrototype>]
private static readonly List<ProtoId<GuideEntryPrototype>> GuidebookEntries = new() { "Cyborgs", "Robotics" };
public BorgSelectTypeMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var group = new ButtonGroup();
foreach (var borgType in _prototypeManager.EnumeratePrototypes<BorgTypePrototype>().OrderBy(PrototypeName))
{
var button = new Button
{
Text = PrototypeName(borgType),
Group = group,
};
button.OnPressed += _ =>
{
_selectedBorgType = borgType;
UpdateInformation(borgType);
};
SelectionsContainer.AddChild(button);
}
ConfirmTypeButton.OnPressed += ConfirmButtonPressed;
HelpGuidebookIds = GuidebookEntries;
}
private void UpdateInformation(BorgTypePrototype prototype)
{
_selectedBorgType = prototype;
InfoContents.Visible = true;
InfoPlaceholder.Visible = false;
ConfirmTypeButton.Disabled = false;
NameLabel.Text = PrototypeName(prototype);
DescriptionLabel.Text = Loc.GetString($"borg-type-{prototype.ID}-desc");
ChassisView.SetPrototype(prototype.DummyPrototype);
}
private void ConfirmButtonPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedBorgType == null)
return;
ConfirmedBorgType?.Invoke(_selectedBorgType);
}
private static string PrototypeName(BorgTypePrototype prototype)
{
return Loc.GetString($"borg-type-{prototype.ID}-name");
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Silicons.Borgs.Components;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Silicons.Borgs;
/// <summary>
/// User interface used by borgs to select their type.
/// </summary>
/// <seealso cref="BorgSelectTypeMenu"/>
/// <seealso cref="BorgSwitchableTypeComponent"/>
/// <seealso cref="BorgSwitchableTypeUiKey"/>
[UsedImplicitly]
public sealed class BorgSelectTypeUserInterface : BoundUserInterface
{
[ViewVariables]
private BorgSelectTypeMenu? _menu;
public BorgSelectTypeUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<BorgSelectTypeMenu>();
_menu.ConfirmedBorgType += prototype => SendMessage(new BorgSelectTypeMessage(prototype));
}
}

View File

@@ -0,0 +1,81 @@
using Content.Shared.Movement.Components;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Silicons.Borgs;
/// <summary>
/// Client side logic for borg type switching. Sets up primarily client-side visual information.
/// </summary>
/// <seealso cref="SharedBorgSwitchableTypeSystem"/>
/// <seealso cref="BorgSwitchableTypeComponent"/>
public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
{
[Dependency] private readonly BorgSystem _borgSystem = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BorgSwitchableTypeComponent, AfterAutoHandleStateEvent>(AfterStateHandler);
SubscribeLocalEvent<BorgSwitchableTypeComponent, ComponentStartup>(OnComponentStartup);
}
private void OnComponentStartup(Entity<BorgSwitchableTypeComponent> ent, ref ComponentStartup args)
{
UpdateEntityAppearance(ent);
}
private void AfterStateHandler(Entity<BorgSwitchableTypeComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateEntityAppearance(ent);
}
protected override void UpdateEntityAppearance(
Entity<BorgSwitchableTypeComponent> entity,
BorgTypePrototype prototype)
{
if (TryComp(entity, out SpriteComponent? sprite))
{
sprite.LayerSetState(BorgVisualLayers.Body, prototype.SpriteBodyState);
sprite.LayerSetState(BorgVisualLayers.LightStatus, prototype.SpriteToggleLightState);
}
if (TryComp(entity, out BorgChassisComponent? chassis))
{
_borgSystem.SetMindStates(
(entity.Owner, chassis),
prototype.SpriteHasMindState,
prototype.SpriteNoMindState);
if (TryComp(entity, out AppearanceComponent? appearance))
{
// Queue update so state changes apply.
_appearance.QueueUpdate(entity, appearance);
}
}
if (prototype.SpriteBodyMovementState is { } movementState)
{
var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
spriteMovement.NoMovementLayers.Clear();
spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
{
State = prototype.SpriteBodyState,
};
spriteMovement.MovementLayers.Clear();
spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
{
State = movementState,
};
}
else
{
RemComp<SpriteMovementComponent>(entity);
}
base.UpdateEntityAppearance(entity, prototype);
}
}

View File

@@ -92,4 +92,18 @@ public sealed class BorgSystem : SharedBorgSystem
sprite.LayerSetState(MMIVisualLayers.Base, state); sprite.LayerSetState(MMIVisualLayers.Base, state);
} }
} }
/// <summary>
/// Sets the sprite states used for the borg "is there a mind or not" indication.
/// </summary>
/// <param name="borg">The entity and component to modify.</param>
/// <param name="hasMindState">The state to use if the borg has a mind.</param>
/// <param name="noMindState">The state to use if the borg has no mind.</param>
/// <seealso cref="BorgChassisComponent.HasMindState"/>
/// <seealso cref="BorgChassisComponent.NoMindState"/>
public void SetMindStates(Entity<BorgChassisComponent> borg, string hasMindState, string noMindState)
{
borg.Comp.HasMindState = hasMindState;
borg.Comp.NoMindState = noMindState;
}
} }

View File

@@ -1,7 +1,7 @@
using Content.Shared.Singularity.EntitySystems; using Content.Shared.Singularity.EntitySystems;
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
namespace Content.Client.Singularity.EntitySystems; namespace Content.Client.Singularity.Systems;
/// <summary> /// <summary>
/// The client-side version of <see cref="SharedEventHorizonSystem"/>. /// The client-side version of <see cref="SharedEventHorizonSystem"/>.

View File

@@ -0,0 +1,12 @@
using Content.Shared.Singularity.EntitySystems;
using Content.Shared.Singularity.Components;
namespace Content.Client.Singularity.Systems;
/// <summary>
/// The client-side version of <see cref="SharedSingularityGeneratorSystem"/>.
/// Manages <see cref="SingularityGeneratorComponent"/>s.
/// Exists to make relevant signal handlers (ie: <see cref="SharedSingularityGeneratorSystem.OnEmagged"/>) work on the client.
/// </summary>
public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSystem
{}

View File

@@ -5,7 +5,7 @@ using Robust.Client.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Client.Singularity.EntitySystems; namespace Content.Client.Singularity.Systems;
/// <summary> /// <summary>
/// The client-side version of <see cref="SharedSingularitySystem"/>. /// The client-side version of <see cref="SharedSingularitySystem"/>.

View File

@@ -307,12 +307,6 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
_entity.GetNetEntity(storageEnt), _entity.GetNetEntity(storageEnt),
new ItemStorageLocation(DraggingRotation, position))); new ItemStorageLocation(DraggingRotation, position)));
} }
else
{
_entity.RaisePredictiveEvent(new StorageRemoveItemEvent(
_entity.GetNetEntity(draggingGhost.Entity),
_entity.GetNetEntity(storageEnt)));
}
_menuDragHelper.EndDrag(); _menuDragHelper.EndDrag();
_container?.BuildItemPieces(); _container?.BuildItemPieces();

View File

@@ -146,8 +146,8 @@ namespace Content.Server.Abilities.Mime
mimePowers.ReadyToRepent = false; mimePowers.ReadyToRepent = false;
mimePowers.VowBroken = false; mimePowers.VowBroken = false;
AddComp<MutedComponent>(uid); AddComp<MutedComponent>(uid);
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert); _alertsSystem.ClearAlert(uid, mimePowers.VowBrokenAlert);
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert); _alertsSystem.ShowAlert(uid, mimePowers.VowAlert);
_actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid); _actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid);
} }
} }

View File

@@ -22,6 +22,7 @@ using Content.Shared.Administration;
using Content.Shared.Administration.Components; using Content.Shared.Administration.Components;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
using Content.Shared.Clumsy;
using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Components;
using Content.Shared.Cluwne; using Content.Shared.Cluwne;
using Content.Shared.Damage; using Content.Shared.Damage;

View File

@@ -1,27 +1,27 @@
using Content.Server.Administration.Components; using Content.Server.Administration.Components;
using Content.Shared.Climbing.Components; using Content.Shared.Climbing.Components;
using Content.Shared.Climbing.Events; using Content.Shared.Clumsy;
using Content.Shared.Climbing.Systems;
using Content.Shared.Interaction.Components;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Administration.Systems; namespace Content.Server.Administration.Systems;
public sealed class SuperBonkSystem: EntitySystem public sealed class SuperBonkSystem : EntitySystem
{ {
[Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly BonkSystem _bonkSystem = default!; [Dependency] private readonly ClumsySystem _clumsySystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
SubscribeLocalEvent<SuperBonkComponent, MobStateChangedEvent>(OnMobStateChanged); SubscribeLocalEvent<SuperBonkComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
} }
public void StartSuperBonk(EntityUid target, float delay = 0.1f, bool stopWhenDead = false ) public void StartSuperBonk(EntityUid target, float delay = 0.1f, bool stopWhenDead = false)
{ {
//The other check in the code to stop when the target dies does not work if the target is already dead. //The other check in the code to stop when the target dies does not work if the target is already dead.
@@ -31,7 +31,6 @@ public sealed class SuperBonkSystem: EntitySystem
return; return;
} }
var hadClumsy = EnsureComp<ClumsyComponent>(target, out _); var hadClumsy = EnsureComp<ClumsyComponent>(target, out _);
var tables = EntityQueryEnumerator<BonkableComponent>(); var tables = EntityQueryEnumerator<BonkableComponent>();
@@ -79,16 +78,17 @@ public sealed class SuperBonkSystem: EntitySystem
private void Bonk(SuperBonkComponent comp) private void Bonk(SuperBonkComponent comp)
{ {
var uid = comp.Tables.Current.Key; var uid = comp.Tables.Current.Key;
var bonkComp = comp.Tables.Current.Value;
// It would be very weird for something without a transform component to have a bonk component // It would be very weird for something without a transform component to have a bonk component
// but just in case because I don't want to crash the server. // but just in case because I don't want to crash the server.
if (!HasComp<TransformComponent>(uid)) if (!HasComp<TransformComponent>(uid) || !TryComp<ClumsyComponent>(comp.Target, out var clumsyComp))
return; return;
_transformSystem.SetCoordinates(comp.Target, Transform(uid).Coordinates); _transformSystem.SetCoordinates(comp.Target, Transform(uid).Coordinates);
_bonkSystem.TryBonk(comp.Target, uid, bonkComp); _clumsySystem.HitHeadClumsy((comp.Target, clumsyComp), uid);
_audioSystem.PlayPvs(clumsyComp.TableBonkSound, comp.Target);
} }
private void OnMobStateChanged(EntityUid uid, SuperBonkComponent comp, MobStateChangedEvent args) private void OnMobStateChanged(EntityUid uid, SuperBonkComponent comp, MobStateChangedEvent args)

View File

@@ -25,6 +25,16 @@ public sealed class TagCommand : ToolshedCommand
}); });
} }
[CommandImplementation("with")]
public IEnumerable<EntityUid> With(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<EntityUid> entities,
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> tag)
{
_tag ??= GetSys<TagSystem>();
return entities.Where(e => _tag.HasTag(e, tag.Evaluate(ctx)!));
}
[CommandImplementation("add")] [CommandImplementation("add")]
public EntityUid Add( public EntityUid Add(
[CommandInvocationContext] IInvocationContext ctx, [CommandInvocationContext] IInvocationContext ctx,

View File

@@ -28,7 +28,8 @@ namespace Content.Server.Announcements
} }
else else
{ {
var message = string.Join(' ', new ArraySegment<string>(args, 1, args.Length-1)); // Explicit IEnumerable<string> due to overload ambiguity on .NET 9
var message = string.Join(' ', (IEnumerable<string>)new ArraySegment<string>(args, 1, args.Length-1));
chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold); chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold);
} }
shell.WriteLine("Sent!"); shell.WriteLine("Sent!");

View File

@@ -184,7 +184,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
return; return;
var players = _playerManager.Sessions var players = _playerManager.Sessions
.Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame) .Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) && status == PlayerGameStatus.JoinedGame)
.ToList(); .ToList();
ChooseAntags((uid, component), players, midround: true); ChooseAntags((uid, component), players, midround: true);

View File

@@ -1,6 +1,6 @@
using System.Globalization;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.Ghost; using Content.Server.Ghost;
using Content.Server.Hands.Systems; using Content.Server.Hands.Systems;
using Content.Server.Inventory; using Content.Server.Inventory;
@@ -14,6 +14,7 @@ using Content.Shared.Bed.Cryostorage;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.Climbing.Systems; using Content.Shared.Climbing.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.GameTicking;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.StationRecords; using Content.Shared.StationRecords;
@@ -26,7 +27,6 @@ using Robust.Shared.Containers;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using System.Globalization;
namespace Content.Server.Bed.Cryostorage; namespace Content.Server.Bed.Cryostorage;

View File

@@ -38,6 +38,7 @@ public sealed class SeedExtractorSystem : EntitySystem
args.User, PopupType.Medium); args.User, PopupType.Medium);
QueueDel(args.Used); QueueDel(args.Used);
args.Handled = true;
var amount = _random.Next(seedExtractor.BaseMinSeeds, seedExtractor.BaseMaxSeeds + 1); var amount = _random.Next(seedExtractor.BaseMinSeeds, seedExtractor.BaseMaxSeeds + 1);
var coords = Transform(uid).Coordinates; var coords = Transform(uid).Coordinates;

View File

@@ -424,7 +424,7 @@ public record struct PriceCalculationEvent()
[ByRefEvent] [ByRefEvent]
public record struct EstimatedPriceCalculationEvent() public record struct EstimatedPriceCalculationEvent()
{ {
public EntityPrototype Prototype; public required EntityPrototype Prototype;
/// <summary> /// <summary>
/// The total price of the entity. /// The total price of the entity.

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Hypospray.Events;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
@@ -85,14 +86,44 @@ public sealed class HypospraySystem : SharedHypospraySystem
string? msgFormat = null; string? msgFormat = null;
if (target == user) // Self event
msgFormat = "hypospray-component-inject-self-message"; var selfEvent = new SelfBeforeHyposprayInjectsEvent(user, entity.Owner, target);
else if (EligibleEntity(user, EntityManager, component) && _interaction.TryRollClumsy(user, component.ClumsyFailChance)) RaiseLocalEvent(user, selfEvent);
if (selfEvent.Cancelled)
{ {
msgFormat = "hypospray-component-inject-self-clumsy-message"; _popup.PopupEntity(Loc.GetString(selfEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user);
target = user; return false;
} }
target = selfEvent.TargetGettingInjected;
if (!EligibleEntity(target, EntityManager, component))
return false;
// Target event
var targetEvent = new TargetBeforeHyposprayInjectsEvent(user, entity.Owner, target);
RaiseLocalEvent(target, targetEvent);
if (targetEvent.Cancelled)
{
_popup.PopupEntity(Loc.GetString(targetEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user);
return false;
}
target = targetEvent.TargetGettingInjected;
if (!EligibleEntity(target, EntityManager, component))
return false;
// The target event gets priority for the overriden message.
if (targetEvent.InjectMessageOverride != null)
msgFormat = targetEvent.InjectMessageOverride;
else if (selfEvent.InjectMessageOverride != null)
msgFormat = selfEvent.InjectMessageOverride;
else if (target == user)
msgFormat = "hypospray-component-inject-self-message";
if (!_solutionContainers.TryGetSolution(uid, component.SolutionName, out var hypoSpraySoln, out var hypoSpraySolution) || hypoSpraySolution.Volume == 0) if (!_solutionContainers.TryGetSolution(uid, component.SolutionName, out var hypoSpraySoln, out var hypoSpraySolution) || hypoSpraySolution.Volume == 0)
{ {
_popup.PopupEntity(Loc.GetString("hypospray-component-empty-message"), target, user); _popup.PopupEntity(Loc.GetString("hypospray-component-empty-message"), target, user);

View File

@@ -13,6 +13,7 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Nutrition.EntitySystems;
namespace Content.Server.Chemistry.EntitySystems; namespace Content.Server.Chemistry.EntitySystems;
@@ -20,6 +21,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
{ {
[Dependency] private readonly BloodstreamSystem _blood = default!; [Dependency] private readonly BloodstreamSystem _blood = default!;
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!; [Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -31,13 +33,14 @@ public sealed class InjectorSystem : SharedInjectorSystem
private bool TryUseInjector(Entity<InjectorComponent> injector, EntityUid target, EntityUid user) private bool TryUseInjector(Entity<InjectorComponent> injector, EntityUid target, EntityUid user)
{ {
var isOpenOrIgnored = injector.Comp.IgnoreClosed || !_openable.IsClosed(target);
// Handle injecting/drawing for solutions // Handle injecting/drawing for solutions
if (injector.Comp.ToggleState == InjectorToggleMode.Inject) if (injector.Comp.ToggleState == InjectorToggleMode.Inject)
{ {
if (SolutionContainers.TryGetInjectableSolution(target, out var injectableSolution, out _)) if (isOpenOrIgnored && SolutionContainers.TryGetInjectableSolution(target, out var injectableSolution, out _))
return TryInject(injector, target, injectableSolution.Value, user, false); return TryInject(injector, target, injectableSolution.Value, user, false);
if (SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _)) if (isOpenOrIgnored && SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _))
return TryInject(injector, target, refillableSolution.Value, user, true); return TryInject(injector, target, refillableSolution.Value, user, true);
if (TryComp<BloodstreamComponent>(target, out var bloodstream)) if (TryComp<BloodstreamComponent>(target, out var bloodstream))
@@ -58,7 +61,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
} }
// Draw from an object (food, beaker, etc) // Draw from an object (food, beaker, etc)
if (SolutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _)) if (isOpenOrIgnored && SolutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _))
return TryDraw(injector, target, drawableSolution.Value, user); return TryDraw(injector, target, drawableSolution.Value, user);
Popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message", Popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message",

View File

@@ -16,6 +16,7 @@ using Content.Shared.Cluwne;
using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Components;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Content.Shared.NameModifier.EntitySystems; using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Clumsy;
namespace Content.Server.Cluwne; namespace Content.Server.Cluwne;

View File

@@ -82,6 +82,8 @@ public sealed class CrayonSystem : SharedCrayonSystem
if (component.DeleteEmpty && component.Charges <= 0) if (component.DeleteEmpty && component.Charges <= 0)
UseUpCrayon(uid, args.User); UseUpCrayon(uid, args.User);
else
_uiSystem.ServerSendUiMessage(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState));
} }
private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEvent args) private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEvent args)

View File

@@ -518,16 +518,23 @@ namespace Content.Server.Database
public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt) public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
var roleBanDetails = await db.DbContext.RoleBan
.Where(b => b.Id == id)
.Select(b => new { b.BanTime, b.PlayerUserId })
.SingleOrDefaultAsync();
var ban = await db.DbContext.RoleBan.SingleOrDefaultAsync(b => b.Id == id); if (roleBanDetails == default)
if (ban is null)
return; return;
ban.Severity = severity;
ban.Reason = reason; await db.DbContext.RoleBan
ban.ExpirationTime = expiration?.UtcDateTime; .Where(b => b.BanTime == roleBanDetails.BanTime && b.PlayerUserId == roleBanDetails.PlayerUserId)
ban.LastEditedById = editedBy; .ExecuteUpdateAsync(setters => setters
ban.LastEditedAt = editedAt.UtcDateTime; .SetProperty(b => b.Severity, severity)
await db.DbContext.SaveChangesAsync(); .SetProperty(b => b.Reason, reason)
.SetProperty(b => b.ExpirationTime, expiration.HasValue ? expiration.Value.UtcDateTime : (DateTime?)null)
.SetProperty(b => b.LastEditedById, editedBy)
.SetProperty(b => b.LastEditedAt, editedAt.UtcDateTime)
);
} }
#endregion #endregion

View File

@@ -184,6 +184,6 @@ namespace Content.Server.GameTicking
=> UserHasJoinedGame(session.UserId); => UserHasJoinedGame(session.UserId);
public bool UserHasJoinedGame(NetUserId userId) public bool UserHasJoinedGame(NetUserId userId)
=> PlayerGameStatuses[userId] == PlayerGameStatus.JoinedGame; => PlayerGameStatuses.TryGetValue(userId, out var status) && status == PlayerGameStatus.JoinedGame;
} }
} }

View File

@@ -7,12 +7,12 @@ using Content.Server.Spawners.Components;
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.GameTicking;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Players; using Content.Shared.Players;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Roles.Jobs; using Content.Shared.Roles.Jobs;
using JetBrains.Annotations;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Network; using Robust.Shared.Network;
@@ -455,71 +455,4 @@ namespace Content.Server.GameTicking
#endregion #endregion
} }
/// <summary>
/// Event raised broadcast before a player is spawned by the GameTicker.
/// You can use this event to spawn a player off-station on late-join but also at round start.
/// When this event is handled, the GameTicker will not perform its own player-spawning logic.
/// </summary>
[PublicAPI]
public sealed class PlayerBeforeSpawnEvent : HandledEntityEventArgs
{
public ICommonSession Player { get; }
public HumanoidCharacterProfile Profile { get; }
public string? JobId { get; }
public bool LateJoin { get; }
public EntityUid Station { get; }
public PlayerBeforeSpawnEvent(ICommonSession player,
HumanoidCharacterProfile profile,
string? jobId,
bool lateJoin,
EntityUid station)
{
Player = player;
Profile = profile;
JobId = jobId;
LateJoin = lateJoin;
Station = station;
}
}
/// <summary>
/// Event raised both directed and broadcast when a player has been spawned by the GameTicker.
/// You can use this to handle people late-joining, or to handle people being spawned at round start.
/// Can be used to give random players a role, modify their equipment, etc.
/// </summary>
[PublicAPI]
public sealed class PlayerSpawnCompleteEvent : EntityEventArgs
{
public EntityUid Mob { get; }
public ICommonSession Player { get; }
public string? JobId { get; }
public bool LateJoin { get; }
public bool Silent { get; }
public EntityUid Station { get; }
public HumanoidCharacterProfile Profile { get; }
// 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,
bool silent,
int joinOrder,
EntityUid station,
HumanoidCharacterProfile profile)
{
Mob = mob;
Player = player;
JobId = jobId;
LateJoin = lateJoin;
Silent = silent;
Station = station;
Profile = profile;
JoinOrder = joinOrder;
}
}
} }

View File

@@ -6,6 +6,7 @@ using Content.Server.Mind;
using Content.Server.Points; using Content.Server.Points;
using Content.Server.RoundEnd; using Content.Server.RoundEnd;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.GameTicking;
using Content.Shared.GameTicking.Components; using Content.Shared.GameTicking.Components;
using Content.Shared.Points; using Content.Shared.Points;
using Content.Shared.Storage; using Content.Shared.Storage;

View File

@@ -34,10 +34,11 @@ public sealed class GatewayGeneratorSystem : EntitySystem
[Dependency] private readonly GatewaySystem _gateway = default!; [Dependency] private readonly GatewaySystem _gateway = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedMapSystem _maps = default!; [Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly SharedSalvageSystem _salvage = default!;
[Dependency] private readonly TileSystem _tile = default!; [Dependency] private readonly TileSystem _tile = default!;
[ValidatePrototypeId<DatasetPrototype>] [ValidatePrototypeId<LocalizedDatasetPrototype>]
private const string PlanetNames = "names_borer"; private const string PlanetNames = "NamesBorer";
// TODO: // TODO:
// Fix shader some more // Fix shader some more
@@ -102,7 +103,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
var mapId = _mapManager.CreateMap(); var mapId = _mapManager.CreateMap();
var mapUid = _mapManager.GetMapEntityId(mapId); var mapUid = _mapManager.GetMapEntityId(mapId);
var gatewayName = SharedSalvageSystem.GetFTLName(_protoManager.Index<DatasetPrototype>(PlanetNames), seed); var gatewayName = _salvage.GetFTLName(_protoManager.Index<LocalizedDatasetPrototype>(PlanetNames), seed);
_metadata.SetEntityName(mapUid, gatewayName); _metadata.SetEntityName(mapUid, gatewayName);
var origin = new Vector2i(random.Next(-MaxOffset, MaxOffset), random.Next(-MaxOffset, MaxOffset)); var origin = new Vector2i(random.Next(-MaxOffset, MaxOffset), random.Next(-MaxOffset, MaxOffset));

View File

@@ -77,7 +77,20 @@ public sealed class DefibrillatorSystem : EntitySystem
Zap(uid, target, args.User, component); Zap(uid, target, args.User, component);
} }
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null) /// <summary>
/// Checks if you can actually defib a target.
/// </summary>
/// <param name="uid">Uid of the defib</param>
/// <param name="target">Uid of the target getting defibbed</param>
/// <param name="user">Uid of the entity using the defibrillator</param>
/// <param name="component">Defib component</param>
/// <param name="targetCanBeAlive">
/// If true, the target can be alive. If false, the function will check if the target is alive and will return false if they are.
/// </param>
/// <returns>
/// Returns true if the target is valid to be defibed, false otherwise.
/// </returns>
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null, bool targetCanBeAlive = false)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return false; return false;
@@ -98,15 +111,25 @@ public sealed class DefibrillatorSystem : EntitySystem
if (!_powerCell.HasActivatableCharge(uid, user: user)) if (!_powerCell.HasActivatableCharge(uid, user: user))
return false; return false;
if (_mobState.IsAlive(target, mobState)) if (!targetCanBeAlive && _mobState.IsAlive(target, mobState))
return false; return false;
if (!component.CanDefibCrit && _mobState.IsCritical(target, mobState)) if (!targetCanBeAlive && !component.CanDefibCrit && _mobState.IsCritical(target, mobState))
return false; return false;
return true; return true;
} }
/// <summary>
/// Tries to start defibrillating the target. If the target is valid, will start the defib do-after.
/// </summary>
/// <param name="uid">Uid of the defib</param>
/// <param name="target">Uid of the target getting defibbed</param>
/// <param name="user">Uid of the entity using the defibrillator</param>
/// <param name="component">Defib component</param>
/// <returns>
/// Returns true if the defibrillation do-after started, otherwise false.
/// </returns>
public bool TryStartZap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null) public bool TryStartZap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
@@ -124,21 +147,38 @@ public sealed class DefibrillatorSystem : EntitySystem
}); });
} }
public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null, MobStateComponent? mob = null, MobThresholdsComponent? thresholds = null) /// <summary>
/// Tries to defibrillate the target with the given defibrillator.
/// </summary>
public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
{ {
if (!Resolve(uid, ref component) || !Resolve(target, ref mob, ref thresholds, false)) if (!Resolve(uid, ref component))
return; return;
// clowns zap themselves
if (HasComp<ClumsyComponent>(user) && user != target)
{
Zap(uid, user, user, component);
return;
}
if (!_powerCell.TryUseActivatableCharge(uid, user: user)) if (!_powerCell.TryUseActivatableCharge(uid, user: user))
return; return;
var selfEvent = new SelfBeforeDefibrillatorZapsEvent(user, uid, target);
RaiseLocalEvent(user, selfEvent);
target = selfEvent.DefibTarget;
// Ensure thet new target is still valid.
if (selfEvent.Cancelled || !CanZap(uid, target, user, component, true))
return;
var targetEvent = new TargetBeforeDefibrillatorZapsEvent(user, uid, target);
RaiseLocalEvent(target, targetEvent);
target = targetEvent.DefibTarget;
if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true))
return;
if (!TryComp<MobStateComponent>(target, out var mob) ||
!TryComp<MobThresholdsComponent>(target, out var thresholds))
return;
_audio.PlayPvs(component.ZapSound, uid); _audio.PlayPvs(component.ZapSound, uid);
_electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true); _electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true);
component.NextZapTime = _timing.CurTime + component.ZapDelay; component.NextZapTime = _timing.CurTime + component.ZapDelay;

View File

@@ -4,7 +4,6 @@ using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Server.Emp; using Content.Server.Emp;
using Content.Server.GameTicking;
using Content.Server.Medical.CrewMonitoring; using Content.Server.Medical.CrewMonitoring;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
@@ -14,8 +13,10 @@ using Content.Shared.Damage;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.GameTicking;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Medical.SuitSensor; using Content.Shared.Medical.SuitSensor;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Verbs; using Content.Shared.Verbs;
@@ -383,7 +384,7 @@ public sealed class SuitSensorSystem : EntitySystem
// Get mob total damage crit threshold // Get mob total damage crit threshold
int? totalDamageThreshold = null; int? totalDamageThreshold = null;
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, Shared.Mobs.MobState.Critical, out var critThreshold)) if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
totalDamageThreshold = critThreshold.Value.Int(); totalDamageThreshold = critThreshold.Value.Int();
// finally, form suit sensor status // finally, form suit sensor status

View File

@@ -11,8 +11,8 @@ public sealed partial class PathfindingSystem
/// </summary> /// </summary>
public record struct BreadthPathArgs() public record struct BreadthPathArgs()
{ {
public Vector2i Start; public required Vector2i Start;
public List<Vector2i> Ends; public required List<Vector2i> Ends;
public bool Diagonals = false; public bool Diagonals = false;

View File

@@ -19,7 +19,7 @@ public sealed partial class PathfindingSystem
public List<Vector2i> Points = new(); public List<Vector2i> Points = new();
public List<Vector2i> Path = new(); public List<Vector2i> Path = new();
public Dictionary<Vector2i, Vector2i> CameFrom; public Dictionary<Vector2i, Vector2i>? CameFrom;
} }
public record struct SplinePathArgs(SimplePathArgs Args) public record struct SplinePathArgs(SimplePathArgs Args)

View File

@@ -84,6 +84,6 @@ public sealed partial class PathfindingSystem
public float MaxWiden = 7f; public float MaxWiden = 7f;
public List<Vector2i> Path; public required List<Vector2i> Path;
} }
} }

View File

@@ -44,7 +44,7 @@ namespace Content.Server.Nutrition.EntitySystems
public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil) public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
{ {
if (!EntityManager.TryGetComponent(target, out FoodComponent? food)) if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
return (false, true); return (false, false);
//Prevents food usage with a wrong utensil //Prevents food usage with a wrong utensil
if ((food.Utensil & utensil.Comp.Types) == 0) if ((food.Utensil & utensil.Comp.Types) == 0)

View File

@@ -53,7 +53,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
return; return;
// no other humans to kill // no other humans to kill
var allHumans = _mind.GetAliveHumansExcept(args.MindId); var allHumans = _mind.GetAliveHumans(args.MindId);
if (allHumans.Count == 0) if (allHumans.Count == 0)
{ {
args.Cancelled = true; args.Cancelled = true;
@@ -77,14 +77,14 @@ public sealed class KillPersonConditionSystem : EntitySystem
return; return;
// no other humans to kill // no other humans to kill
var allHumans = _mind.GetAliveHumansExcept(args.MindId); var allHumans = _mind.GetAliveHumans(args.MindId);
if (allHumans.Count == 0) if (allHumans.Count == 0)
{ {
args.Cancelled = true; args.Cancelled = true;
return; return;
} }
var allHeads = new List<EntityUid>(); var allHeads = new HashSet<Entity<MindComponent>>();
foreach (var person in allHumans) foreach (var person in allHumans)
{ {
if (TryComp<MindComponent>(person, out var mind) && mind.OwnedEntity is { } ent && HasComp<CommandStaffComponent>(ent)) if (TryComp<MindComponent>(person, out var mind) && mind.OwnedEntity is { } ent && HasComp<CommandStaffComponent>(ent))

View File

@@ -314,6 +314,9 @@ public sealed class MoverController : SharedMoverController
var linearInput = Vector2.Zero; var linearInput = Vector2.Zero;
var brakeInput = 0f; var brakeInput = 0f;
var angularInput = 0f; var angularInput = 0f;
var linearCount = 0;
var brakeCount = 0;
var angularCount = 0;
foreach (var (pilotUid, pilot, _, consoleXform) in pilots) foreach (var (pilotUid, pilot, _, consoleXform) in pilots)
{ {
@@ -322,24 +325,27 @@ public sealed class MoverController : SharedMoverController
if (brakes > 0f) if (brakes > 0f)
{ {
brakeInput += brakes; brakeInput += brakes;
brakeCount++;
} }
if (strafe.Length() > 0f) if (strafe.Length() > 0f)
{ {
var offsetRotation = consoleXform.LocalRotation; var offsetRotation = consoleXform.LocalRotation;
linearInput += offsetRotation.RotateVec(strafe); linearInput += offsetRotation.RotateVec(strafe);
linearCount++;
} }
if (rotation != 0f) if (rotation != 0f)
{ {
angularInput += rotation; angularInput += rotation;
angularCount++;
} }
} }
var count = pilots.Count; // Don't slow down the shuttle if there's someone just looking at the console
linearInput /= count; linearInput /= Math.Max(1, linearCount);
angularInput /= count; angularInput /= Math.Max(1, angularCount);
brakeInput /= count; brakeInput /= Math.Max(1, brakeCount);
// Handle shuttle movement // Handle shuttle movement
if (brakeInput > 0f) if (brakeInput > 0f)

View File

@@ -199,6 +199,9 @@ public sealed partial class PolymorphSystem : EntitySystem
var targetTransformComp = Transform(uid); var targetTransformComp = Transform(uid);
if (configuration.PolymorphSound != null)
_audio.PlayPvs(configuration.PolymorphSound, targetTransformComp.Coordinates);
var child = Spawn(configuration.Entity, _transform.GetMapCoordinates(uid, targetTransformComp), rotation: _transform.GetWorldRotation(uid)); var child = Spawn(configuration.Entity, _transform.GetMapCoordinates(uid, targetTransformComp), rotation: _transform.GetWorldRotation(uid));
MakeSentientCommand.MakeSentient(child, EntityManager); MakeSentientCommand.MakeSentient(child, EntityManager);
@@ -288,6 +291,9 @@ public sealed partial class PolymorphSystem : EntitySystem
var uidXform = Transform(uid); var uidXform = Transform(uid);
var parentXform = Transform(parent); var parentXform = Transform(parent);
if (component.Configuration.ExitPolymorphSound != null)
_audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates);
_transform.SetParent(parent, parentXform, uidXform.ParentUid); _transform.SetParent(parent, parentXform, uidXform.ParentUid);
_transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation); _transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation);

View File

@@ -1,5 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Parallax; using Content.Server.Parallax;
using Content.Shared.Maps;
using Content.Shared.Parallax.Biomes; using Content.Shared.Parallax.Biomes;
using Content.Shared.Procedural; using Content.Shared.Procedural;
using Content.Shared.Procedural.PostGeneration; using Content.Shared.Procedural.PostGeneration;
@@ -15,27 +16,35 @@ public sealed partial class DungeonJob
/// </summary> /// </summary>
private async Task PostGen(BiomeDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random) private async Task PostGen(BiomeDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
{ {
if (_entManager.TryGetComponent(_gridUid, out BiomeComponent? biomeComp)) if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome))
return; return;
biomeComp = _entManager.AddComponent<BiomeComponent>(_gridUid);
var biomeSystem = _entManager.System<BiomeSystem>(); var biomeSystem = _entManager.System<BiomeSystem>();
biomeSystem.SetTemplate(_gridUid, biomeComp, _prototype.Index(dunGen.BiomeTemplate));
var seed = random.Next(); var seed = random.Next();
var xformQuery = _entManager.GetEntityQuery<TransformComponent>(); var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
foreach (var node in dungeon.RoomTiles) var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
while (tiles.MoveNext(out var tileRef))
{ {
var node = tileRef.Value.GridIndices;
if (reservedTiles.Contains(node)) if (reservedTiles.Contains(node))
continue; continue;
if (dunGen.TileMask is not null)
{
if (!dunGen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Value.Tile.TypeId]).ID))
continue;
}
// Need to set per-tile to override data. // Need to set per-tile to override data.
if (biomeSystem.TryGetTile(node, biomeComp.Layers, seed, _grid, out var tile)) if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, _grid, out var tile))
{ {
_maps.SetTile(_gridUid, _grid, node, tile.Value); _maps.SetTile(_gridUid, _grid, node, tile.Value);
} }
if (biomeSystem.TryGetDecals(node, biomeComp.Layers, seed, _grid, out var decals)) if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, _grid, out var decals))
{ {
foreach (var decal in decals) foreach (var decal in decals)
{ {
@@ -43,7 +52,7 @@ public sealed partial class DungeonJob
} }
} }
if (biomeSystem.TryGetEntity(node, biomeComp, _grid, out var entityProto)) if (tile is not null && biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile.Value, seed, _grid, out var entityProto))
{ {
var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector)); var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
var xform = xformQuery.Get(ent); var xform = xformQuery.Get(ent);
@@ -61,7 +70,5 @@ public sealed partial class DungeonJob
if (!ValidateResume()) if (!ValidateResume())
return; return;
} }
biomeComp.Enabled = false;
} }
} }

View File

@@ -28,7 +28,7 @@ public sealed partial class SalvageSystem
var mission = GetMission(_prototypeManager.Index<SalvageDifficultyPrototype>(missionparams.Difficulty), missionparams.Seed); var mission = GetMission(_prototypeManager.Index<SalvageDifficultyPrototype>(missionparams.Difficulty), missionparams.Seed);
data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1); data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1);
_labelSystem.Label(cdUid, GetFTLName(_prototypeManager.Index<DatasetPrototype>("names_borer"), missionparams.Seed)); _labelSystem.Label(cdUid, GetFTLName(_prototypeManager.Index<LocalizedDatasetPrototype>("NamesBorer"), missionparams.Seed));
_audio.PlayPvs(component.PrintSound, uid); _audio.PlayPvs(component.PrintSound, uid);
UpdateConsoles((station.Value, data)); UpdateConsoles((station.Value, data));

View File

@@ -104,7 +104,9 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
destComp.BeaconsOnly = true; destComp.BeaconsOnly = true;
destComp.RequireCoordinateDisk = true; destComp.RequireCoordinateDisk = true;
destComp.Enabled = true; destComp.Enabled = true;
_metaData.SetEntityName(mapUid, SharedSalvageSystem.GetFTLName(_prototypeManager.Index<DatasetPrototype>("names_borer"), _missionParams.Seed)); _metaData.SetEntityName(
mapUid,
_entManager.System<SharedSalvageSystem>().GetFTLName(_prototypeManager.Index<LocalizedDatasetPrototype>("NamesBorer"), _missionParams.Seed));
_entManager.AddComponent<FTLBeaconComponent>(mapUid); _entManager.AddComponent<FTLBeaconComponent>(mapUid);
// Saving the mission mapUid to a CD is made optional, in case one is somehow made in a process without a CD entity // Saving the mission mapUid to a CD is made optional, in case one is somehow made in a process without a CD entity

View File

@@ -32,7 +32,7 @@ public interface IGridSpawnGroup
public float MaximumDistance { get; } public float MaximumDistance { get; }
/// <inheritdoc /> /// <inheritdoc />
public ProtoId<DatasetPrototype>? NameDataset { get; } public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
/// <inheritdoc /> /// <inheritdoc />
int MinCount { get; set; } int MinCount { get; set; }
@@ -75,7 +75,7 @@ public sealed class DungeonSpawnGroup : IGridSpawnGroup
public float MaximumDistance { get; } public float MaximumDistance { get; }
/// <inheritdoc /> /// <inheritdoc />
public ProtoId<DatasetPrototype>? NameDataset { get; } public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
/// <inheritdoc /> /// <inheritdoc />
public int MinCount { get; set; } = 1; public int MinCount { get; set; } = 1;
@@ -106,7 +106,7 @@ public sealed class GridSpawnGroup : IGridSpawnGroup
/// <inheritdoc /> /// <inheritdoc />
public float MaximumDistance { get; } public float MaximumDistance { get; }
public ProtoId<DatasetPrototype>? NameDataset { get; } public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
public int MinCount { get; set; } = 1; public int MinCount { get; set; } = 1;
public int MaxCount { get; set; } = 1; public int MaxCount { get; set; } = 1;
public ComponentRegistry AddComponents { get; set; } = new(); public ComponentRegistry AddComponents { get; set; } = new();

View File

@@ -19,10 +19,10 @@ using Content.Shared.Administration;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.GameTicking;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Parallax.Biomes; using Content.Shared.Parallax.Biomes;
using Content.Shared.Preferences;
using Content.Shared.Salvage; using Content.Shared.Salvage;
using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Components;
using Content.Shared.Tiles; using Content.Shared.Tiles;

View File

@@ -208,7 +208,7 @@ public sealed partial class ShuttleSystem
if (_protoManager.TryIndex(group.NameDataset, out var dataset)) if (_protoManager.TryIndex(group.NameDataset, out var dataset))
{ {
_metadata.SetEntityName(spawned, SharedSalvageSystem.GetFTLName(dataset, _random.Next())); _metadata.SetEntityName(spawned, _salvage.GetFTLName(dataset, _random.Next()));
} }
if (group.Hide) if (group.Hide)

View File

@@ -8,6 +8,7 @@ using Content.Server.Station.Systems;
using Content.Server.Stunnable; using Content.Server.Stunnable;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Salvage;
using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.Systems;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -51,6 +52,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedSalvageSystem _salvage = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!; [Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly StunSystem _stuns = default!; [Dependency] private readonly StunSystem _stuns = default!;

View File

@@ -0,0 +1,82 @@
using Content.Server.Inventory;
using Content.Server.Radio.Components;
using Content.Shared.Inventory;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Silicons.Borgs;
/// <summary>
/// Server-side logic for borg type switching. Handles more heavyweight and server-specific switching logic.
/// </summary>
public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
{
[Dependency] private readonly BorgSystem _borgSystem = default!;
[Dependency] private readonly ServerInventorySystem _inventorySystem = default!;
protected override void SelectBorgModule(Entity<BorgSwitchableTypeComponent> ent, ProtoId<BorgTypePrototype> borgType)
{
var prototype = Prototypes.Index(borgType);
// Assign radio channels
string[] radioChannels = [.. ent.Comp.InherentRadioChannels, .. prototype.RadioChannels];
if (TryComp(ent, out IntrinsicRadioTransmitterComponent? transmitter))
transmitter.Channels = [.. radioChannels];
if (TryComp(ent, out ActiveRadioComponent? activeRadio))
activeRadio.Channels = [.. radioChannels];
// Borg transponder for the robotics console
if (TryComp(ent, out BorgTransponderComponent? transponder))
{
_borgSystem.SetTransponderSprite(
(ent.Owner, transponder),
new SpriteSpecifier.Rsi(new ResPath("Mobs/Silicon/chassis.rsi"), prototype.SpriteBodyState));
_borgSystem.SetTransponderName(
(ent.Owner, transponder),
Loc.GetString($"borg-type-{borgType}-transponder"));
}
// Configure modules
if (TryComp(ent, out BorgChassisComponent? chassis))
{
var chassisEnt = (ent.Owner, chassis);
_borgSystem.SetMaxModules(
chassisEnt,
prototype.ExtraModuleCount + prototype.DefaultModules.Length);
_borgSystem.SetModuleWhitelist(chassisEnt, prototype.ModuleWhitelist);
foreach (var module in prototype.DefaultModules)
{
var moduleEntity = Spawn(module);
var borgModule = Comp<BorgModuleComponent>(moduleEntity);
_borgSystem.SetBorgModuleDefault((moduleEntity, borgModule), true);
_borgSystem.InsertModule(chassisEnt, moduleEntity);
}
}
// Configure special components
if (Prototypes.TryIndex(ent.Comp.SelectedBorgType, out var previousPrototype))
{
if (previousPrototype.AddComponents is { } removeComponents)
EntityManager.RemoveComponents(ent, removeComponents);
}
if (prototype.AddComponents is { } addComponents)
{
EntityManager.AddComponents(ent, addComponents);
}
// Configure inventory template (used for hat spacing)
if (TryComp(ent, out InventoryComponent? inventory))
{
_inventorySystem.SetTemplateId((ent.Owner, inventory), prototype.InventoryTemplateId);
}
base.SelectBorgModule(ent, borgType);
}
}

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Components;
using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Containers; using Robust.Shared.Containers;
namespace Content.Server.Silicons.Borgs; namespace Content.Server.Silicons.Borgs;
@@ -300,6 +301,24 @@ public sealed partial class BorgSystem
return true; return true;
} }
/// <summary>
/// Check if a module can be removed from a borg.
/// </summary>
/// <param name="borg">The borg that the module is being removed from.</param>
/// <param name="module">The module to remove from the borg.</param>
/// <param name="user">The user attempting to remove the module.</param>
/// <returns>True if the module can be removed.</returns>
public bool CanRemoveModule(
Entity<BorgChassisComponent> borg,
Entity<BorgModuleComponent> module,
EntityUid? user = null)
{
if (module.Comp.DefaultModule)
return false;
return true;
}
/// <summary> /// <summary>
/// Installs and activates all modules currently inside the borg's module container /// Installs and activates all modules currently inside the borg's module container
/// </summary> /// </summary>
@@ -369,4 +388,24 @@ public sealed partial class BorgSystem
var ev = new BorgModuleUninstalledEvent(uid); var ev = new BorgModuleUninstalledEvent(uid);
RaiseLocalEvent(module, ref ev); RaiseLocalEvent(module, ref ev);
} }
/// <summary>
/// Sets <see cref="BorgChassisComponent.MaxModules"/>.
/// </summary>
/// <param name="ent">The borg to modify.</param>
/// <param name="maxModules">The new max module count.</param>
public void SetMaxModules(Entity<BorgChassisComponent> ent, int maxModules)
{
ent.Comp.MaxModules = maxModules;
}
/// <summary>
/// Sets <see cref="BorgChassisComponent.ModuleWhitelist"/>.
/// </summary>
/// <param name="ent">The borg to modify.</param>
/// <param name="whitelist">The new module whitelist.</param>
public void SetModuleWhitelist(Entity<BorgChassisComponent> ent, EntityWhitelist? whitelist)
{
ent.Comp.ModuleWhitelist = whitelist;
}
} }

View File

@@ -8,6 +8,7 @@ using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.Components; using Content.Server.Explosion.Components;
using Robust.Shared.Utility;
namespace Content.Server.Silicons.Borgs; namespace Content.Server.Silicons.Borgs;
@@ -134,4 +135,20 @@ public sealed partial class BorgSystem
return false; return false;
} }
/// <summary>
/// Sets <see cref="BorgTransponderComponent.Sprite"/>.
/// </summary>
public void SetTransponderSprite(Entity<BorgTransponderComponent> ent, SpriteSpecifier sprite)
{
ent.Comp.Sprite = sprite;
}
/// <summary>
/// Sets <see cref="BorgTransponderComponent.Name"/>.
/// </summary>
public void SetTransponderName(Entity<BorgTransponderComponent> ent, string name)
{
ent.Comp.Name = name;
}
} }

View File

@@ -82,6 +82,9 @@ public sealed partial class BorgSystem
if (!component.ModuleContainer.Contains(module)) if (!component.ModuleContainer.Contains(module))
return; return;
if (!CanRemoveModule((uid, component), (module, Comp<BorgModuleComponent>(module)), args.Actor))
return;
_adminLog.Add(LogType.Action, LogImpact.Medium, _adminLog.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(args.Actor):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); _container.Remove(module, component.ModuleContainer);

View File

@@ -129,7 +129,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
if (module != null && CanInsertModule(uid, used, component, module, args.User)) if (module != null && CanInsertModule(uid, used, component, module, args.User))
{ {
_container.Insert(used, component.ModuleContainer); InsertModule((uid, component), used);
_adminLog.Add(LogType.Action, LogImpact.Low, _adminLog.Add(LogType.Action, LogImpact.Low,
$"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}"); $"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
args.Handled = true; args.Handled = true;
@@ -137,6 +137,19 @@ public sealed partial class BorgSystem : SharedBorgSystem
} }
} }
/// <summary>
/// Inserts a new module into a borg, the same as if a player inserted it manually.
/// </summary>
/// <para>
/// This does not run checks to see if the borg is actually allowed to be inserted, such as whitelists.
/// </para>
/// <param name="ent">The borg to insert into.</param>
/// <param name="module">The module to insert.</param>
public void InsertModule(Entity<BorgChassisComponent> ent, EntityUid module)
{
_container.Insert(module, ent.Comp.ModuleContainer);
}
// todo: consider transferring over the ghost role? managing that might suck. // todo: consider transferring over the ghost role? managing that might suck.
protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args) protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
{ {

View File

@@ -0,0 +1,280 @@
using Content.Server.StationEvents.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Dataset;
using Content.Shared.FixedPoint;
using Content.Shared.GameTicking.Components;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Linq;
namespace Content.Server.Silicons.Laws;
public sealed class IonStormSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SiliconLawSystem _siliconLaw = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
// funny
[ValidatePrototypeId<DatasetPrototype>]
private const string Threats = "IonStormThreats";
[ValidatePrototypeId<DatasetPrototype>]
private const string Objects = "IonStormObjects";
[ValidatePrototypeId<DatasetPrototype>]
private const string Crew = "IonStormCrew";
[ValidatePrototypeId<DatasetPrototype>]
private const string Adjectives = "IonStormAdjectives";
[ValidatePrototypeId<DatasetPrototype>]
private const string Verbs = "IonStormVerbs";
[ValidatePrototypeId<DatasetPrototype>]
private const string NumberBase = "IonStormNumberBase";
[ValidatePrototypeId<DatasetPrototype>]
private const string NumberMod = "IonStormNumberMod";
[ValidatePrototypeId<DatasetPrototype>]
private const string Areas = "IonStormAreas";
[ValidatePrototypeId<DatasetPrototype>]
private const string Feelings = "IonStormFeelings";
[ValidatePrototypeId<DatasetPrototype>]
private const string FeelingsPlural = "IonStormFeelingsPlural";
[ValidatePrototypeId<DatasetPrototype>]
private const string Musts = "IonStormMusts";
[ValidatePrototypeId<DatasetPrototype>]
private const string Requires = "IonStormRequires";
[ValidatePrototypeId<DatasetPrototype>]
private const string Actions = "IonStormActions";
[ValidatePrototypeId<DatasetPrototype>]
private const string Allergies = "IonStormAllergies";
[ValidatePrototypeId<DatasetPrototype>]
private const string AllergySeverities = "IonStormAllergySeverities";
[ValidatePrototypeId<DatasetPrototype>]
private const string Concepts = "IonStormConcepts";
[ValidatePrototypeId<DatasetPrototype>]
private const string Drinks = "IonStormDrinks";
[ValidatePrototypeId<DatasetPrototype>]
private const string Foods = "IonStormFoods";
/// <summary>
/// Randomly alters the laws of an individual silicon.
/// </summary>
public void IonStormTarget(Entity<SiliconLawBoundComponent, IonStormTargetComponent> ent, bool adminlog = true)
{
var lawBound = ent.Comp1;
var target = ent.Comp2;
if (!_robustRandom.Prob(target.Chance))
return;
var laws = _siliconLaw.GetLaws(ent, lawBound);
if (laws.Laws.Count == 0)
return;
// try to swap it out with a random lawset
if (_robustRandom.Prob(target.RandomLawsetChance))
{
var lawsets = _proto.Index<WeightedRandomPrototype>(target.RandomLawsets);
var lawset = lawsets.Pick(_robustRandom);
laws = _siliconLaw.GetLawset(lawset);
}
// clone it so not modifying stations lawset
laws = laws.Clone();
// shuffle them all
if (_robustRandom.Prob(target.ShuffleChance))
{
// hopefully work with existing glitched laws if there are multiple ion storms
var baseOrder = FixedPoint2.New(1);
foreach (var law in laws.Laws)
{
if (law.Order < baseOrder)
baseOrder = law.Order;
}
_robustRandom.Shuffle(laws.Laws);
// change order based on shuffled position
for (int i = 0; i < laws.Laws.Count; i++)
{
laws.Laws[i].Order = baseOrder + i;
}
}
// see if we can remove a random law
if (laws.Laws.Count > 0 && _robustRandom.Prob(target.RemoveChance))
{
var i = _robustRandom.Next(laws.Laws.Count);
laws.Laws.RemoveAt(i);
}
// generate a new law...
var newLaw = GenerateLaw();
// see if the law we add will replace a random existing law or be a new glitched order one
if (laws.Laws.Count > 0 && _robustRandom.Prob(target.ReplaceChance))
{
var i = _robustRandom.Next(laws.Laws.Count);
laws.Laws[i] = new SiliconLaw()
{
LawString = newLaw,
Order = laws.Laws[i].Order
};
}
else
{
laws.Laws.Insert(0, new SiliconLaw
{
LawString = newLaw,
Order = -1,
LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", _robustRandom.Next(5, 10)))
});
}
// sets all unobfuscated laws' indentifier in order from highest to lowest priority
// This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen
int orderDeduction = -1;
for (int i = 0; i < laws.Laws.Count; i++)
{
var notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString();
if (notNullIdentifier.Any(char.IsSymbol))
{
orderDeduction += 1;
}
else
{
laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString();
}
}
// adminlog is used to prevent adminlog spam.
if (adminlog)
_adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}");
// laws unique to this silicon, dont use station laws anymore
EnsureComp<SiliconLawProviderComponent>(ent);
var ev = new IonStormLawsEvent(laws);
RaiseLocalEvent(ent, ref ev);
}
// for your own sake direct your eyes elsewhere
private string GenerateLaw()
{
// pick all values ahead of time to make the logic cleaner
var threats = Pick(Threats);
var objects = Pick(Objects);
var crew1 = Pick(Crew);
var crew2 = Pick(Crew);
var adjective = Pick(Adjectives);
var verb = Pick(Verbs);
var number = Pick(NumberBase) + " " + Pick(NumberMod);
var area = Pick(Areas);
var feeling = Pick(Feelings);
var feelingPlural = Pick(FeelingsPlural);
var must = Pick(Musts);
var require = Pick(Requires);
var action = Pick(Actions);
var allergy = Pick(Allergies);
var allergySeverity = Pick(AllergySeverities);
var concept = Pick(Concepts);
var drink = Pick(Drinks);
var food = Pick(Foods);
var joined = $"{number} {adjective}";
// a lot of things have subjects of a threat/crew/object
var triple = _robustRandom.Next(0, 3) switch
{
0 => threats,
1 => crew1,
2 => objects,
_ => throw new IndexOutOfRangeException(),
};
var crewAll = _robustRandom.Prob(0.5f) ? crew2 : Loc.GetString("ion-storm-crew");
var objectsThreats = _robustRandom.Prob(0.5f) ? objects : threats;
var objectsConcept = _robustRandom.Prob(0.5f) ? objects : concept;
// s goes ahead of require, is/are
// i dont think theres a way to do this in fluent
var (who, plural) = _robustRandom.Next(0, 5) switch
{
0 => (Loc.GetString("ion-storm-you"), false),
1 => (Loc.GetString("ion-storm-the-station"), true),
2 => (Loc.GetString("ion-storm-the-crew"), true),
3 => (Loc.GetString("ion-storm-the-job", ("job", crew2)), false),
_ => (area, true) // THE SINGULARITY REQUIRES THE HAPPY CLOWNS
};
var jobChange = _robustRandom.Next(0, 3) switch
{
0 => crew1,
1 => Loc.GetString("ion-storm-clowns"),
_ => Loc.GetString("ion-storm-heads")
};
var part = Loc.GetString("ion-storm-part", ("part", _robustRandom.Prob(0.5f)));
var harm = _robustRandom.Next(0, 6) switch
{
0 => concept,
1 => $"{adjective} {threats}",
2 => $"{adjective} {objects}",
3 => Loc.GetString("ion-storm-adjective-things", ("adjective", adjective)),
4 => crew1,
_ => Loc.GetString("ion-storm-x-and-y", ("x", crew1), ("y", crew2))
};
if (plural) feeling = feelingPlural;
var subjects = _robustRandom.Prob(0.5f) ? objectsThreats : Loc.GetString("ion-storm-people");
// message logic!!!
return _robustRandom.Next(0, 35) switch
{
0 => Loc.GetString("ion-storm-law-on-station", ("joined", joined), ("subjects", triple)),
1 => Loc.GetString("ion-storm-law-no-shuttle", ("joined", joined), ("subjects", triple)),
2 => Loc.GetString("ion-storm-law-crew-are", ("who", crewAll), ("joined", joined), ("subjects", objectsThreats)),
3 => Loc.GetString("ion-storm-law-subjects-harmful", ("adjective", adjective), ("subjects", triple)),
4 => Loc.GetString("ion-storm-law-must-harmful", ("must", must)),
5 => Loc.GetString("ion-storm-law-thing-harmful", ("thing", _robustRandom.Prob(0.5f) ? concept : action)),
6 => Loc.GetString("ion-storm-law-job-harmful", ("adjective", adjective), ("job", crew1)),
7 => Loc.GetString("ion-storm-law-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
8 => Loc.GetString("ion-storm-law-not-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
9 => Loc.GetString("ion-storm-law-requires", ("who", who), ("plural", plural), ("thing", _robustRandom.Prob(0.5f) ? concept : require)),
10 => Loc.GetString("ion-storm-law-requires-subjects", ("who", who), ("plural", plural), ("joined", joined), ("subjects", triple)),
11 => Loc.GetString("ion-storm-law-allergic", ("who", who), ("plural", plural), ("severity", allergySeverity), ("allergy", _robustRandom.Prob(0.5f) ? concept : allergy)),
12 => Loc.GetString("ion-storm-law-allergic-subjects", ("who", who), ("plural", plural), ("severity", allergySeverity), ("adjective", adjective), ("subjects", _robustRandom.Prob(0.5f) ? objects : crew1)),
13 => Loc.GetString("ion-storm-law-feeling", ("who", who), ("feeling", feeling), ("concept", concept)),
14 => Loc.GetString("ion-storm-law-feeling-subjects", ("who", who), ("feeling", feeling), ("joined", joined), ("subjects", triple)),
15 => Loc.GetString("ion-storm-law-you-are", ("concept", concept)),
16 => Loc.GetString("ion-storm-law-you-are-subjects", ("joined", joined), ("subjects", triple)),
17 => Loc.GetString("ion-storm-law-you-must-always", ("must", must)),
18 => Loc.GetString("ion-storm-law-you-must-never", ("must", must)),
19 => Loc.GetString("ion-storm-law-eat", ("who", crewAll), ("adjective", adjective), ("food", _robustRandom.Prob(0.5f) ? food : triple)),
20 => Loc.GetString("ion-storm-law-drink", ("who", crewAll), ("adjective", adjective), ("drink", drink)),
21 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)),
22 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)),
23 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)),
24 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)),
25 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)),
26 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)),
27 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)),
28 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)),
29 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)),
30 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)),
31 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)),
32 => Loc.GetString("ion-storm-law-harm", ("who", harm)),
33 => Loc.GetString("ion-storm-law-protect", ("who", harm)),
_ => Loc.GetString("ion-storm-law-concept-verb", ("concept", concept), ("verb", verb), ("subjects", triple))
};
}
/// <summary>
/// Picks a random value from an ion storm dataset.
/// All ion storm datasets start with IonStorm.
/// </summary>
private string Pick(string name)
{
var dataset = _proto.Index<DatasetPrototype>(name);
return _robustRandom.Pick(dataset.Values);
}
}

View File

@@ -1,7 +1,6 @@
using System.Linq; using System.Linq;
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.Radio.Components; using Content.Server.Radio.Components;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
@@ -9,6 +8,7 @@ using Content.Shared.Administration;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.Emag.Components; using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.Roles; using Content.Shared.Roles;
@@ -17,12 +17,12 @@ using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed; using Robust.Shared.Toolshed;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
namespace Content.Server.Silicons.Laws; namespace Content.Server.Silicons.Laws;
@@ -50,9 +50,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
SubscribeLocalEvent<SiliconLawProviderComponent, GetSiliconLawsEvent>(OnDirectedGetLaws); SubscribeLocalEvent<SiliconLawProviderComponent, GetSiliconLawsEvent>(OnDirectedGetLaws);
SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws); SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws);
SubscribeLocalEvent<SiliconLawProviderComponent, MindAddedMessage>(OnLawProviderMindAdded);
SubscribeLocalEvent<SiliconLawProviderComponent, MindRemovedMessage>(OnLawProviderMindRemoved);
SubscribeLocalEvent<SiliconLawProviderComponent, GotEmaggedEvent>(OnEmagLawsAdded); SubscribeLocalEvent<SiliconLawProviderComponent, GotEmaggedEvent>(OnEmagLawsAdded);
SubscribeLocalEvent<EmagSiliconLawComponent, MindAddedMessage>(OnEmagMindAdded);
SubscribeLocalEvent<EmagSiliconLawComponent, MindRemovedMessage>(OnEmagMindRemoved);
} }
private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args)
@@ -67,10 +67,35 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
var msg = Loc.GetString("laws-notify"); var msg = Loc.GetString("laws-notify");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg)); var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, _chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.FromHex("#2ed2fd"));
actor.PlayerSession.Channel, colorOverride: Color.FromHex("#2ed2fd"));
if (!TryComp<SiliconLawProviderComponent>(uid, out var lawcomp))
return;
if (!lawcomp.Subverted)
return;
var modifedLawMsg = Loc.GetString("laws-notify-subverted");
var modifiedLawWrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", modifedLawMsg));
_chatManager.ChatMessageToOne(ChatChannel.Server, modifedLawMsg, modifiedLawWrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Red);
} }
private void OnLawProviderMindAdded(Entity<SiliconLawProviderComponent> ent, ref MindAddedMessage args)
{
if (!ent.Comp.Subverted)
return;
EnsureSubvertedSiliconRole(args.Mind);
}
private void OnLawProviderMindRemoved(Entity<SiliconLawProviderComponent> ent, ref MindRemovedMessage args)
{
if (!ent.Comp.Subverted)
return;
RemoveSubvertedSiliconRole(args.Mind);
}
private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent component, ToggleLawsScreenEvent args) private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent component, ToggleLawsScreenEvent args)
{ {
if (args.Handled || !TryComp<ActorComponent>(uid, out var actor)) if (args.Handled || !TryComp<ActorComponent>(uid, out var actor))
@@ -117,9 +142,12 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
// gotta tell player to check their laws // gotta tell player to check their laws
NotifyLawsChanged(uid, component.LawUploadSound); NotifyLawsChanged(uid, component.LawUploadSound);
// Show the silicon has been subverted.
component.Subverted = true;
// new laws may allow antagonist behaviour so make it clear for admins // new laws may allow antagonist behaviour so make it clear for admins
if (TryComp<EmagSiliconLawComponent>(uid, out var emag)) if(_mind.TryGetMind(uid, out var mindId, out _))
EnsureEmaggedRole(uid, emag); EnsureSubvertedSiliconRole(mindId);
} }
} }
@@ -130,6 +158,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
if (component.Lawset == null) if (component.Lawset == null)
component.Lawset = GetLawset(component.Laws); component.Lawset = GetLawset(component.Laws);
// Show the silicon has been subverted.
component.Subverted = true;
// Add the first emag law before the others // Add the first emag law before the others
component.Lawset?.Laws.Insert(0, new SiliconLaw component.Lawset?.Laws.Insert(0, new SiliconLaw
{ {
@@ -152,35 +183,25 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
base.OnGotEmagged(uid, component, ref args); base.OnGotEmagged(uid, component, ref args);
NotifyLawsChanged(uid, component.EmaggedSound); NotifyLawsChanged(uid, component.EmaggedSound);
EnsureEmaggedRole(uid, component); if(_mind.TryGetMind(uid, out var mindId, out _))
EnsureSubvertedSiliconRole(mindId);
_stunSystem.TryParalyze(uid, component.StunTime, true); _stunSystem.TryParalyze(uid, component.StunTime, true);
} }
private void OnEmagMindAdded(EntityUid uid, EmagSiliconLawComponent component, MindAddedMessage args) private void EnsureSubvertedSiliconRole(EntityUid mindId)
{ {
if (HasComp<EmaggedComponent>(uid))
EnsureEmaggedRole(uid, component);
}
private void OnEmagMindRemoved(EntityUid uid, EmagSiliconLawComponent component, MindRemovedMessage args)
{
if (component.AntagonistRole == null)
return;
_roles.MindTryRemoveRole<SubvertedSiliconRoleComponent>(args.Mind);
}
private void EnsureEmaggedRole(EntityUid uid, EmagSiliconLawComponent component)
{
if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _))
return;
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId)) if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon"); _roles.MindAddRole(mindId, "MindRoleSubvertedSilicon");
} }
private void RemoveSubvertedSiliconRole(EntityUid mindId)
{
if (_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
_roles.MindTryRemoveRole<SubvertedSiliconRoleComponent>(mindId);
}
public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null) public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))

View File

@@ -1,33 +0,0 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Content.Server.Singularity.EntitySystems;
namespace Content.Server.Singularity.Components;
[RegisterComponent]
public sealed partial class SingularityGeneratorComponent : Component
{
/// <summary>
/// The amount of power this generator has accumulated.
/// If you want to set this use <see cref="SingularityGeneratorSystem.SetPower"/>
/// </summary>
[DataField("power")]
[Access(friends:typeof(SingularityGeneratorSystem))]
public float Power = 0;
/// <summary>
/// The power threshold at which this generator will spawn a singularity.
/// If you want to set this use <see cref="SingularityGeneratorSystem.SetThreshold"/>
/// </summary>
[DataField("threshold")]
[Access(friends:typeof(SingularityGeneratorSystem))]
public float Threshold = 16;
/// <summary>
/// The prototype ID used to spawn a singularity.
/// </summary>
[DataField("spawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string? SpawnPrototype = "Singularity";
}

View File

@@ -196,6 +196,7 @@ namespace Content.Server.Singularity.EntitySystems
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcReceiver)) if (TryComp<ApcPowerReceiverComponent>(uid, out var apcReceiver))
{ {
apcReceiver.Load = component.PowerUseActive; apcReceiver.Load = component.PowerUseActive;
if (apcReceiver.Powered)
PowerOn(uid, component); PowerOn(uid, component);
} }
// Do not directly PowerOn(). // Do not directly PowerOn().

View File

@@ -1,14 +1,23 @@
using Content.Server.ParticleAccelerator.Components; using Content.Server.ParticleAccelerator.Components;
using Content.Server.Singularity.Components; using Content.Shared.Popups;
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
using Content.Shared.Singularity.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
using Robust.Shared.Timing;
namespace Content.Server.Singularity.EntitySystems; namespace Content.Server.Singularity.EntitySystems;
public sealed class SingularityGeneratorSystem : EntitySystem public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSystem
{ {
#region Dependencies #region Dependencies
[Dependency] private readonly IViewVariablesManager _vvm = default!; [Dependency] private readonly IViewVariablesManager _vvm = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
#endregion Dependencies #endregion Dependencies
public override void Initialize() public override void Initialize()
@@ -100,11 +109,37 @@ public sealed class SingularityGeneratorSystem : EntitySystem
/// <param name="args">The state of the beginning of the collision.</param> /// <param name="args">The state of the beginning of the collision.</param>
private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent component, ref StartCollideEvent args) private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent component, ref StartCollideEvent args)
{ {
if (EntityManager.TryGetComponent<SingularityGeneratorComponent>(args.OtherEntity, out var singularityGeneratorComponent)) if (!EntityManager.TryGetComponent<SingularityGeneratorComponent>(args.OtherEntity, out var generatorComp))
return;
if (_timing.CurTime < _metadata.GetPauseTime(uid) + generatorComp.NextFailsafe && !generatorComp.FailsafeDisabled)
{
EntityManager.QueueDeleteEntity(uid);
return;
}
var contained = true;
if (!generatorComp.FailsafeDisabled)
{
var transform = Transform(args.OtherEntity);
var directions = Enum.GetValues<Direction>().Length;
for (var i = 0; i < directions - 1; i += 2) // Skip every other direction, checking only cardinals
{
if (!CheckContainmentField((Direction)i, new Entity<SingularityGeneratorComponent>(args.OtherEntity, generatorComp), transform))
contained = false;
}
}
if (!contained && !generatorComp.FailsafeDisabled)
{
generatorComp.NextFailsafe = _timing.CurTime + generatorComp.FailsafeCooldown;
PopupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe", ("target", args.OtherEntity)), args.OtherEntity, PopupType.LargeCaution);
}
else
{ {
SetPower( SetPower(
args.OtherEntity, args.OtherEntity,
singularityGeneratorComponent.Power + component.State switch generatorComp.Power + component.State switch
{ {
ParticleAcceleratorPowerState.Standby => 0, ParticleAcceleratorPowerState.Standby => 0,
ParticleAcceleratorPowerState.Level0 => 1, ParticleAcceleratorPowerState.Level0 => 1,
@@ -113,10 +148,46 @@ public sealed class SingularityGeneratorSystem : EntitySystem
ParticleAcceleratorPowerState.Level3 => 8, ParticleAcceleratorPowerState.Level3 => 8,
_ => 0 _ => 0
}, },
singularityGeneratorComponent generatorComp
); );
}
EntityManager.QueueDeleteEntity(uid); EntityManager.QueueDeleteEntity(uid);
} }
}
#endregion Event Handlers #endregion Event Handlers
/// <summary>
/// Checks whether there's a containment field in a given direction away from the generator
/// </summary>
/// <param name="transform">The transform component of the singularity generator.</param>
/// <remarks>Mostly copied from <see cref="ContainmentFieldGeneratorSystem"/> </remarks>
private bool CheckContainmentField(Direction dir, Entity<SingularityGeneratorComponent> generator, TransformComponent transform)
{
var component = generator.Comp;
var (worldPosition, worldRotation) = _transformSystem.GetWorldPositionRotation(transform);
var dirRad = dir.ToAngle() + worldRotation;
var ray = new CollisionRay(worldPosition, dirRad.ToVec(), component.CollisionMask);
var rayCastResults = _physics.IntersectRay(transform.MapID, ray, component.FailsafeDistance, generator, false);
var genQuery = GetEntityQuery<ContainmentFieldComponent>();
RayCastResults? closestResult = null;
foreach (var result in rayCastResults)
{
if (genQuery.HasComponent(result.HitEntity))
closestResult = result;
break;
}
if (closestResult == null)
return false;
var ent = closestResult.Value.HitEntity;
// Check that the field can't be moved. The fields' transform parenting is weird, so skip that
return TryComp<PhysicsComponent>(ent, out var collidableComponent) && collidableComponent.BodyType == BodyType.Static;
}
} }

View File

@@ -1,64 +1,14 @@
using System.Linq;
using Content.Server.Silicons.Laws; using Content.Server.Silicons.Laws;
using Content.Server.StationEvents.Components; using Content.Server.StationEvents.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Dataset;
using Content.Shared.FixedPoint;
using Content.Shared.GameTicking.Components; using Content.Shared.GameTicking.Components;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components; using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Station.Components; using Content.Shared.Station.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events; namespace Content.Server.StationEvents.Events;
public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent> public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
{ {
[Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IonStormSystem _ionStorm = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SiliconLawSystem _siliconLaw = default!;
// funny
[ValidatePrototypeId<DatasetPrototype>]
private const string Threats = "IonStormThreats";
[ValidatePrototypeId<DatasetPrototype>]
private const string Objects = "IonStormObjects";
[ValidatePrototypeId<DatasetPrototype>]
private const string Crew = "IonStormCrew";
[ValidatePrototypeId<DatasetPrototype>]
private const string Adjectives = "IonStormAdjectives";
[ValidatePrototypeId<DatasetPrototype>]
private const string Verbs = "IonStormVerbs";
[ValidatePrototypeId<DatasetPrototype>]
private const string NumberBase = "IonStormNumberBase";
[ValidatePrototypeId<DatasetPrototype>]
private const string NumberMod = "IonStormNumberMod";
[ValidatePrototypeId<DatasetPrototype>]
private const string Areas = "IonStormAreas";
[ValidatePrototypeId<DatasetPrototype>]
private const string Feelings = "IonStormFeelings";
[ValidatePrototypeId<DatasetPrototype>]
private const string FeelingsPlural = "IonStormFeelingsPlural";
[ValidatePrototypeId<DatasetPrototype>]
private const string Musts = "IonStormMusts";
[ValidatePrototypeId<DatasetPrototype>]
private const string Requires = "IonStormRequires";
[ValidatePrototypeId<DatasetPrototype>]
private const string Actions = "IonStormActions";
[ValidatePrototypeId<DatasetPrototype>]
private const string Allergies = "IonStormAllergies";
[ValidatePrototypeId<DatasetPrototype>]
private const string AllergySeverities = "IonStormAllergySeverities";
[ValidatePrototypeId<DatasetPrototype>]
private const string Concepts = "IonStormConcepts";
[ValidatePrototypeId<DatasetPrototype>]
private const string Drinks = "IonStormDrinks";
[ValidatePrototypeId<DatasetPrototype>]
private const string Foods = "IonStormFoods";
protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
{ {
@@ -74,217 +24,7 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
if (CompOrNull<StationMemberComponent>(xform.GridUid)?.Station != chosenStation) if (CompOrNull<StationMemberComponent>(xform.GridUid)?.Station != chosenStation)
continue; continue;
if (!RobustRandom.Prob(target.Chance)) _ionStorm.IonStormTarget((ent, lawBound, target));
continue;
var laws = _siliconLaw.GetLaws(ent, lawBound);
if (laws.Laws.Count == 0)
continue;
// try to swap it out with a random lawset
if (RobustRandom.Prob(target.RandomLawsetChance))
{
var lawsets = PrototypeManager.Index<WeightedRandomPrototype>(target.RandomLawsets);
var lawset = lawsets.Pick(RobustRandom);
laws = _siliconLaw.GetLawset(lawset);
} }
else
{
// clone it so not modifying stations lawset
laws = laws.Clone();
}
// shuffle them all
if (RobustRandom.Prob(target.ShuffleChance))
{
// hopefully work with existing glitched laws if there are multiple ion storms
FixedPoint2 baseOrder = FixedPoint2.New(1);
foreach (var law in laws.Laws)
{
if (law.Order < baseOrder)
baseOrder = law.Order;
}
RobustRandom.Shuffle(laws.Laws);
// change order based on shuffled position
for (int i = 0; i < laws.Laws.Count; i++)
{
laws.Laws[i].Order = baseOrder + i;
}
}
// see if we can remove a random law
if (laws.Laws.Count > 0 && RobustRandom.Prob(target.RemoveChance))
{
var i = RobustRandom.Next(laws.Laws.Count);
laws.Laws.RemoveAt(i);
}
// generate a new law...
var newLaw = GenerateLaw();
// see if the law we add will replace a random existing law or be a new glitched order one
if (laws.Laws.Count > 0 && RobustRandom.Prob(target.ReplaceChance))
{
var i = RobustRandom.Next(laws.Laws.Count);
laws.Laws[i] = new SiliconLaw()
{
LawString = newLaw,
Order = laws.Laws[i].Order
};
}
else
{
laws.Laws.Insert(0, new SiliconLaw
{
LawString = newLaw,
Order = -1,
LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", RobustRandom.Next(5, 10)))
});
}
// sets all unobfuscated laws' indentifier in order from highest to lowest priority
// This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen
int orderDeduction = -1;
for (int i = 0; i < laws.Laws.Count; i++)
{
string notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString();
if (notNullIdentifier.Any(char.IsSymbol))
{
orderDeduction += 1;
}
else
{
laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString();
}
}
_adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}");
// laws unique to this silicon, dont use station laws anymore
EnsureComp<SiliconLawProviderComponent>(ent);
var ev = new IonStormLawsEvent(laws);
RaiseLocalEvent(ent, ref ev);
}
}
// for your own sake direct your eyes elsewhere
private string GenerateLaw()
{
// pick all values ahead of time to make the logic cleaner
var threats = Pick(Threats);
var objects = Pick(Objects);
var crew1 = Pick(Crew);
var crew2 = Pick(Crew);
var adjective = Pick(Adjectives);
var verb = Pick(Verbs);
var number = Pick(NumberBase) + " " + Pick(NumberMod);
var area = Pick(Areas);
var feeling = Pick(Feelings);
var feelingPlural = Pick(FeelingsPlural);
var must = Pick(Musts);
var require = Pick(Requires);
var action = Pick(Actions);
var allergy = Pick(Allergies);
var allergySeverity = Pick(AllergySeverities);
var concept = Pick(Concepts);
var drink = Pick(Drinks);
var food = Pick(Foods);
var joined = $"{number} {adjective}";
// a lot of things have subjects of a threat/crew/object
var triple = RobustRandom.Next(0, 3) switch
{
0 => threats,
1 => crew1,
2 => objects,
_ => throw new IndexOutOfRangeException(),
};
var crewAll = RobustRandom.Prob(0.5f) ? crew2 : Loc.GetString("ion-storm-crew");
var objectsThreats = RobustRandom.Prob(0.5f) ? objects : threats;
var objectsConcept = RobustRandom.Prob(0.5f) ? objects : concept;
// s goes ahead of require, is/are
// i dont think theres a way to do this in fluent
var (who, plural) = RobustRandom.Next(0, 5) switch
{
0 => (Loc.GetString("ion-storm-you"), false),
1 => (Loc.GetString("ion-storm-the-station"), true),
2 => (Loc.GetString("ion-storm-the-crew"), true),
3 => (Loc.GetString("ion-storm-the-job", ("job", crew2)), false),
_ => (area, true) // THE SINGULARITY REQUIRES THE HAPPY CLOWNS
};
var jobChange = RobustRandom.Next(0, 3) switch
{
0 => crew1,
1 => Loc.GetString("ion-storm-clowns"),
_ => Loc.GetString("ion-storm-heads")
};
var part = Loc.GetString("ion-storm-part", ("part", RobustRandom.Prob(0.5f)));
var harm = RobustRandom.Next(0, 6) switch
{
0 => concept,
1 => $"{adjective} {threats}",
2 => $"{adjective} {objects}",
3 => Loc.GetString("ion-storm-adjective-things", ("adjective", adjective)),
4 => crew1,
_ => Loc.GetString("ion-storm-x-and-y", ("x", crew1), ("y", crew2))
};
if (plural) feeling = feelingPlural;
var subjects = RobustRandom.Prob(0.5f) ? objectsThreats : Loc.GetString("ion-storm-people");
// message logic!!!
return RobustRandom.Next(0, 36) switch
{
0 => Loc.GetString("ion-storm-law-on-station", ("joined", joined), ("subjects", triple)),
1 => Loc.GetString("ion-storm-law-no-shuttle", ("joined", joined), ("subjects", triple)),
2 => Loc.GetString("ion-storm-law-crew-are", ("who", crewAll), ("joined", joined), ("subjects", objectsThreats)),
3 => Loc.GetString("ion-storm-law-subjects-harmful", ("adjective", adjective), ("subjects", triple)),
4 => Loc.GetString("ion-storm-law-must-harmful", ("must", must)),
5 => Loc.GetString("ion-storm-law-thing-harmful", ("thing", RobustRandom.Prob(0.5f) ? concept : action)),
6 => Loc.GetString("ion-storm-law-job-harmful", ("adjective", adjective), ("job", crew1)),
7 => Loc.GetString("ion-storm-law-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
8 => Loc.GetString("ion-storm-law-not-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
9 => Loc.GetString("ion-storm-law-requires", ("who", who), ("plural", plural), ("thing", RobustRandom.Prob(0.5f) ? concept : require)),
10 => Loc.GetString("ion-storm-law-requires-subjects", ("who", who), ("plural", plural), ("joined", joined), ("subjects", triple)),
11 => Loc.GetString("ion-storm-law-allergic", ("who", who), ("plural", plural), ("severity", allergySeverity), ("allergy", RobustRandom.Prob(0.5f) ? concept : allergy)),
12 => Loc.GetString("ion-storm-law-allergic-subjects", ("who", who), ("plural", plural), ("severity", allergySeverity), ("adjective", adjective), ("subjects", RobustRandom.Prob(0.5f) ? objects : crew1)),
13 => Loc.GetString("ion-storm-law-feeling", ("who", who), ("feeling", feeling), ("concept", concept)),
14 => Loc.GetString("ion-storm-law-feeling-subjects", ("who", who), ("feeling", feeling), ("joined", joined), ("subjects", triple)),
15 => Loc.GetString("ion-storm-law-you-are", ("concept", concept)),
16 => Loc.GetString("ion-storm-law-you-are-subjects", ("joined", joined), ("subjects", triple)),
17 => Loc.GetString("ion-storm-law-you-must-always", ("must", must)),
18 => Loc.GetString("ion-storm-law-you-must-never", ("must", must)),
19 => Loc.GetString("ion-storm-law-eat", ("who", crewAll), ("adjective", adjective), ("food", RobustRandom.Prob(0.5f) ? food : triple)),
20 => Loc.GetString("ion-storm-law-drink", ("who", crewAll), ("adjective", adjective), ("drink", drink)),
22 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)),
23 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)),
24 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)),
25 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)),
26 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)),
27 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)),
28 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)),
29 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)),
30 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)),
31 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)),
32 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)),
33 => Loc.GetString("ion-storm-law-harm", ("who", harm)),
34 => Loc.GetString("ion-storm-law-protect", ("who", harm)),
_ => Loc.GetString("ion-storm-law-concept-verb", ("concept", concept), ("verb", verb), ("subjects", triple))
};
}
/// <summary>
/// Picks a random value from an ion storm dataset.
/// All ion storm datasets start with IonStorm.
/// </summary>
private string Pick(string name)
{
var dataset = _proto.Index<DatasetPrototype>(name);
return RobustRandom.Pick(dataset.Values);
} }
} }

View File

@@ -1,9 +1,8 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO;
using Content.Server.Access.Systems; using Content.Server.Access.Systems;
using Content.Server.Forensics; using Content.Server.Forensics;
using Content.Server.GameTicking;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.GameTicking;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Preferences; using Content.Shared.Preferences;

View File

@@ -256,6 +256,11 @@ public sealed partial class StoreSystem
RaiseLocalEvent(buyer, listing.ProductEvent); RaiseLocalEvent(buyer, listing.ProductEvent);
} }
if (listing.DisableRefund)
{
component.RefundAllowed = false;
}
//log dat shit. //log dat shit.
_admin.Add(LogType.StorePurchase, _admin.Add(LogType.StorePurchase,
LogImpact.Low, LogImpact.Low,

View File

@@ -1,11 +1,10 @@
using Content.Server.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Traits; using Content.Shared.Traits;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
namespace Content.Server.Traits; namespace Content.Server.Traits;

View File

@@ -1,15 +1,12 @@
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Cargo.Systems; using Content.Server.Cargo.Systems;
using Content.Server.Interaction;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Stunnable;
using Content.Server.Weapons.Ranged.Components; using Content.Server.Weapons.Ranged.Components;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Effects; using Content.Shared.Effects;
using Content.Shared.Interaction.Components;
using Content.Shared.Projectiles; using Content.Shared.Projectiles;
using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged;
@@ -33,16 +30,13 @@ public sealed partial class GunSystem : SharedGunSystem
[Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly DamageExamineSystem _damageExamine = default!; [Dependency] private readonly DamageExamineSystem _damageExamine = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly PricingSystem _pricing = default!; [Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly StaminaSystem _stamina = default!; [Dependency] private readonly StaminaSystem _stamina = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
private const float DamagePitchVariation = 0.05f; private const float DamagePitchVariation = 0.05f;
public const float GunClumsyChance = 0.5f;
public override void Initialize() public override void Initialize()
{ {
@@ -71,28 +65,16 @@ public sealed partial class GunSystem : SharedGunSystem
{ {
userImpulse = true; userImpulse = true;
// Try a clumsy roll if (user != null)
// TODO: Who put this here
if (TryComp<ClumsyComponent>(user, out var clumsy) && gun.ClumsyProof == false)
{ {
for (var i = 0; i < ammo.Count; i++) var selfEvent = new SelfBeforeGunShotEvent(user.Value, (gunUid, gun), ammo);
RaiseLocalEvent(user.Value, selfEvent);
if (selfEvent.Cancelled)
{ {
if (_interaction.TryRollClumsy(user.Value, GunClumsyChance, clumsy))
{
// Wound them
Damageable.TryChangeDamage(user, clumsy.ClumsyDamage, origin: user);
_stun.TryParalyze(user.Value, TimeSpan.FromSeconds(3f), true);
// Apply salt to the wound ("Honk!")
Audio.PlayPvs(new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/bang.ogg"), gunUid);
Audio.PlayPvs(clumsy.ClumsySound, gunUid);
PopupSystem.PopupEntity(Loc.GetString("gun-clumsy"), user.Value);
userImpulse = false; userImpulse = false;
return; return;
} }
} }
}
var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem); var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem);
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem); var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);

View File

@@ -0,0 +1,41 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// Chat window opacity slider, controlling the alpha of the chat window background.
/// Goes from to 0 (completely transparent) to 1 (completely opaque)
/// </summary>
public static readonly CVarDef<float> ChatWindowOpacity =
CVarDef.Create("accessibility.chat_window_transparency", 0.85f, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// Toggle for visual effects that may potentially cause motion sickness.
/// Where reasonable, effects affected by this CVar should use an alternate effect.
/// Please do not use this CVar as a bandaid for effects that could otherwise be made accessible without issue.
/// </summary>
public static readonly CVarDef<bool> ReducedMotion =
CVarDef.Create("accessibility.reduced_motion", false, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<bool> ChatEnableColorName =
CVarDef.Create("accessibility.enable_color_name",
true,
CVar.CLIENTONLY | CVar.ARCHIVE,
"Toggles displaying names with individual colors.");
/// <summary>
/// Screen shake intensity slider, controlling the intensity of the CameraRecoilSystem.
/// Goes from 0 (no recoil at all) to 1 (regular amounts of recoil)
/// </summary>
public static readonly CVarDef<float> ScreenShakeIntensity =
CVarDef.Create("accessibility.screen_shake_intensity", 1f, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// A generic toggle for various visual effects that are color sensitive.
/// As of 2/16/24, only applies to progress bar colors.
/// </summary>
public static readonly CVarDef<bool> AccessibilityColorblindFriendly =
CVarDef.Create("accessibility.colorblind_friendly", false, CVar.CLIENTONLY | CVar.ARCHIVE);
}

View File

@@ -0,0 +1,39 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// Ahelp rate limit values are accounted in periods of this size (seconds).
/// After the period has passed, the count resets.
/// </summary>
/// <seealso cref="AhelpRateLimitCount"/>
public static readonly CVarDef<float> AhelpRateLimitPeriod =
CVarDef.Create("ahelp.rate_limit_period", 2f, CVar.SERVERONLY);
/// <summary>
/// How many ahelp messages are allowed in a single rate limit period.
/// </summary>
/// <seealso cref="AhelpRateLimitPeriod"/>
public static readonly CVarDef<int> AhelpRateLimitCount =
CVarDef.Create("ahelp.rate_limit_count", 10, CVar.SERVERONLY);
/// <summary>
/// Should the administrator's position be displayed in ahelp.
/// If it is is false, only the admin's ckey will be displayed in the ahelp.
/// </summary>
/// <seealso cref="AdminUseCustomNamesAdminRank"/>
/// <seealso cref="AhelpAdminPrefixWebhook"/>
public static readonly CVarDef<bool> AhelpAdminPrefix =
CVarDef.Create("ahelp.admin_prefix", false, CVar.SERVERONLY);
/// <summary>
/// Should the administrator's position be displayed in the webhook.
/// If it is is false, only the admin's ckey will be displayed in webhook.
/// </summary>
/// <seealso cref="AdminUseCustomNamesAdminRank"/>
/// <seealso cref="AhelpAdminPrefix"/>
public static readonly CVarDef<bool> AhelpAdminPrefixWebhook =
CVarDef.Create("ahelp.admin_prefix_webhook", false, CVar.SERVERONLY);
}

View File

@@ -0,0 +1,42 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// Controls if admin logs are enabled. Highly recommended to shut this off for development.
/// </summary>
public static readonly CVarDef<bool> AdminLogsEnabled =
CVarDef.Create("adminlogs.enabled", true, CVar.SERVERONLY);
public static readonly CVarDef<float> AdminLogsQueueSendDelay =
CVarDef.Create("adminlogs.queue_send_delay_seconds", 5f, CVar.SERVERONLY);
/// <summary>
/// When to skip the waiting time to save in-round admin logs, if no admin logs are currently being saved
/// </summary>
public static readonly CVarDef<int> AdminLogsQueueMax =
CVarDef.Create("adminlogs.queue_max", 5000, CVar.SERVERONLY);
/// <summary>
/// When to skip the waiting time to save pre-round admin logs, if no admin logs are currently being saved
/// </summary>
public static readonly CVarDef<int> AdminLogsPreRoundQueueMax =
CVarDef.Create("adminlogs.pre_round_queue_max", 5000, CVar.SERVERONLY);
/// <summary>
/// When to start dropping logs
/// </summary>
public static readonly CVarDef<int> AdminLogsDropThreshold =
CVarDef.Create("adminlogs.drop_threshold", 20000, CVar.SERVERONLY);
/// <summary>
/// How many logs to send to the client at once
/// </summary>
public static readonly CVarDef<int> AdminLogsClientBatchSize =
CVarDef.Create("adminlogs.client_batch_size", 1000, CVar.SERVERONLY);
public static readonly CVarDef<string> AdminLogsServerName =
CVarDef.Create("adminlogs.server_name", "unknown", CVar.SERVERONLY);
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// Time that players have to wait before rules can be accepted.
/// </summary>
public static readonly CVarDef<float> RulesWaitTime =
CVarDef.Create("rules.time", 45f, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Don't show rules to localhost/loopback interface.
/// </summary>
public static readonly CVarDef<bool> RulesExemptLocal =
CVarDef.Create("rules.exempt_local", true, CVar.SERVERONLY);
}

View File

@@ -0,0 +1,163 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
public static readonly CVarDef<bool> AdminAnnounceLogin =
CVarDef.Create("admin.announce_login", true, CVar.SERVERONLY);
public static readonly CVarDef<bool> AdminAnnounceLogout =
CVarDef.Create("admin.announce_logout", true, CVar.SERVERONLY);
/// <summary>
/// The token used to authenticate with the admin API. Leave empty to disable the admin API. This is a secret! Do not share!
/// </summary>
public static readonly CVarDef<string> AdminApiToken =
CVarDef.Create("admin.api_token", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
/// <summary>
/// Should users be able to see their own notes? Admins will be able to see and set notes regardless
/// </summary>
public static readonly CVarDef<bool> SeeOwnNotes =
CVarDef.Create("admin.see_own_notes", false, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Should the server play a quick sound to the active admins whenever a new player joins?
/// </summary>
public static readonly CVarDef<bool> AdminNewPlayerJoinSound =
CVarDef.Create("admin.new_player_join_sound", false, CVar.SERVERONLY);
/// <summary>
/// The amount of days before the note starts fading. It will slowly lose opacity until it reaches stale. Set to 0 to disable.
/// </summary>
public static readonly CVarDef<double> NoteFreshDays =
CVarDef.Create("admin.note_fresh_days", 91.31055, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// The amount of days before the note completely fades, and can only be seen by admins if they press "see more notes". Set to 0
/// if you want the note to immediately disappear without fading.
/// </summary>
public static readonly CVarDef<double> NoteStaleDays =
CVarDef.Create("admin.note_stale_days", 365.2422, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// How much time does the user have to wait in seconds before confirming that they saw an admin message?
/// </summary>
public static readonly CVarDef<float> MessageWaitTime =
CVarDef.Create("admin.message_wait_time", 3f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Default severity for role bans
/// </summary>
public static readonly CVarDef<string> RoleBanDefaultSeverity =
CVarDef.Create("admin.role_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Default severity for department bans
/// </summary>
public static readonly CVarDef<string> DepartmentBanDefaultSeverity =
CVarDef.Create("admin.department_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Default severity for server bans
/// </summary>
public static readonly CVarDef<string> ServerBanDefaultSeverity =
CVarDef.Create("admin.server_ban_default_severity", "High", CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Whether a server ban will ban the player's ip by default.
/// </summary>
public static readonly CVarDef<bool> ServerBanIpBanDefault =
CVarDef.Create("admin.server_ban_ip_ban_default", true, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Whether a server ban will ban the player's hardware id by default.
/// </summary>
public static readonly CVarDef<bool> ServerBanHwidBanDefault =
CVarDef.Create("admin.server_ban_hwid_ban_default", true, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Whether to use details from last connection for ip/hwid in the BanPanel.
/// </summary>
public static readonly CVarDef<bool> ServerBanUseLastDetails =
CVarDef.Create("admin.server_ban_use_last_details", true, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Whether to erase a player's chat messages and their entity from the game when banned.
/// </summary>
public static readonly CVarDef<bool> ServerBanErasePlayer =
CVarDef.Create("admin.server_ban_erase_player", false, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Minimum players sharing a connection required to create an alert. -1 to disable the alert.
/// </summary>
/// <remarks>
/// If you set this to 0 or 1 then it will alert on every connection, so probably don't do that.
/// </remarks>
public static readonly CVarDef<int> AdminAlertMinPlayersSharingConnection =
CVarDef.Create("admin.alert.min_players_sharing_connection", -1, CVar.SERVERONLY);
/// <summary>
/// Minimum explosion intensity to create an admin alert message. -1 to disable the alert.
/// </summary>
public static readonly CVarDef<int> AdminAlertExplosionMinIntensity =
CVarDef.Create("admin.alert.explosion_min_intensity", 60, CVar.SERVERONLY);
/// <summary>
/// Minimum particle accelerator strength to create an admin alert message.
/// </summary>
public static readonly CVarDef<int> AdminAlertParticleAcceleratorMinPowerState =
CVarDef.Create("admin.alert.particle_accelerator_min_power_state", 5, CVar.SERVERONLY); // strength 4
/// <summary>
/// Should the ban details in admin channel include PII? (IP, HWID, etc)
/// </summary>
public static readonly CVarDef<bool> AdminShowPIIOnBan =
CVarDef.Create("admin.show_pii_onban", false, CVar.SERVERONLY);
/// <summary>
/// If an admin joins a round by reading up or using the late join button, automatically
/// de-admin them.
/// </summary>
public static readonly CVarDef<bool> AdminDeadminOnJoin =
CVarDef.Create("admin.deadmin_on_join", false, CVar.SERVERONLY);
/// <summary>
/// Overrides the name the client sees in ahelps. Set empty to disable.
/// </summary>
public static readonly CVarDef<string> AdminAhelpOverrideClientName =
CVarDef.Create("admin.override_adminname_in_client_ahelp", string.Empty, CVar.SERVERONLY);
/// <summary>
/// The threshold of minutes to appear as a "new player" in the ahelp menu
/// If 0, appearing as a new player is disabled.
/// </summary>
public static readonly CVarDef<int> NewPlayerThreshold =
CVarDef.Create("admin.new_player_threshold", 0, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// How long an admin client can go without any input before being considered AFK.
/// </summary>
public static readonly CVarDef<float> AdminAfkTime =
CVarDef.Create("admin.afk_time", 600f, CVar.SERVERONLY);
/// <summary>
/// If true, admins are able to connect even if
/// <see cref="SoftMaxPlayers"/> would otherwise block regular players.
/// </summary>
public static readonly CVarDef<bool> AdminBypassMaxPlayers =
CVarDef.Create("admin.bypass_max_players", true, CVar.SERVERONLY);
/// <summary>
/// Determine if custom rank names are used.
/// If it is false, it'd use the actual rank name regardless of the individual's title.
/// </summary>
/// <seealso cref="AhelpAdminPrefix"/>
/// <seealso cref="AhelpAdminPrefixWebhook"/>
public static readonly CVarDef<bool> AdminUseCustomNamesAdminRank =
CVarDef.Create("admin.use_custom_names_admin_rank", true, CVar.SERVERONLY);
public static readonly CVarDef<bool> BanHardwareIds =
CVarDef.Create("ban.hardware_ids", true, CVar.SERVERONLY);
}

View File

@@ -0,0 +1,153 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// Whether gas differences will move entities.
/// </summary>
public static readonly CVarDef<bool> SpaceWind =
CVarDef.Create("atmos.space_wind", false, CVar.SERVERONLY);
/// <summary>
/// Divisor from maxForce (pressureDifference * 2.25f) to force applied on objects.
/// </summary>
public static readonly CVarDef<float> SpaceWindPressureForceDivisorThrow =
CVarDef.Create("atmos.space_wind_pressure_force_divisor_throw", 15f, CVar.SERVERONLY);
/// <summary>
/// Divisor from maxForce (pressureDifference * 2.25f) to force applied on objects.
/// </summary>
public static readonly CVarDef<float> SpaceWindPressureForceDivisorPush =
CVarDef.Create("atmos.space_wind_pressure_force_divisor_push", 2500f, CVar.SERVERONLY);
/// <summary>
/// The maximum velocity (not force) that may be applied to an object by atmospheric pressure differences.
/// Useful to prevent clipping through objects.
/// </summary>
public static readonly CVarDef<float> SpaceWindMaxVelocity =
CVarDef.Create("atmos.space_wind_max_velocity", 30f, CVar.SERVERONLY);
/// <summary>
/// The maximum force that may be applied to an object by pushing (i.e. not throwing) atmospheric pressure differences.
/// A "throwing" atmospheric pressure difference ignores this limit, but not the max. velocity limit.
/// </summary>
public static readonly CVarDef<float> SpaceWindMaxPushForce =
CVarDef.Create("atmos.space_wind_max_push_force", 20f, CVar.SERVERONLY);
/// <summary>
/// Whether monstermos tile equalization is enabled.
/// </summary>
public static readonly CVarDef<bool> MonstermosEqualization =
CVarDef.Create("atmos.monstermos_equalization", true, CVar.SERVERONLY);
/// <summary>
/// Whether monstermos explosive depressurization is enabled.
/// Needs <see cref="MonstermosEqualization"/> to be enabled to work.
/// </summary>
public static readonly CVarDef<bool> MonstermosDepressurization =
CVarDef.Create("atmos.monstermos_depressurization", true, CVar.SERVERONLY);
/// <summary>
/// Whether monstermos explosive depressurization will rip tiles..
/// Needs <see cref="MonstermosEqualization"/> and <see cref="MonstermosDepressurization"/> to be enabled to work.
/// WARNING: This cvar causes MAJOR contrast issues, and usually tends to make any spaced scene look very cluttered.
/// This not only usually looks strange, but can also reduce playability for people with impaired vision. Please think twice before enabling this on your server.
/// Also looks weird on slow spacing for unrelated reasons. If you do want to enable this, you should probably turn on instaspacing.
/// </summary>
public static readonly CVarDef<bool> MonstermosRipTiles =
CVarDef.Create("atmos.monstermos_rip_tiles", false, CVar.SERVERONLY);
/// <summary>
/// Whether explosive depressurization will cause the grid to gain an impulse.
/// Needs <see cref="MonstermosEqualization"/> and <see cref="MonstermosDepressurization"/> to be enabled to work.
/// </summary>
public static readonly CVarDef<bool> AtmosGridImpulse =
CVarDef.Create("atmos.grid_impulse", false, CVar.SERVERONLY);
/// <summary>
/// What fraction of air from a spaced tile escapes every tick.
/// 1.0 for instant spacing, 0.2 means 20% of remaining air lost each time
/// </summary>
public static readonly CVarDef<float> AtmosSpacingEscapeRatio =
CVarDef.Create("atmos.mmos_spacing_speed", 0.15f, CVar.SERVERONLY);
/// <summary>
/// Minimum amount of air allowed on a spaced tile before it is reset to 0 immediately in kPa
/// Since the decay due to SpacingEscapeRatio follows a curve, it would never reach 0.0 exactly
/// unless we truncate it somewhere.
/// </summary>
public static readonly CVarDef<float> AtmosSpacingMinGas =
CVarDef.Create("atmos.mmos_min_gas", 2.0f, CVar.SERVERONLY);
/// <summary>
/// How much wind can go through a single tile before that tile doesn't depressurize itself
/// (I.e spacing is limited in large rooms heading into smaller spaces)
/// </summary>
public static readonly CVarDef<float> AtmosSpacingMaxWind =
CVarDef.Create("atmos.mmos_max_wind", 500f, CVar.SERVERONLY);
/// <summary>
/// Whether atmos superconduction is enabled.
/// </summary>
/// <remarks> Disabled by default, superconduction is awful. </remarks>
public static readonly CVarDef<bool> Superconduction =
CVarDef.Create("atmos.superconduction", false, CVar.SERVERONLY);
/// <summary>
/// Heat loss per tile due to radiation at 20 degC, in W.
/// </summary>
public static readonly CVarDef<float> SuperconductionTileLoss =
CVarDef.Create("atmos.superconduction_tile_loss", 30f, CVar.SERVERONLY);
/// <summary>
/// Whether excited groups will be processed and created.
/// </summary>
public static readonly CVarDef<bool> ExcitedGroups =
CVarDef.Create("atmos.excited_groups", true, CVar.SERVERONLY);
/// <summary>
/// Whether all tiles in an excited group will clear themselves once being exposed to space.
/// Similar to <see cref="MonstermosDepressurization"/>, without none of the tile ripping or
/// things being thrown around very violently.
/// Needs <see cref="ExcitedGroups"/> to be enabled to work.
/// </summary>
public static readonly CVarDef<bool> ExcitedGroupsSpaceIsAllConsuming =
CVarDef.Create("atmos.excited_groups_space_is_all_consuming", false, CVar.SERVERONLY);
/// <summary>
/// Maximum time in milliseconds that atmos can take processing.
/// </summary>
public static readonly CVarDef<float> AtmosMaxProcessTime =
CVarDef.Create("atmos.max_process_time", 3f, CVar.SERVERONLY);
/// <summary>
/// Atmos tickrate in TPS. Atmos processing will happen every 1/TPS seconds.
/// </summary>
public static readonly CVarDef<float> AtmosTickRate =
CVarDef.Create("atmos.tickrate", 15f, CVar.SERVERONLY);
/// <summary>
/// Scale factor for how fast things happen in our atmosphere
/// simulation compared to real life. 1x means pumps run at 1x
/// speed. Players typically expect things to happen faster
/// in-game.
/// </summary>
public static readonly CVarDef<float> AtmosSpeedup =
CVarDef.Create("atmos.speedup", 8f, CVar.SERVERONLY);
/// <summary>
/// Like atmos.speedup, but only for gas and reaction heat values. 64x means
/// gases heat up and cool down 64x faster than real life.
/// </summary>
public static readonly CVarDef<float> AtmosHeatScale =
CVarDef.Create("atmos.heat_scale", 8f, CVar.SERVERONLY);
/// <summary>
/// Maximum explosion radius for explosions caused by bursting a gas tank ("max caps").
/// Setting this to zero disables the explosion but still allows the tank to burst and leak.
/// </summary>
public static readonly CVarDef<float> AtmosTankFragment =
CVarDef.Create("atmos.max_explosion_range", 26f, CVar.SERVERONLY);
}

View File

@@ -0,0 +1,61 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// How long we'll wait until re-sampling nearby objects for ambience. Should be pretty fast, but doesn't have to match the tick rate.
/// </summary>
public static readonly CVarDef<float> AmbientCooldown =
CVarDef.Create("ambience.cooldown", 0.1f, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// How large of a range to sample for ambience.
/// </summary>
public static readonly CVarDef<float> AmbientRange =
CVarDef.Create("ambience.range", 8f, CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Maximum simultaneous ambient sounds.
/// </summary>
public static readonly CVarDef<int> MaxAmbientSources =
CVarDef.Create("ambience.max_sounds", 16, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// The minimum value the user can set for ambience.max_sounds
/// </summary>
public static readonly CVarDef<int> MinMaxAmbientSourcesConfigured =
CVarDef.Create("ambience.min_max_sounds_configured", 16, CVar.REPLICATED | CVar.SERVER | CVar.CHEAT);
/// <summary>
/// The maximum value the user can set for ambience.max_sounds
/// </summary>
public static readonly CVarDef<int> MaxMaxAmbientSourcesConfigured =
CVarDef.Create("ambience.max_max_sounds_configured", 64, CVar.REPLICATED | CVar.SERVER | CVar.CHEAT);
/// <summary>
/// Ambience volume.
/// </summary>
public static readonly CVarDef<float> AmbienceVolume =
CVarDef.Create("ambience.volume", 1.5f, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// Ambience music volume.
/// </summary>
public static readonly CVarDef<float> AmbientMusicVolume =
CVarDef.Create("ambience.music_volume", 1.5f, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// Lobby / round end music volume.
/// </summary>
public static readonly CVarDef<float> LobbyMusicVolume =
CVarDef.Create("ambience.lobby_music_volume", 0.50f, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// UI volume.
/// </summary>
public static readonly CVarDef<float> InterfaceVolume =
CVarDef.Create("audio.interface_volume", 0.50f, CVar.ARCHIVE | CVar.CLIENTONLY);
}

View File

@@ -0,0 +1,26 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
public static readonly CVarDef<bool> LoocEnabled =
CVarDef.Create("looc.enabled", true, CVar.NOTIFY | CVar.REPLICATED);
public static readonly CVarDef<bool> AdminLoocEnabled =
CVarDef.Create("looc.enabled_admin", true, CVar.NOTIFY);
/// <summary>
/// True: Dead players can use LOOC
/// False: Dead player LOOC gets redirected to dead chat
/// </summary>
public static readonly CVarDef<bool> DeadLoocEnabled =
CVarDef.Create("looc.enabled_dead", false, CVar.NOTIFY | CVar.REPLICATED);
/// <summary>
/// True: Crit players can use LOOC
/// False: Crit player LOOC gets redirected to dead chat
/// </summary>
public static readonly CVarDef<bool> CritLoocEnabled =
CVarDef.Create("looc.enabled_crit", false, CVar.NOTIFY | CVar.REPLICATED);
}

View File

@@ -0,0 +1,27 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
public static readonly CVarDef<bool>
OocEnabled = CVarDef.Create("ooc.enabled", true, CVar.NOTIFY | CVar.REPLICATED);
public static readonly CVarDef<bool> AdminOocEnabled =
CVarDef.Create("ooc.enabled_admin", true, CVar.NOTIFY);
/// <summary>
/// If true, whenever OOC is disabled the Discord OOC relay will also be disabled.
/// </summary>
public static readonly CVarDef<bool> DisablingOOCDisablesRelay =
CVarDef.Create("ooc.disabling_ooc_disables_relay", true, CVar.SERVERONLY);
/// <summary>
/// Whether or not OOC chat should be enabled during a round.
/// </summary>
public static readonly CVarDef<bool> OocEnableDuringRound =
CVarDef.Create("ooc.enable_during_round", false, CVar.NOTIFY | CVar.REPLICATED | CVar.SERVER);
public static readonly CVarDef<bool> ShowOocPatronColor =
CVarDef.Create("ooc.show_ooc_patron_color", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.CLIENT);
}

View File

@@ -0,0 +1,68 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// Chat rate limit values are accounted in periods of this size (seconds).
/// After the period has passed, the count resets.
/// </summary>
/// <seealso cref="ChatRateLimitCount"/>
public static readonly CVarDef<float> ChatRateLimitPeriod =
CVarDef.Create("chat.rate_limit_period", 2f, CVar.SERVERONLY);
/// <summary>
/// How many chat messages are allowed in a single rate limit period.
/// </summary>
/// <remarks>
/// The total rate limit throughput per second is effectively
/// <see cref="ChatRateLimitCount"/> divided by <see cref="ChatRateLimitCount"/>.
/// </remarks>
/// <seealso cref="ChatRateLimitPeriod"/>
public static readonly CVarDef<int> ChatRateLimitCount =
CVarDef.Create("chat.rate_limit_count", 10, CVar.SERVERONLY);
/// <summary>
/// Minimum delay (in seconds) between notifying admins about chat message rate limit violations.
/// A negative value disables admin announcements.
/// </summary>
public static readonly CVarDef<int> ChatRateLimitAnnounceAdminsDelay =
CVarDef.Create("chat.rate_limit_announce_admins_delay", 15, CVar.SERVERONLY);
public static readonly CVarDef<int> ChatMaxMessageLength =
CVarDef.Create("chat.max_message_length", 1000, CVar.SERVER | CVar.REPLICATED);
public static readonly CVarDef<int> ChatMaxAnnouncementLength =
CVarDef.Create("chat.max_announcement_length", 256, CVar.SERVER | CVar.REPLICATED);
public static readonly CVarDef<bool> ChatSanitizerEnabled =
CVarDef.Create("chat.chat_sanitizer_enabled", true, CVar.SERVERONLY);
public static readonly CVarDef<bool> ChatShowTypingIndicator =
CVarDef.Create("chat.show_typing_indicator", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
public static readonly CVarDef<bool> ChatEnableFancyBubbles =
CVarDef.Create("chat.enable_fancy_bubbles",
true,
CVar.CLIENTONLY | CVar.ARCHIVE,
"Toggles displaying fancy speech bubbles, which display the speaking character's name.");
public static readonly CVarDef<bool> ChatFancyNameBackground =
CVarDef.Create("chat.fancy_name_background",
false,
CVar.CLIENTONLY | CVar.ARCHIVE,
"Toggles displaying a background under the speaking character's name.");
/// <summary>
/// A message broadcast to each player that joins the lobby.
/// May be changed by admins ingame through use of the "set-motd" command.
/// In this case the new value, if not empty, is broadcast to all connected players and saved between rounds.
/// May be requested by any player through use of the "get-motd" command.
/// </summary>
public static readonly CVarDef<string> MOTD =
CVarDef.Create("chat.motd",
"",
CVar.SERVER | CVar.SERVERONLY | CVar.ARCHIVE,
"A message broadcast to each player that joins the lobby.");
}

View File

@@ -0,0 +1,35 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
// These are server-only for now since I don't foresee a client use yet,
// and I don't wanna have to start coming up with like .client suffixes and stuff like that.
/// <summary>
/// Configuration presets to load during startup.
/// Multiple presets can be separated by comma and are loaded in order.
/// </summary>
/// <remarks>
/// Loaded presets must be located under the <c>ConfigPresets/</c> resource directory and end with the <c>.toml</c> extension.
/// Only the file name (without extension) must be given for this variable.
/// </remarks>
public static readonly CVarDef<string> ConfigPresets =
CVarDef.Create("config.presets", "", CVar.SERVERONLY);
/// <summary>
/// Whether to load the preset development CVars.
/// This disables some things like lobby to make development easier.
/// Even when true, these are only loaded if the game is compiled with <c>DEVELOPMENT</c> set.
/// </summary>
public static readonly CVarDef<bool> ConfigPresetDevelopment =
CVarDef.Create("config.preset_development", true, CVar.SERVERONLY);
/// <summary>
/// Whether to load the preset debug CVars.
/// Even when true, these are only loaded if the game is compiled with <c>DEBUG</c> set.
/// </summary>
public static readonly CVarDef<bool> ConfigPresetDebug =
CVarDef.Create("config.preset_debug", true, CVar.SERVERONLY);
}

View File

@@ -0,0 +1,15 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
public static readonly CVarDef<bool> ConsoleLoginLocal =
CVarDef.Create("console.loginlocal", true, CVar.ARCHIVE | CVar.SERVERONLY);
/// <summary>
/// Automatically log in the given user as host, equivalent to the <c>promotehost</c> command.
/// </summary>
public static readonly CVarDef<string> ConsoleLoginHostUser =
CVarDef.Create("console.login_host_user", "", CVar.ARCHIVE | CVar.SERVERONLY);
}

View File

@@ -0,0 +1,24 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// Setting this allows a crew manifest to be opened from any window
/// that has a crew manifest button, and sends the correct message.
/// If this is false, only in-game entities will allow you to see
/// the crew manifest, if the functionality is coded in.
/// Having administrator priveledge ignores this, but will still
/// hide the button in UI windows.
/// </summary>
public static readonly CVarDef<bool> CrewManifestWithoutEntity =
CVarDef.Create("crewmanifest.no_entity", true, CVar.REPLICATED);
/// <summary>
/// Setting this allows the crew manifest to be viewed from 'unsecure'
/// entities, such as the PDA.
/// </summary>
public static readonly CVarDef<bool> CrewManifestUnsecure =
CVarDef.Create("crewmanifest.unsecure", true, CVar.REPLICATED);
}

View File

@@ -0,0 +1,77 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
#if DEBUG
private const int DefaultSqliteDelay = 1;
#else
private const int DefaultSqliteDelay = 0;
#endif
public static readonly CVarDef<string> DatabaseEngine =
CVarDef.Create("database.engine", "sqlite", CVar.SERVERONLY);
public static readonly CVarDef<string> DatabaseSqliteDbPath =
CVarDef.Create("database.sqlite_dbpath", "preferences.db", CVar.SERVERONLY);
/// <summary>
/// Milliseconds to asynchronously delay all SQLite database acquisitions with.
/// </summary>
/// <remarks>
/// Defaults to 1 on DEBUG, 0 on RELEASE.
/// This is intended to help catch .Result deadlock bugs that only happen on postgres
/// (because SQLite is not actually asynchronous normally)
/// </remarks>
public static readonly CVarDef<int> DatabaseSqliteDelay =
CVarDef.Create("database.sqlite_delay", DefaultSqliteDelay, CVar.SERVERONLY);
/// <summary>
/// Amount of concurrent SQLite database operations.
/// </summary>
/// <remarks>
/// Note that SQLite is not a properly asynchronous database and also has limited read/write concurrency.
/// Increasing this number may allow more concurrent reads, but it probably won't matter much.
/// SQLite operations are normally ran on the thread pool, which may cause thread pool starvation if the concurrency is too high.
/// </remarks>
public static readonly CVarDef<int> DatabaseSqliteConcurrency =
CVarDef.Create("database.sqlite_concurrency", 3, CVar.SERVERONLY);
public static readonly CVarDef<string> DatabasePgHost =
CVarDef.Create("database.pg_host", "localhost", CVar.SERVERONLY);
public static readonly CVarDef<int> DatabasePgPort =
CVarDef.Create("database.pg_port", 5432, CVar.SERVERONLY);
public static readonly CVarDef<string> DatabasePgDatabase =
CVarDef.Create("database.pg_database", "ss14", CVar.SERVERONLY);
public static readonly CVarDef<string> DatabasePgUsername =
CVarDef.Create("database.pg_username", "postgres", CVar.SERVERONLY);
public static readonly CVarDef<string> DatabasePgPassword =
CVarDef.Create("database.pg_password", "", CVar.SERVERONLY | CVar.CONFIDENTIAL);
/// <summary>
/// Max amount of concurrent Postgres database operations.
/// </summary>
public static readonly CVarDef<int> DatabasePgConcurrency =
CVarDef.Create("database.pg_concurrency", 8, CVar.SERVERONLY);
/// <summary>
/// Milliseconds to asynchronously delay all PostgreSQL database operations with.
/// </summary>
/// <remarks>
/// This is intended for performance testing. It works different from <see cref="DatabaseSqliteDelay"/>,
/// as the lag is applied after acquiring the database lock.
/// </remarks>
public static readonly CVarDef<int> DatabasePgFakeLag =
CVarDef.Create("database.pg_fake_lag", 0, CVar.SERVERONLY);
/// <summary>
/// Basically only exists for integration tests to avoid race conditions.
/// </summary>
public static readonly CVarDef<bool> DatabaseSynchronous =
CVarDef.Create("database.sync", false, CVar.SERVERONLY);
}

View File

@@ -0,0 +1,61 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
/// <summary>
/// The role that will get mentioned if a new SOS ahelp comes in.
/// </summary>
public static readonly CVarDef<string> DiscordAhelpMention =
CVarDef.Create("discord.on_call_ping", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
/// <summary>
/// URL of the discord webhook to relay unanswered ahelp messages.
/// </summary>
public static readonly CVarDef<string> DiscordOnCallWebhook =
CVarDef.Create("discord.on_call_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
/// <summary>
/// URL of the Discord webhook which will relay all ahelp messages.
/// </summary>
public static readonly CVarDef<string> DiscordAHelpWebhook =
CVarDef.Create("discord.ahelp_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
/// <summary>
/// The server icon to use in the Discord ahelp embed footer.
/// Valid values are specified at https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure.
/// </summary>
public static readonly CVarDef<string> DiscordAHelpFooterIcon =
CVarDef.Create("discord.ahelp_footer_icon", string.Empty, CVar.SERVERONLY);
/// <summary>
/// The avatar to use for the webhook. Should be an URL.
/// </summary>
public static readonly CVarDef<string> DiscordAHelpAvatar =
CVarDef.Create("discord.ahelp_avatar", string.Empty, CVar.SERVERONLY);
/// <summary>
/// URL of the Discord webhook which will relay all custom votes. If left empty, disables the webhook.
/// </summary>
public static readonly CVarDef<string> DiscordVoteWebhook =
CVarDef.Create("discord.vote_webhook", string.Empty, CVar.SERVERONLY);
/// <summary>
/// URL of the Discord webhook which will relay all votekick votes. If left empty, disables the webhook.
/// </summary>
public static readonly CVarDef<string> DiscordVotekickWebhook =
CVarDef.Create("discord.votekick_webhook", string.Empty, CVar.SERVERONLY);
/// <summary>
/// URL of the Discord webhook which will relay round restart messages.
/// </summary>
public static readonly CVarDef<string> DiscordRoundUpdateWebhook =
CVarDef.Create("discord.round_update_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
/// <summary>
/// Role id for the Discord webhook to ping when the round ends.
/// </summary>
public static readonly CVarDef<string> DiscordRoundEndRoleWebhook =
CVarDef.Create("discord.round_end_role", string.Empty, CVar.SERVERONLY);
}

Some files were not shown because too many files have changed in this diff Show More