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:
- any-glob-to-any-file: '**/*.swsl'
"No C#":
"Changes: No C#":
- 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.
- all-globs-to-all-files: "!**/*.cs"

View File

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

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions-ecosystem/action-add-labels@v1
with:
labels: "Status: Needs Review"
labels: "S: Needs Review"
- uses: actions-ecosystem/action-remove-labels@v1
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:
- uses: actions-ecosystem/action-add-labels@v1
with:
labels: "Branch: stable"
labels: "Branch: Stable"

View File

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

View File

@@ -3,6 +3,8 @@
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
jobs:
add_label:
@@ -11,4 +13,4 @@ jobs:
- uses: actions-ecosystem/action-add-labels@v1
if: join(github.event.issue.labels) == ''
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" />
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
Visible="False" HorizontalExpand="True" />
<OptionButton Name="ExpiryLengthDropdown" Visible="False" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<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 IClientConsoleHost _console = default!;
private enum Multipliers
{
Minutes,
Hours,
Days,
Weeks,
Months,
Years,
Centuries
}
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed;
public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit)
@@ -31,6 +42,20 @@ public sealed partial class NoteEdit : FancyWindow
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-message"), (int) NoteType.Message);
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;
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 _)
@@ -187,6 +213,16 @@ public sealed partial class NoteEdit : FancyWindow
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)
{
if (!ParseExpiryTime())
@@ -263,13 +299,24 @@ public sealed partial class NoteEdit : FancyWindow
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;
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;
return true;
}

View File

@@ -58,6 +58,7 @@ public sealed class ClientClothingSystem : ClothingSystem
base.Initialize();
SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals);
SubscribeLocalEvent<ClothingComponent, InventoryTemplateUpdated>(OnInventoryTemplateUpdated);
SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip);
@@ -70,11 +71,7 @@ public sealed class ClientClothingSystem : ClothingSystem
if (args.Sprite == null)
return;
var enumerator = _inventorySystem.GetSlotEnumerator((uid, component));
while (enumerator.NextItem(out var item, out var slot))
{
RenderEquipment(uid, item, slot.Name, component);
}
UpdateAllSlots(uid, component);
// 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))
@@ -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)
{
if (!TryComp(args.Equipee, out InventoryComponent? inventory))

View File

@@ -1,15 +1,20 @@
<DefaultWindow xmlns="https://spacestation14.io">
<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">
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/>
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
</BoxContainer>
<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 Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.6">
<Button Name="FavoriteButton" Visible="false" HorizontalExpand="False"
HorizontalAlignment="Right" Margin="0 0 0 15"/>
<Control>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5">
<BoxContainer Orientation="Horizontal" Align="Center">

View File

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

View File

@@ -1,7 +1,8 @@
using System.Linq;
using System.Numerics;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -11,7 +12,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
@@ -33,10 +33,12 @@ namespace Content.Client.Construction.UI
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
private readonly SpriteSystem _spriteSystem;
private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected;
private List<ConstructionPrototype> _favoritedRecipes = [];
private Dictionary<string, TextureButton> _recipeButtons = new();
private string _selectedCategory = string.Empty;
private string _favoriteCatName = "construction-category-favorites";
private string _forAllCategoryName = "construction-category-all";
@@ -85,6 +87,7 @@ namespace Content.Client.Construction.UI
IoCManager.InjectDependencies(this);
_constructionView = new ConstructionMenu();
_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
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
@@ -150,12 +153,24 @@ namespace Content.Client.Construction.UI
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)
{
var (search, category) = args;
var recipesList = _constructionView.Recipes;
recipesList.Clear();
var recipes = new List<ConstructionPrototype>();
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));
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)
{
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)
@@ -257,11 +333,10 @@ namespace Content.Client.Construction.UI
private void PopulateInfo(ConstructionPrototype prototype)
{
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
_constructionView.ClearRecipeInfo();
_constructionView.SetRecipeInfo(
prototype.Name, prototype.Description, spriteSys.Frame0(prototype.Icon),
prototype.Name, prototype.Description, _spriteSystem.Frame0(prototype.Icon),
prototype.Type != ConstructionType.Item,
!_favoritedRecipes.Contains(prototype));
@@ -274,7 +349,6 @@ namespace Content.Client.Construction.UI
if (_constructionSystem?.GetGuide(prototype) is not { } guide)
return;
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
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)
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);
}
}
private static ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
{
return new(itemList)
{
Metadata = recipe,
Text = recipe.Name,
Icon = recipe.Icon.Frame0(),
Icon = _spriteSystem.Frame0(recipe.Icon),
TooltipEnabled = true,
TooltipText = recipe.Description
TooltipText = recipe.Description,
};
}

View File

@@ -31,7 +31,7 @@ namespace Content.Client.Crayon.UI
private void PopulateCrayons()
{
var crayonDecals = _protoManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon"));
_menu?.Populate(crayonDecals);
_menu?.Populate(crayonDecals.ToList());
}
public override void OnProtoReload(PrototypesReloadedEventArgs args)
@@ -44,6 +44,16 @@ namespace Content.Client.Crayon.UI
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)
{
base.UpdateState(state);

View File

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

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Content.Client.Stylesheets;
using Content.Shared.Crayon;
using Content.Shared.Decals;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
@@ -18,7 +20,12 @@ namespace Content.Client.Crayon.UI
[GenerateTypedNameReferences]
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 Color _color;
@@ -28,8 +35,10 @@ namespace Content.Client.Crayon.UI
public CrayonWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
Search.OnTextChanged += _ => RefreshList();
Search.OnTextChanged += SearchChanged;
ColorSelector.OnColorChanged += SelectColor;
}
@@ -44,25 +53,60 @@ namespace Content.Client.Crayon.UI
private void RefreshList()
{
// Clear
Grid.DisposeAllChildren();
if (_decals == null)
Grids.DisposeAllChildren();
if (_decals == null || _allDecals == null)
return;
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;
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()
{
TextureNormal = tex,
Name = decal,
ToolTip = decal,
TextureNormal = texture,
Name = name,
ToolTip = name,
Modulate = _color,
Scale = new System.Numerics.Vector2(2, 2)
};
button.OnPressed += ButtonOnPressed;
if (_selected == decal)
if (_selected == name)
{
var panelContainer = new PanelContainer()
{
@@ -75,20 +119,28 @@ namespace Content.Client.Crayon.UI
button,
},
};
Grid.AddChild(panelContainer);
grid.AddChild(panelContainer);
}
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)
{
if (obj.Button.Name == null) return;
_selected = obj.Button.Name;
_autoSelected = null;
OnSelected?.Invoke(_selected);
RefreshList();
}
@@ -107,12 +159,38 @@ namespace Content.Client.Crayon.UI
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)
{
_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();

View File

@@ -235,9 +235,23 @@ namespace Content.Client.Inventory
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 readonly SlotDefinition SlotDef;
public SlotDefinition SlotDef;
public EntityUid? HeldEntity => Container?.ContainedEntity;
public bool Blocked;
public bool Highlighted;

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ using Content.Client.Message;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface;
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!;
[ViewVariables]
protected bool IsActive;
protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET;
@@ -102,7 +103,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
args.Components.Add(component);
}
private void RefreshOverlay(EntityUid uid)
protected void RefreshOverlay(EntityUid uid)
{
if (uid != _player.LocalSession?.AttachedEntity)
return;

View File

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

View File

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

View File

@@ -131,7 +131,8 @@ public sealed partial class BorgMenu : FancyWindow
_modules.Clear();
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 += () =>
{
RemoveModuleButtonPressed?.Invoke(module);

View File

@@ -9,7 +9,7 @@ public sealed partial class BorgModuleControl : PanelContainer
{
public Action? RemoveButtonPressed;
public BorgModuleControl(EntityUid entity, IEntityManager entityManager)
public BorgModuleControl(EntityUid entity, IEntityManager entityManager, bool canRemove)
{
RobustXamlLoader.Load(this);
@@ -20,6 +20,7 @@ public sealed partial class BorgModuleControl : PanelContainer
{
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);
}
}
/// <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.Components;
namespace Content.Client.Singularity.EntitySystems;
namespace Content.Client.Singularity.Systems;
/// <summary>
/// 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.Utility;
namespace Content.Client.Singularity.EntitySystems;
namespace Content.Client.Singularity.Systems;
/// <summary>
/// The client-side version of <see cref="SharedSingularitySystem"/>.

View File

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

View File

@@ -146,8 +146,8 @@ namespace Content.Server.Abilities.Mime
mimePowers.ReadyToRepent = false;
mimePowers.VowBroken = false;
AddComp<MutedComponent>(uid);
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert);
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert);
_alertsSystem.ClearAlert(uid, mimePowers.VowBrokenAlert);
_alertsSystem.ShowAlert(uid, mimePowers.VowAlert);
_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.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Clumsy;
using Content.Shared.Clothing.Components;
using Content.Shared.Cluwne;
using Content.Shared.Damage;

View File

@@ -1,27 +1,27 @@
using Content.Server.Administration.Components;
using Content.Shared.Climbing.Components;
using Content.Shared.Climbing.Events;
using Content.Shared.Climbing.Systems;
using Content.Shared.Interaction.Components;
using Content.Shared.Clumsy;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Shared.Audio.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 BonkSystem _bonkSystem = default!;
[Dependency] private readonly ClumsySystem _clumsySystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
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.
@@ -31,7 +31,6 @@ public sealed class SuperBonkSystem: EntitySystem
return;
}
var hadClumsy = EnsureComp<ClumsyComponent>(target, out _);
var tables = EntityQueryEnumerator<BonkableComponent>();
@@ -79,16 +78,17 @@ public sealed class SuperBonkSystem: EntitySystem
private void Bonk(SuperBonkComponent comp)
{
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
// 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;
_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)

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")]
public EntityUid Add(
[CommandInvocationContext] IInvocationContext ctx,

View File

@@ -28,7 +28,8 @@ namespace Content.Server.Announcements
}
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);
}
shell.WriteLine("Sent!");

View File

@@ -184,7 +184,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
return;
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();
ChooseAntags((uid, component), players, midround: true);

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Hypospray.Events;
using Content.Shared.Chemistry;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
@@ -85,14 +86,44 @@ public sealed class HypospraySystem : SharedHypospraySystem
string? msgFormat = null;
if (target == user)
msgFormat = "hypospray-component-inject-self-message";
else if (EligibleEntity(user, EntityManager, component) && _interaction.TryRollClumsy(user, component.ClumsyFailChance))
// Self event
var selfEvent = new SelfBeforeHyposprayInjectsEvent(user, entity.Owner, target);
RaiseLocalEvent(user, selfEvent);
if (selfEvent.Cancelled)
{
msgFormat = "hypospray-component-inject-self-clumsy-message";
target = user;
_popup.PopupEntity(Loc.GetString(selfEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), 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)
{
_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.Mobs.Components;
using Content.Shared.Stacks;
using Content.Shared.Nutrition.EntitySystems;
namespace Content.Server.Chemistry.EntitySystems;
@@ -20,6 +21,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
{
[Dependency] private readonly BloodstreamSystem _blood = default!;
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
public override void Initialize()
{
@@ -31,13 +33,14 @@ public sealed class InjectorSystem : SharedInjectorSystem
private bool TryUseInjector(Entity<InjectorComponent> injector, EntityUid target, EntityUid user)
{
var isOpenOrIgnored = injector.Comp.IgnoreClosed || !_openable.IsClosed(target);
// Handle injecting/drawing for solutions
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);
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);
if (TryComp<BloodstreamComponent>(target, out var bloodstream))
@@ -58,7 +61,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
}
// 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);
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 Robust.Shared.Audio.Systems;
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Clumsy;
namespace Content.Server.Cluwne;

View File

@@ -82,6 +82,8 @@ public sealed class CrayonSystem : SharedCrayonSystem
if (component.DeleteEmpty && component.Charges <= 0)
UseUpCrayon(uid, args.User);
else
_uiSystem.ServerSendUiMessage(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState));
}
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)
{
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 (ban is null)
if (roleBanDetails == default)
return;
ban.Severity = severity;
ban.Reason = reason;
ban.ExpirationTime = expiration?.UtcDateTime;
ban.LastEditedById = editedBy;
ban.LastEditedAt = editedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync();
await db.DbContext.RoleBan
.Where(b => b.BanTime == roleBanDetails.BanTime && b.PlayerUserId == roleBanDetails.PlayerUserId)
.ExecuteUpdateAsync(setters => setters
.SetProperty(b => b.Severity, severity)
.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

View File

@@ -184,6 +184,6 @@ namespace Content.Server.GameTicking
=> UserHasJoinedGame(session.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.Station.Components;
using Content.Shared.Database;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Players;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
@@ -455,71 +455,4 @@ namespace Content.Server.GameTicking
#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.RoundEnd;
using Content.Server.Station.Systems;
using Content.Shared.GameTicking;
using Content.Shared.GameTicking.Components;
using Content.Shared.Points;
using Content.Shared.Storage;

View File

@@ -34,10 +34,11 @@ public sealed class GatewayGeneratorSystem : EntitySystem
[Dependency] private readonly GatewaySystem _gateway = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly SharedSalvageSystem _salvage = default!;
[Dependency] private readonly TileSystem _tile = default!;
[ValidatePrototypeId<DatasetPrototype>]
private const string PlanetNames = "names_borer";
[ValidatePrototypeId<LocalizedDatasetPrototype>]
private const string PlanetNames = "NamesBorer";
// TODO:
// Fix shader some more
@@ -102,7 +103,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
var mapId = _mapManager.CreateMap();
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);
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);
}
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))
return false;
@@ -98,15 +111,25 @@ public sealed class DefibrillatorSystem : EntitySystem
if (!_powerCell.HasActivatableCharge(uid, user: user))
return false;
if (_mobState.IsAlive(target, mobState))
if (!targetCanBeAlive && _mobState.IsAlive(target, mobState))
return false;
if (!component.CanDefibCrit && _mobState.IsCritical(target, mobState))
if (!targetCanBeAlive && !component.CanDefibCrit && _mobState.IsCritical(target, mobState))
return false;
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)
{
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;
// clowns zap themselves
if (HasComp<ClumsyComponent>(user) && user != target)
{
Zap(uid, user, user, component);
return;
}
if (!_powerCell.TryUseActivatableCharge(uid, user: user))
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);
_electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true);
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.Systems;
using Content.Server.Emp;
using Content.Server.GameTicking;
using Content.Server.Medical.CrewMonitoring;
using Content.Server.Popups;
using Content.Server.Station.Systems;
@@ -14,8 +13,10 @@ using Content.Shared.Damage;
using Content.Shared.DeviceNetwork;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.GameTicking;
using Content.Shared.Interaction;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Verbs;
@@ -383,7 +384,7 @@ public sealed class SuitSensorSystem : EntitySystem
// Get mob total damage crit threshold
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();
// finally, form suit sensor status

View File

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

View File

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

View File

@@ -84,6 +84,6 @@ public sealed partial class PathfindingSystem
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)
{
if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
return (false, true);
return (false, false);
//Prevents food usage with a wrong utensil
if ((food.Utensil & utensil.Comp.Types) == 0)

View File

@@ -53,7 +53,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
return;
// no other humans to kill
var allHumans = _mind.GetAliveHumansExcept(args.MindId);
var allHumans = _mind.GetAliveHumans(args.MindId);
if (allHumans.Count == 0)
{
args.Cancelled = true;
@@ -77,14 +77,14 @@ public sealed class KillPersonConditionSystem : EntitySystem
return;
// no other humans to kill
var allHumans = _mind.GetAliveHumansExcept(args.MindId);
var allHumans = _mind.GetAliveHumans(args.MindId);
if (allHumans.Count == 0)
{
args.Cancelled = true;
return;
}
var allHeads = new List<EntityUid>();
var allHeads = new HashSet<Entity<MindComponent>>();
foreach (var person in allHumans)
{
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 brakeInput = 0f;
var angularInput = 0f;
var linearCount = 0;
var brakeCount = 0;
var angularCount = 0;
foreach (var (pilotUid, pilot, _, consoleXform) in pilots)
{
@@ -322,24 +325,27 @@ public sealed class MoverController : SharedMoverController
if (brakes > 0f)
{
brakeInput += brakes;
brakeCount++;
}
if (strafe.Length() > 0f)
{
var offsetRotation = consoleXform.LocalRotation;
linearInput += offsetRotation.RotateVec(strafe);
linearCount++;
}
if (rotation != 0f)
{
angularInput += rotation;
angularCount++;
}
}
var count = pilots.Count;
linearInput /= count;
angularInput /= count;
brakeInput /= count;
// Don't slow down the shuttle if there's someone just looking at the console
linearInput /= Math.Max(1, linearCount);
angularInput /= Math.Max(1, angularCount);
brakeInput /= Math.Max(1, brakeCount);
// Handle shuttle movement
if (brakeInput > 0f)

View File

@@ -199,6 +199,9 @@ public sealed partial class PolymorphSystem : EntitySystem
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));
MakeSentientCommand.MakeSentient(child, EntityManager);
@@ -288,6 +291,9 @@ public sealed partial class PolymorphSystem : EntitySystem
var uidXform = Transform(uid);
var parentXform = Transform(parent);
if (component.Configuration.ExitPolymorphSound != null)
_audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates);
_transform.SetParent(parent, parentXform, uidXform.ParentUid);
_transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation);

View File

@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Content.Server.Parallax;
using Content.Shared.Maps;
using Content.Shared.Parallax.Biomes;
using Content.Shared.Procedural;
using Content.Shared.Procedural.PostGeneration;
@@ -15,27 +16,35 @@ public sealed partial class DungeonJob
/// </summary>
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;
biomeComp = _entManager.AddComponent<BiomeComponent>(_gridUid);
var biomeSystem = _entManager.System<BiomeSystem>();
biomeSystem.SetTemplate(_gridUid, biomeComp, _prototype.Index(dunGen.BiomeTemplate));
var seed = random.Next();
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))
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.
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);
}
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)
{
@@ -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 xform = xformQuery.Get(ent);
@@ -61,7 +70,5 @@ public sealed partial class DungeonJob
if (!ValidateResume())
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);
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);
UpdateConsoles((station.Value, data));

View File

@@ -104,7 +104,9 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
destComp.BeaconsOnly = true;
destComp.RequireCoordinateDisk = 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);
// 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; }
/// <inheritdoc />
public ProtoId<DatasetPrototype>? NameDataset { get; }
public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
/// <inheritdoc />
int MinCount { get; set; }
@@ -75,7 +75,7 @@ public sealed class DungeonSpawnGroup : IGridSpawnGroup
public float MaximumDistance { get; }
/// <inheritdoc />
public ProtoId<DatasetPrototype>? NameDataset { get; }
public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
/// <inheritdoc />
public int MinCount { get; set; } = 1;
@@ -106,7 +106,7 @@ public sealed class GridSpawnGroup : IGridSpawnGroup
/// <inheritdoc />
public float MaximumDistance { get; }
public ProtoId<DatasetPrototype>? NameDataset { get; }
public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
public int MinCount { get; set; } = 1;
public int MaxCount { get; set; } = 1;
public ComponentRegistry AddComponents { get; set; } = new();

View File

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

View File

@@ -208,7 +208,7 @@ public sealed partial class ShuttleSystem
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)

View File

@@ -8,6 +8,7 @@ using Content.Server.Station.Systems;
using Content.Server.Stunnable;
using Content.Shared.GameTicking;
using Content.Shared.Mobs.Systems;
using Content.Shared.Salvage;
using Content.Shared.Shuttles.Systems;
using Content.Shared.Throwing;
using JetBrains.Annotations;
@@ -51,6 +52,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedSalvageSystem _salvage = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly StationSystem _station = 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.Interaction.Components;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
namespace Content.Server.Silicons.Borgs;
@@ -300,6 +301,24 @@ public sealed partial class BorgSystem
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>
/// Installs and activates all modules currently inside the borg's module container
/// </summary>
@@ -369,4 +388,24 @@ public sealed partial class BorgSystem
var ev = new BorgModuleUninstalledEvent(uid);
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.Systems;
using Content.Server.Explosion.Components;
using Robust.Shared.Utility;
namespace Content.Server.Silicons.Borgs;
@@ -134,4 +135,20 @@ public sealed partial class BorgSystem
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))
return;
if (!CanRemoveModule((uid, component), (module, Comp<BorgModuleComponent>(module)), args.Actor))
return;
_adminLog.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(args.Actor):player} removed module {ToPrettyString(module)} from borg {ToPrettyString(uid)}");
_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))
{
_container.Insert(used, component.ModuleContainer);
InsertModule((uid, component), used);
_adminLog.Add(LogType.Action, LogImpact.Low,
$"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
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.
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 Content.Server.Administration;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.Radio.Components;
using Content.Server.Roles;
using Content.Server.Station.Systems;
@@ -9,6 +8,7 @@ using Content.Shared.Administration;
using Content.Shared.Chat;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
@@ -17,12 +17,12 @@ using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Stunnable;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
namespace Content.Server.Silicons.Laws;
@@ -50,9 +50,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
SubscribeLocalEvent<SiliconLawProviderComponent, GetSiliconLawsEvent>(OnDirectedGetLaws);
SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws);
SubscribeLocalEvent<SiliconLawProviderComponent, MindAddedMessage>(OnLawProviderMindAdded);
SubscribeLocalEvent<SiliconLawProviderComponent, MindRemovedMessage>(OnLawProviderMindRemoved);
SubscribeLocalEvent<SiliconLawProviderComponent, GotEmaggedEvent>(OnEmagLawsAdded);
SubscribeLocalEvent<EmagSiliconLawComponent, MindAddedMessage>(OnEmagMindAdded);
SubscribeLocalEvent<EmagSiliconLawComponent, MindRemovedMessage>(OnEmagMindRemoved);
}
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 wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false,
actor.PlayerSession.Channel, colorOverride: Color.FromHex("#2ed2fd"));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, 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)
{
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
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
if (TryComp<EmagSiliconLawComponent>(uid, out var emag))
EnsureEmaggedRole(uid, emag);
if(_mind.TryGetMind(uid, out var mindId, out _))
EnsureSubvertedSiliconRole(mindId);
}
}
@@ -130,6 +158,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
if (component.Lawset == null)
component.Lawset = GetLawset(component.Laws);
// Show the silicon has been subverted.
component.Subverted = true;
// Add the first emag law before the others
component.Lawset?.Laws.Insert(0, new SiliconLaw
{
@@ -152,35 +183,25 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
base.OnGotEmagged(uid, component, ref args);
NotifyLawsChanged(uid, component.EmaggedSound);
EnsureEmaggedRole(uid, component);
if(_mind.TryGetMind(uid, out var mindId, out _))
EnsureSubvertedSiliconRole(mindId);
_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))
_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)
{
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))
{
apcReceiver.Load = component.PowerUseActive;
if (apcReceiver.Powered)
PowerOn(uid, component);
}
// Do not directly PowerOn().

View File

@@ -1,14 +1,23 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Singularity.Components;
using Content.Shared.Popups;
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.Timing;
namespace Content.Server.Singularity.EntitySystems;
public sealed class SingularityGeneratorSystem : EntitySystem
public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSystem
{
#region Dependencies
[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
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>
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(
args.OtherEntity,
singularityGeneratorComponent.Power + component.State switch
generatorComp.Power + component.State switch
{
ParticleAcceleratorPowerState.Standby => 0,
ParticleAcceleratorPowerState.Level0 => 1,
@@ -113,10 +148,46 @@ public sealed class SingularityGeneratorSystem : EntitySystem
ParticleAcceleratorPowerState.Level3 => 8,
_ => 0
},
singularityGeneratorComponent
generatorComp
);
}
EntityManager.QueueDeleteEntity(uid);
}
}
#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.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 Content.Shared.Station.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events;
public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
{
[Dependency] private readonly IPrototypeManager _proto = 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";
[Dependency] private readonly IonStormSystem _ionStorm = default!;
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)
continue;
if (!RobustRandom.Prob(target.Chance))
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);
_ionStorm.IonStormTarget((ent, lawBound, target));
}
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.IO;
using Content.Server.Access.Systems;
using Content.Server.Forensics;
using Content.Server.GameTicking;
using Content.Shared.Access.Components;
using Content.Shared.GameTicking;
using Content.Shared.Inventory;
using Content.Shared.PDA;
using Content.Shared.Preferences;

View File

@@ -256,6 +256,11 @@ public sealed partial class StoreSystem
RaiseLocalEvent(buyer, listing.ProductEvent);
}
if (listing.DisableRefund)
{
component.RefundAllowed = false;
}
//log dat shit.
_admin.Add(LogType.StorePurchase,
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.EntitySystems;
using Content.Shared.Roles;
using Content.Shared.Traits;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
namespace Content.Server.Traits;

View File

@@ -1,15 +1,12 @@
using System.Linq;
using System.Numerics;
using Content.Server.Cargo.Systems;
using Content.Server.Interaction;
using Content.Server.Power.EntitySystems;
using Content.Server.Stunnable;
using Content.Server.Weapons.Ranged.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Effects;
using Content.Shared.Interaction.Components;
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Ranged;
@@ -33,16 +30,13 @@ public sealed partial class GunSystem : SharedGunSystem
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly StaminaSystem _stamina = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
private const float DamagePitchVariation = 0.05f;
public const float GunClumsyChance = 0.5f;
public override void Initialize()
{
@@ -71,28 +65,16 @@ public sealed partial class GunSystem : SharedGunSystem
{
userImpulse = true;
// Try a clumsy roll
// TODO: Who put this here
if (TryComp<ClumsyComponent>(user, out var clumsy) && gun.ClumsyProof == false)
if (user != null)
{
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;
return;
}
}
}
var fromMap = fromCoordinates.ToMap(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