Merge branch 'master' into Alchemy-Redux

This commit is contained in:
KittenColony
2025-02-06 23:01:23 +13:00
committed by GitHub
357 changed files with 6727 additions and 2386 deletions

View File

@@ -1,3 +1,4 @@
using Content.Client._CP14.Discord;
using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
@@ -43,6 +44,9 @@ namespace Content.Client.Entry
{
public sealed class EntryPoint : GameClient
{
//CP14
[Dependency] private readonly DiscordAuthManager _discordAuth = default!;
//CP14 end
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IGameController _gameController = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
@@ -160,7 +164,10 @@ namespace Content.Client.Entry
_parallaxManager.LoadDefaultParallax();
_overlayManager.AddOverlay(new CP14BasePostProcessOverlay()); // CP14-PostProcess
//CP14
_overlayManager.AddOverlay(new CP14BasePostProcessOverlay());
_discordAuth.Initialize();
//CP14 end
_overlayManager.AddOverlay(new SingularityOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
_chatManager.Initialize();

View File

@@ -1,3 +1,4 @@
using Content.Client._CP14.Discord;
using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
@@ -33,6 +34,9 @@ namespace Content.Client.IoC
{
var collection = IoCManager.Instance!;
//CP14
collection.Register<DiscordAuthManager>();
//CP14 end
collection.Register<IParallaxManager, ParallaxManager>();
collection.Register<IChatManager, ChatManager>();
collection.Register<ISharedChatManager, ChatManager>();

View File

@@ -0,0 +1,27 @@
using Content.Shared._CP14.Discord;
using Robust.Client.State;
using Robust.Shared.Network;
namespace Content.Client._CP14.Discord;
public sealed class DiscordAuthManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
public string AuthUrl { get; private set; } = "";
public void Initialize()
{
_netManager.RegisterNetMessage<MsgDiscordAuthCheck>();
_netManager.RegisterNetMessage<MsgDiscordAuthRequired>(OnDiscordAuthRequired);
}
private void OnDiscordAuthRequired(MsgDiscordAuthRequired msg)
{
if (_stateManager.CurrentState is DiscordAuthState)
return;
AuthUrl = msg.AuthUrl;
_stateManager.RequestStateChange<DiscordAuthState>();
}
}

View File

@@ -0,0 +1,36 @@
using System.Threading;
using Content.Shared._CP14.Discord;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Network;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client._CP14.Discord;
public sealed class DiscordAuthState : State
{
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
private DiscordAuthGui? _gui;
private readonly CancellationTokenSource _checkTimerCancel = new();
protected override void Startup()
{
_gui = new DiscordAuthGui();
_userInterfaceManager.StateRoot.AddChild(_gui);
Timer.SpawnRepeating(TimeSpan.FromSeconds(5),
() =>
{
_netManager.ClientSendMessage(new MsgDiscordAuthCheck());
},
_checkTimerCancel.Token);
}
protected override void Shutdown()
{
_checkTimerCancel.Cancel();
_gui!.Dispose();
}
}

View File

@@ -0,0 +1,29 @@
<Control xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:parallax="clr-namespace:Content.Client.Parallax"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<parallax:ParallaxControl />
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Margin="8 0 0 0" Text="{Loc 'cp14-discord-auth-title'}"
StyleClasses="LabelHeading" VAlign="Center" />
<Button Name="QuitButton" Text="{Loc 'cp14-discord-auth-quit-btn'}"
HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
<controls:HighDivider />
<BoxContainer Orientation="Vertical" Margin="50 20 50 20">
<RichTextLabel Name="InfoLabel" />
</BoxContainer>
<BoxContainer HorizontalExpand="True">
<LineEdit Editable="False" Name="AuthLinkEdit" HorizontalExpand="True" />
<LineEdit Editable="False" Name="DLinkEdit" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Button Name="AuthorizeButton" Text="{Loc 'cp14-discord-auth-text'}" HorizontalExpand="True" StyleClasses="OpenRight" />
<Button Name="DiscordButton" Text="{Loc 'cp14-discord-auth-browser-btn'}" HorizontalExpand="True" StyleClasses="OpenRight" />
</BoxContainer>
</BoxContainer>
</Control>
</Control>

View File

@@ -0,0 +1,46 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._CP14.Discord;
[GenerateTypedNameReferences]
public sealed partial class DiscordAuthGui : Control
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly DiscordAuthManager _discordAuthManager = default!;
private const string DiscordLink = "https://discord.com/invite/Sud2DMfhCC"; //TODO: Unhardcode
public DiscordAuthGui()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
var link = _discordAuthManager.AuthUrl;
AuthLinkEdit.SetText(link);
DLinkEdit.SetText(DiscordLink);
InfoLabel.SetMessage(Loc.GetString("cp14-discord-info"));
var uriOpener = IoCManager.Resolve<IUriOpener>();
QuitButton.OnPressed += _ =>
{
_consoleHost.ExecuteCommand("quit");
};
AuthorizeButton.OnPressed += _ =>
{
uriOpener.OpenUri(link);
};
DiscordButton.OnPressed += _ =>
{
uriOpener.OpenUri(DiscordLink);
};
}
}

View File

@@ -1,20 +1,14 @@
<Control xmlns="https://spacestation14.io">
<GridContainer Columns="2">
<EntityPrototypeView
Name="EntityView"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
Scale="2,2"
HorizontalAlignment="Left"
VerticalExpand="True" />
<TextureRect
Name="View"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
HorizontalAlignment="Left"
Stretch="KeepAspectCentered" />
<Label Name="Name" />
</GridContainer>
<Button Name="Button">
<GridContainer Columns="2">
<EntityPrototypeView Name="View"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
Scale="2,2"
HorizontalAlignment="Center"
VerticalExpand="True"/>
<Label Name="Name"/>
</GridContainer>
</Button>
</Control>

View File

@@ -3,7 +3,7 @@
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared.Stacks;
using Content.Shared._CP14.Workbench;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
@@ -13,13 +13,14 @@ using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Workbench;
[GenerateTypedNameReferences]
public sealed partial class CP14WorkbenchRecipeControl : Control
public sealed partial class CP14WorkbenchRequirementControl : Control
{
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly SpriteSystem _sprite;
public CP14WorkbenchRecipeControl()
public CP14WorkbenchRequirementControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
@@ -27,25 +28,22 @@ public sealed partial class CP14WorkbenchRecipeControl : Control
_sprite = _entity.System<SpriteSystem>();
}
public CP14WorkbenchRecipeControl(EntityPrototype prototype, int count) : this()
public CP14WorkbenchRequirementControl(CP14WorkbenchCraftRequirement requirement) : this()
{
var entityName = prototype.Name;
Name.Text = count <= 1 ? entityName : $"{entityName} x{count}";
Name.Text = requirement.GetRequirementTitle(_proto);
View.Visible = false;
EntityView.SetPrototype(prototype);
}
var texture = requirement.GetRequirementTexture(_proto);
if (texture is not null)
{
View.Visible = true;
View.Texture = _sprite.Frame0(texture);
}
public CP14WorkbenchRecipeControl(StackPrototype prototype, int count) : this()
{
var entityName = Loc.GetString(prototype.Name);
Name.Text = $"{entityName} x{count}";
var icon = prototype.Icon;
if (icon is null)
return;
EntityView.Visible = false;
View.Texture = _sprite.Frame0(icon);
var entityView = requirement.GetRequirementEntityView(_proto);
if (entityView is not null)
{
EntityView.Visible = true;
EntityView.SetPrototype(entityView);
}
}
}

View File

@@ -1,14 +1,22 @@
<Control xmlns="https://spacestation14.io">
<Button Name="Button">
<GridContainer Columns="2">
<EntityPrototypeView Name="View"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
Scale="2,2"
HorizontalAlignment="Center"
VerticalExpand="True"/>
<Label Name="Name"/>
</GridContainer>
</Button>
<GridContainer Columns="2">
<EntityPrototypeView
Name="EntityView"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
Scale="2,2"
HorizontalAlignment="Left"
Visible="False"
VerticalExpand="True" />
<TextureRect
Name="View"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
HorizontalAlignment="Left"
Visible="False"
Stretch="KeepAspectCentered" />
<Label Name="Name" />
</GridContainer>
</Control>

View File

@@ -14,7 +14,7 @@ using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Workbench;
[GenerateTypedNameReferences]
public sealed partial class CP14WorkbenchRequirementControl : Control
public sealed partial class CP14WorkbenchRecipeControl : Control
{
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
@@ -26,7 +26,7 @@ public sealed partial class CP14WorkbenchRequirementControl : Control
private readonly CP14WorkbenchRecipePrototype _recipePrototype;
private readonly bool _craftable;
public CP14WorkbenchRequirementControl(CP14WorkbenchUiRecipesEntry entry)
public CP14WorkbenchRecipeControl(CP14WorkbenchUiRecipesEntry entry)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);

View File

@@ -74,7 +74,7 @@ public sealed partial class CP14WorkbenchWindow : DefaultWindow
if (entry.Craftable)
{
var control = new CP14WorkbenchRequirementControl(entry);
var control = new CP14WorkbenchRecipeControl(entry);
control.OnSelect += RecipeSelect;
CraftsContainer.AddChild(control);
}
@@ -84,7 +84,7 @@ public sealed partial class CP14WorkbenchWindow : DefaultWindow
foreach (var entry in uncraftableList)
{
var control = new CP14WorkbenchRequirementControl(entry);
var control = new CP14WorkbenchRecipeControl(entry);
control.OnSelect += RecipeSelect;
CraftsContainer.AddChild(control);
}
@@ -119,14 +119,10 @@ public sealed partial class CP14WorkbenchWindow : DefaultWindow
ItemDescription.Text = result.Description;
ItemRequirements.RemoveAllChildren();
foreach (var (entProtoId, count) in recipe.Entities)
{
ItemRequirements.AddChild(new CP14WorkbenchRecipeControl(_prototype.Index(entProtoId), count));
}
foreach (var (stackProtoId, count) in recipe.Stacks)
foreach (var requirement in recipe.Requirements)
{
ItemRequirements.AddChild(new CP14WorkbenchRecipeControl(_prototype.Index(stackProtoId), count));
ItemRequirements.AddChild(new CP14WorkbenchRequirementControl(requirement));
}
CraftButton.Disabled = !entry.Craftable;

View File

@@ -39,6 +39,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill"))
.Select(p => p.ID)
.ToList();
@@ -101,6 +102,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill"))
.Select(p => p.ID)
.ToList();
foreach (var protoId in protoIds)
@@ -341,6 +343,7 @@ namespace Content.IntegrationTests.Tests
"DebugExceptionInitialize",
"DebugExceptionStartup",
"GridFill",
"RoomFill",
"Map", // We aren't testing a map entity in this test
"MapGrid",
"Broadphase",

View File

@@ -1,3 +1,4 @@
using Content.Server._CP14.Discord;
using Content.Server.Acz;
using Content.Server.Administration;
using Content.Server.Administration.Logs;
@@ -101,6 +102,10 @@ namespace Content.Server.Entry
logManager.GetSawmill("Storage").Level = LogLevel.Info;
logManager.GetSawmill("db.ef").Level = LogLevel.Info;
//CP14
IoCManager.Resolve<DiscordAuthManager>().Initialize();
//CP14 end
IoCManager.Resolve<IAdminLogManager>().Initialize();
IoCManager.Resolve<IConnectionManager>().Initialize();
_dbManager.Init();

View File

@@ -57,7 +57,7 @@ namespace Content.Server.GameTicking
// Make the player actually join the game.
// timer time must be > tick length
Timer.Spawn(0, () => _playerManager.JoinGame(args.Session));
//Timer.Spawn(0, () => _playerManager.JoinGame(args.Session)); //CP14 Discord AuthManager
var record = await _db.GetPlayerRecordByUserId(args.Session.UserId);
var firstConnection = record != null &&

View File

@@ -1,3 +1,4 @@
using Content.Server._CP14.Discord;
using Content.Server.Administration;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
@@ -36,6 +37,9 @@ namespace Content.Server.IoC
{
public static void Register()
{
//CP14
IoCManager.Register<DiscordAuthManager>();
//CP14 end
IoCManager.Register<IChatManager, ChatManager>();
IoCManager.Register<ISharedChatManager, ChatManager>();
IoCManager.Register<IChatSanitizationManager, ChatSanitizationManager>();

View File

@@ -74,7 +74,7 @@ namespace Content.Server.Nutrition.EntitySystems
}
// Convert smokable item into reagents to be smoked
private bool TryTransferReagents(Entity<SmokingPipeComponent> entity, Entity<SmokableComponent> smokable)
public bool TryTransferReagents(Entity<SmokingPipeComponent> entity, Entity<SmokableComponent> smokable) //CP14 make it public
{
if (entity.Comp.BowlSlot.Item == null)
return false;

View File

@@ -130,31 +130,31 @@ public sealed partial class DungeonSystem
var finalRoomRotation = roomTransform.Rotation();
if (clearExisting)
{
var point1 = Vector2.Transform(-room.Size / 2, roomTransform);
var point2 = Vector2.Transform(room.Size / 2, roomTransform);
var gridBounds = GetRotatedBox(point1, point2, finalRoomRotation);
entitySet.Clear();
// Polygon skin moment
gridBounds = gridBounds.Enlarged(-0.05f);
_lookup.GetLocalEntitiesIntersecting(gridUid, gridBounds, entitySet, LookupFlags.Uncontained);
foreach (var templateEnt in entitySet)
{
Del(templateEnt);
}
if (TryComp(gridUid, out DecalGridComponent? decalGrid))
{
foreach (var decal in _decals.GetDecalsIntersecting(gridUid, gridBounds, decalGrid))
{
_decals.RemoveDecal(gridUid, decal.Index, decalGrid);
}
}
}
//if (clearExisting) //CP14 disable default clearExisting
//{
// var point1 = Vector2.Transform(-room.Size / 2, roomTransform);
// var point2 = Vector2.Transform(room.Size / 2, roomTransform);
//
// var gridBounds = GetRotatedBox(point1, point2, finalRoomRotation);
//
// entitySet.Clear();
// // Polygon skin moment
// gridBounds = gridBounds.Enlarged(-0.05f);
// _lookup.GetLocalEntitiesIntersecting(gridUid, gridBounds, entitySet, LookupFlags.Uncontained);
//
// foreach (var templateEnt in entitySet)
// {
// Del(templateEnt);
// }
//
// if (TryComp(gridUid, out DecalGridComponent? decalGrid))
// {
// foreach (var decal in _decals.GetDecalsIntersecting(gridUid, gridBounds, decalGrid))
// {
// _decals.RemoveDecal(gridUid, decal.Index, decalGrid);
// }
// }
//}
var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
var tileOffset = -roomCenter + grid.TileSizeHalfVector;
@@ -183,14 +183,14 @@ public sealed partial class DungeonSystem
_tiles.Add((rounded, tileRef.Tile));
//CP14 clearExisting variant
//if (clearExisting)
//{
// var anchored = _maps.GetAnchoredEntities((gridUid, grid), rounded);
// foreach (var ent in anchored)
// {
// QueueDel(ent);
// }
//}
if (clearExisting)
{
var anchored = _maps.GetAnchoredEntities((gridUid, grid), rounded);
foreach (var ent in anchored)
{
QueueDel(ent);
}
}
//CP14 clearExisting variant end
}
}

View File

@@ -5,7 +5,6 @@
using System.Linq;
using Content.Server._CP14.BiomeSpawner.Components;
using Content.Server._CP14.RoundSeed;
using Content.Server.Decals;
using Content.Server.Parallax;
using Content.Shared.Whitelist;
@@ -13,6 +12,7 @@ using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.BiomeSpawner.EntitySystems;
@@ -24,12 +24,21 @@ public sealed class CP14BiomeSpawnerSystem : EntitySystem
[Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly DecalSystem _decals = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly CP14RoundSeedSystem _roundSeed = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private int _globalSeed = 0;
public override void Initialize()
{
SubscribeLocalEvent<CP14BiomeSpawnerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<Shared.GameTicking.RoundEndMessageEvent>(OnRoundEnd);
UpdateSeed();
}
private void OnRoundEnd(Shared.GameTicking.RoundEndMessageEvent ev)
{
UpdateSeed();
}
private void OnMapInit(Entity<CP14BiomeSpawnerComponent> ent, ref MapInitEvent args)
@@ -38,6 +47,11 @@ public sealed class CP14BiomeSpawnerSystem : EntitySystem
QueueDel(ent);
}
private void UpdateSeed()
{
_globalSeed = _random.Next(int.MinValue, int.MaxValue);
}
private void SpawnBiome(Entity<CP14BiomeSpawnerComponent> ent)
{
var biome = _proto.Index(ent.Comp.Biome);
@@ -50,11 +64,9 @@ public sealed class CP14BiomeSpawnerSystem : EntitySystem
if (!TryComp<MapGridComponent>(gridUid, out var map))
return;
var seed = _roundSeed.GetSeed();
var vec = _transform.GetGridOrMapTilePosition(ent);
if (!_biome.TryGetTile(vec, biome.Layers, seed, map, out var tile))
if (!_biome.TryGetTile(vec, biome.Layers, _globalSeed, map, out var tile))
return;
// Set new tile
@@ -70,7 +82,7 @@ public sealed class CP14BiomeSpawnerSystem : EntitySystem
}
//Add decals
if (_biome.TryGetDecals(vec, biome.Layers, seed, map, out var decals))
if (_biome.TryGetDecals(vec, biome.Layers, _globalSeed, map, out var decals))
{
foreach (var decal in decals)
{
@@ -87,7 +99,7 @@ public sealed class CP14BiomeSpawnerSystem : EntitySystem
QueueDel(entToRemove);
}
if (_biome.TryGetEntity(vec, biome.Layers, tile.Value, seed, map, out var entityProto))
if (_biome.TryGetEntity(vec, biome.Layers, tile.Value, _globalSeed, map, out var entityProto))
Spawn(entityProto, new EntityCoordinates(gridUid, tileCenterVec));
}
}

View File

@@ -14,20 +14,20 @@ public sealed partial class CP14DemiplaneSystem
private void OnRiftInit(Entity<CP14DemiplaneRiftComponent> rift, ref MapInitEvent args)
{
var map = Transform(rift).MapUid;
if (TryComp<CP14DemiplaneComponent>(map, out var demiplan)) // In demiplan
if (_demiplaneQuery.TryComp(map, out var demiplane)) // In demiplane
{
if (rift.Comp.TryAutoLinkToMap)
rift.Comp.Demiplane = map.Value;
if (rift.Comp.ActiveTeleport)
AddDemiplanRandomEntryPoint((map.Value, demiplan), rift);
AddDemiplaneRandomEntryPoint((map.Value, demiplane), rift);
}
else if (rift.Comp.Demiplane is not null) //We out of demiplan
else if (rift.Comp.Demiplane is not null) //We out of demiplane
{
if (TryComp<CP14DemiplaneComponent>(rift.Comp.Demiplane, out var riftDemiplane))
if (_demiplaneQuery.TryComp(rift.Comp.Demiplane, out var riftDemiplane))
{
if (rift.Comp.ActiveTeleport)
AddDemiplanRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
AddDemiplaneRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
}
}
}
@@ -37,38 +37,35 @@ public sealed partial class CP14DemiplaneSystem
if (rift.Comp.Demiplane is null)
return;
if (!TryComp<CP14DemiplaneComponent>(rift.Comp.Demiplane, out var riftDemiplane))
if (!_demiplaneQuery.TryComp(rift.Comp.Demiplane, out var riftDemiplane))
return;
RemoveDemiplanRandomEntryPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
RemoveDemiplaneRandomEntryPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
RemoveDemiplanRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
}
/// <summary>
///Add a position in the real world where you can get out of this demiplan
///Add a position in the real world where you can get out of this demiplane
/// </summary>
private void AddDemiplanRandomExitPoint(Entity<CP14DemiplaneComponent> demiplan,
private void AddDemiplaneRandomExitPoint(Entity<CP14DemiplaneComponent> demiplane,
Entity<CP14DemiplaneRiftComponent> exitPoint)
{
if (demiplan.Comp.ExitPoints.Contains(exitPoint))
return;
demiplan.Comp.ExitPoints.Add(exitPoint);
exitPoint.Comp.Demiplane = demiplan;
demiplane.Comp.ExitPoints.Add(exitPoint);
exitPoint.Comp.Demiplane = demiplane;
}
/// <summary>
/// Removing the demiplan exit point, one of which the player can exit to
/// Removing the demiplane exit point, one of which the player can exit to
/// </summary>
private void RemoveDemiplanRandomExitPoint(Entity<CP14DemiplaneComponent>? demiplan,
private void RemoveDemiplanRandomExitPoint(Entity<CP14DemiplaneComponent>? demiplane,
EntityUid exitPoint)
{
if (!TryComp<CP14DemiplaneRiftComponent>(exitPoint, out var riftComp))
return;
if (demiplan is not null && demiplan.Value.Comp.ExitPoints.Contains(exitPoint))
if (demiplane is not null && demiplane.Value.Comp.ExitPoints.Contains(exitPoint))
{
demiplan.Value.Comp.ExitPoints.Remove(exitPoint);
demiplane.Value.Comp.ExitPoints.Remove(exitPoint);
riftComp.Demiplane = null;
}
@@ -77,27 +74,24 @@ public sealed partial class CP14DemiplaneSystem
}
/// <summary>
/// Add a position within the demiplan that can be entered into the demiplan
/// Add a position within the demiplane that can be entered into the demiplane
/// </summary>
private void AddDemiplanRandomEntryPoint(Entity<CP14DemiplaneComponent> demiplan,
private void AddDemiplaneRandomEntryPoint(Entity<CP14DemiplaneComponent> demiplane,
Entity<CP14DemiplaneRiftComponent> entryPoint)
{
if (demiplan.Comp.EntryPoints.Contains(entryPoint))
return;
demiplan.Comp.EntryPoints.Add(entryPoint);
entryPoint.Comp.Demiplane = demiplan;
demiplane.Comp.EntryPoints.Add(entryPoint);
entryPoint.Comp.Demiplane = demiplane;
}
private void RemoveDemiplanRandomEntryPoint(Entity<CP14DemiplaneComponent>? demiplan,
private void RemoveDemiplaneRandomEntryPoint(Entity<CP14DemiplaneComponent>? demiplane,
EntityUid entryPoint)
{
if (!TryComp<CP14DemiplaneRiftComponent>(entryPoint, out var riftComp))
return;
if (demiplan is not null && demiplan.Value.Comp.EntryPoints.Contains(entryPoint))
if (demiplane is not null && demiplane.Value.Comp.EntryPoints.Contains(entryPoint))
{
demiplan.Value.Comp.EntryPoints.Remove(entryPoint);
demiplane.Value.Comp.EntryPoints.Remove(entryPoint);
riftComp.Demiplane = null;
}
@@ -105,26 +99,26 @@ public sealed partial class CP14DemiplaneSystem
QueueDel(entryPoint);
}
public bool TryGetDemiplanEntryPoint(Entity<CP14DemiplaneComponent> demiplan, out EntityUid? entryPoint)
public bool TryGetDemiplaneEntryPoint(Entity<CP14DemiplaneComponent> demiplane, out EntityUid? entryPoint)
{
entryPoint = null;
if (demiplan.Comp.EntryPoints.Count == 0)
if (demiplane.Comp.EntryPoints.Count == 0)
return false;
entryPoint = _random.Pick(demiplan.Comp.EntryPoints);
entryPoint = _random.Pick(demiplane.Comp.EntryPoints);
return true;
}
public bool TryGetDemiplanExitPoint(Entity<CP14DemiplaneComponent> demiplan,
public bool TryGetDemiplaneExitPoint(Entity<CP14DemiplaneComponent> demiplane,
out EntityUid? exitPoint)
{
exitPoint = null;
if (demiplan.Comp.ExitPoints.Count == 0)
if (demiplane.Comp.ExitPoints.Count == 0)
return false;
exitPoint = _random.Pick(demiplan.Comp.ExitPoints);
exitPoint = _random.Pick(demiplane.Comp.ExitPoints);
return true;
}
}

View File

@@ -0,0 +1,34 @@
using Content.Server.Chat.Systems;
using Robust.Shared.Random;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem
{
[Dependency] private readonly ChatSystem _chat = default!;
private void InitEchoes()
{
SubscribeLocalEvent<EntitySpokeEvent>(OnSpeak);
}
private void OnSpeak(EntitySpokeEvent ev)
{
var map = Transform(ev.Source).MapUid;
if (!_demiplaneQuery.TryComp(map, out var demiplane))
return;
//Get random exit, and send message there
if (demiplane.ExitPoints.Count == 0)
return;
var exit = _random.Pick(demiplane.ExitPoints);
_chat.TrySendInGameICMessage(exit,
ev.Message,
InGameICChatType.Whisper,
ChatTransmitRange.NoGhosts,
nameOverride: Loc.GetString("cp14-demiplane-echoes"),
hideLog: true);
}
}

View File

@@ -2,11 +2,9 @@ using System.Linq;
using System.Threading;
using Content.Server._CP14.Demiplane.Components;
using Content.Server._CP14.Demiplane.Jobs;
using Content.Server._CP14.RoundEnd;
using Content.Server.GameTicking;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared._CP14.Demiplane.Prototypes;
using Content.Shared._CP14.MagicManacostModify;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
@@ -112,11 +110,11 @@ public sealed partial class CP14DemiplaneSystem
/// <summary>
/// Generates a new random demiplane based on the specified parameters
/// </summary>
public void SpawnRandomDemiplane(ProtoId<CP14DemiplaneLocationPrototype> location, List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers, out Entity<CP14DemiplaneComponent> demiplan, out MapId mapId)
public void SpawnRandomDemiplane(ProtoId<CP14DemiplaneLocationPrototype> location, List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers, out Entity<CP14DemiplaneComponent>? demiplane, out MapId mapId)
{
var mapUid = _mapSystem.CreateMap(out mapId, runMapInit: false);
var demiComp = EntityManager.EnsureComponent<CP14DemiplaneComponent>(mapUid);
demiplan = (mapUid, demiComp);
demiplane = (mapUid, demiComp);
var cancelToken = new CancellationTokenSource();
var job = new CP14SpawnRandomDemiplaneJob(
@@ -141,25 +139,43 @@ public sealed partial class CP14DemiplaneSystem
private void GeneratorUsedInHand(Entity<CP14DemiplaneGeneratorDataComponent> generator, ref UseInHandEvent args)
{
if (generator.Comp.Location is null)
return;
//block the opening of demiplanes after the end of a round
if (_gameTicker.RunLevel != GameRunLevel.InRound)
{
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open-end-round"), generator, args.User);
return;
}
//We cant open demiplan in another demiplan or if parent is not Map
if (HasComp<CP14DemiplaneComponent>(Transform(generator).MapUid) || !HasComp<MapGridComponent>(_transform.GetParentUid(args.User)))
//We cant open demiplane in another demiplane or if parent is not Map
if (_demiplaneQuery.HasComp(Transform(generator).MapUid) || !HasComp<MapGridComponent>(_transform.GetParentUid(args.User)))
{
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open", ("name", MetaData(generator).EntityName)), generator, args.User);
return;
}
SpawnRandomDemiplane(generator.Comp.Location.Value, generator.Comp.SelectedModifiers, out var demiplane, out var mapId);
if (generator.Comp.Location is null)
return;
//an attempt to open demiplanes can be intercepted by other systems that substitute a map instead of generating the planned demiplane.
Entity<CP14DemiplaneComponent>? demiplane = null;
var ev = new CP14DemiplaneGenerationCatchAttemptEvent();
RaiseLocalEvent(ev);
if (ev.Demiplane is null)
{
SpawnRandomDemiplane(generator.Comp.Location.Value, generator.Comp.SelectedModifiers, out demiplane, out var mapId);
}
else
{
demiplane = ev.Demiplane;
}
_statistic.TrackAdd(generator.Comp.Statistic, 1);
if (demiplane is null)
return;
//Admin log needed
EnsureComp<CP14DemiplaneDestroyWithoutStabilizationComponent>(demiplane);
EnsureComp<CP14DemiplaneDestroyWithoutStabilizationComponent>(demiplane.Value);
//Ура, щиткод и магические переменные!
var tempRift = EntityManager.Spawn("CP14DemiplaneTimedRadiusPassway");
@@ -169,8 +185,8 @@ public sealed partial class CP14DemiplaneSystem
var connection = EnsureComp<CP14DemiplaneRiftComponent>(tempRift);
var connection2 = EnsureComp<CP14DemiplaneRiftComponent>(tempRift2);
AddDemiplanRandomExitPoint(demiplane, (tempRift, connection));
AddDemiplanRandomExitPoint(demiplane, (tempRift2, connection2));
AddDemiplaneRandomExitPoint(demiplane.Value, (tempRift, connection));
AddDemiplaneRandomExitPoint(demiplane.Value, (tempRift2, connection2));
#if !DEBUG
QueueDel(generator); //wtf its crash debug build!
@@ -370,3 +386,9 @@ public sealed partial class CP14DemiplaneSystem
throw new InvalidOperationException($"Invalid weighted pick in CP14DemiplanSystem.Generation!");
}
}
public sealed class CP14DemiplaneGenerationCatchAttemptEvent : EntityEventArgs
{
public bool Handled = false;
public Entity<CP14DemiplaneComponent>? Demiplane;
}

View File

@@ -81,10 +81,20 @@ public sealed partial class CP14DemiplaneSystem
if (TryTeleportOutDemiplane(demiplane, uid))
{
if (!safe)
{
var ev = new CP14DemiplaneUnsafeExit();
RaiseLocalEvent(uid, ev);
_body.GibBody(uid);
}
}
}
QueueDel(demiplane);
}
}
public sealed class CP14DemiplaneUnsafeExit : EntityEventArgs
{
}

View File

@@ -1,9 +1,12 @@
using Content.Server._CP14.Demiplane.Components;
using Content.Server._CP14.RoundStatistic;
using Content.Server.Flash;
using Content.Server.Procedural;
using Content.Shared._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared.Popups;
using Robust.Server.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -25,16 +28,45 @@ public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
[Dependency] private readonly FlashSystem _flash = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly CP14RoundStatTrackerSystem _statistic = default!;
private EntityQuery<CP14DemiplaneComponent> _demiplaneQuery;
public override void Initialize()
{
base.Initialize();
_demiplaneQuery = GetEntityQuery<CP14DemiplaneComponent>();
InitGeneration();
InitConnections();
InitStabilization();
InitEchoes();
SubscribeLocalEvent<CP14DemiplaneComponent, ComponentShutdown>(OnDemiplanShutdown);
SubscribeLocalEvent<CP14SpawnOutOfDemiplaneComponent, MapInitEvent>(OnSpawnOutOfDemiplane);
}
private void OnSpawnOutOfDemiplane(Entity<CP14SpawnOutOfDemiplaneComponent> ent, ref MapInitEvent args)
{
//Check if entity is in demiplane
var map = Transform(ent).MapUid;
if (!_demiplaneQuery.TryComp(map, out var demiplane))
return;
//Get random exit demiplane point and spawn entity there
if (demiplane.ExitPoints.Count == 0)
return;
var exit = _random.Pick(demiplane.ExitPoints);
var coordinates = Transform(exit).Coordinates;
var proto = ent.Comp.Proto;
if (proto is null)
proto = MetaData(ent).EntityPrototype?.ID;
Spawn(proto, coordinates);
}
public override void Update(float frameTime)
@@ -51,32 +83,45 @@ public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
/// <param name="demiplane">The demiplane the entity will be teleported to</param>
/// <param name="entity">The entity to be teleported</param>
/// <returns></returns>
public bool TryTeleportIntoDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
public override bool TryTeleportIntoDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
if (entity is null)
return false;
if (!TryGetDemiplanEntryPoint(demiplane, out var entryPoint) || entryPoint is null)
if (!TryGetDemiplaneEntryPoint(demiplane, out var entryPoint) || entryPoint is null)
{
Log.Error($"{entity} cant get in demiplane {demiplane}: no active entry points!");
return false;
}
var targetCoord = Transform(entryPoint.Value).Coordinates;
_flash.Flash(entity.Value, null, null, 3000f, 0.5f);
_transform.SetCoordinates(entity.Value, targetCoord);
_audio.PlayGlobal(demiplane.Comp.ArrivalSound, entity.Value);
TeleportEntityToCoordinate(entity.Value, Transform(entryPoint.Value).Coordinates, demiplane.Comp.ArrivalSound);
return true;
}
/// <summary>
/// Simple teleportation, with common special effects for all the game's teleportation mechanics
/// </summary>
/// <param name="entity"></param>
/// <param name="coordinates"></param>
/// <param name="sound"></param>
public void TeleportEntityToCoordinate(EntityUid? entity, EntityCoordinates coordinates, SoundSpecifier? sound = null)
{
if (entity is null)
return;
_flash.Flash(entity.Value, null, null, 3000f, 0.5f);
_transform.SetCoordinates(entity.Value, coordinates);
_audio.PlayGlobal(sound, entity.Value);
}
/// <summary>
/// Teleports an entity from the demiplane to the real world, to one of the random exit points in the real world.
/// </summary>
/// <param name="demiplane">The demiplane from which the entity will be teleported</param>
/// <param name="entity">An entity that will be teleported into the real world. This entity must be in the demiplane, otherwise the function will not work.</param>
/// <returns></returns>
public bool TryTeleportOutDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
public override bool TryTeleportOutDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
if (entity is null)
return false;
@@ -84,17 +129,13 @@ public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
if (Transform(entity.Value).MapUid != demiplane.Owner)
return false;
if (!TryGetDemiplanExitPoint(demiplane, out var connection) || connection is null)
if (!TryGetDemiplaneExitPoint(demiplane, out var connection) || connection is null)
{
Log.Error($"{entity} cant get out of demiplane {demiplane}: no active connections!");
return false;
}
var targetCoord = Transform(connection.Value).Coordinates;
_flash.Flash(entity.Value, null, null, 3000f, 0.5f);
_transform.SetCoordinates(entity.Value, targetCoord);
_audio.PlayGlobal(demiplane.Comp.DepartureSound, entity.Value);
TeleportEntityToCoordinate(entity.Value, Transform(connection.Value).Coordinates, demiplane.Comp.DepartureSound);
return true;
}
@@ -117,7 +158,7 @@ public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
foreach (var entry in demiplane.Comp.EntryPoints)
{
RemoveDemiplanRandomEntryPoint(demiplane, entry);
RemoveDemiplaneRandomEntryPoint(demiplane, entry);
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared._CP14.Demiplane.Prototypes;
using Content.Shared._CP14.RoundStatistic;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane.Components;
@@ -22,5 +23,8 @@ public sealed partial class CP14DemiplaneGeneratorDataComponent : Component
public Dictionary<int, float> TiersContent = new();
[DataField(required: true)]
public Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> Limits;
public Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> Limits = new();
[DataField]
public ProtoId<CP14RoundStatTrackerPrototype> Statistic = "DemiplaneOpen";
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// Creates an entity on demiplane exit points when that entity appears.
/// </summary>
[RegisterComponent]
public sealed partial class CP14SpawnOutOfDemiplaneComponent : Component
{
/// <summary>
/// If null, the ProtoId of this entity is taken from the entity itself.
/// </summary>
[DataField]
public EntProtoId? Proto;
}

View File

@@ -0,0 +1,30 @@
using Content.Server._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
using Robust.Shared.Map.Components;
namespace Content.Server._CP14.DemiplaneAdmin;
public sealed partial class CP14DemiplaneAdminSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14DemiplaneGenerationCatchAttemptEvent>(OnAdminDemiplaneCatch);
}
private void OnAdminDemiplaneCatch(CP14DemiplaneGenerationCatchAttemptEvent ev)
{
if (ev.Handled)
return;
var query = EntityQueryEnumerator<CP14DemiplaneRiftCatcherComponent, MapComponent, CP14DemiplaneComponent>();
while (query.MoveNext(out var uid, out var catcher, out var map, out var demiplane))
{
ev.Demiplane = (uid, demiplane);
ev.Handled = true;
RemCompDeferred(uid, catcher);
return;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server._CP14.DemiplaneAdmin;
/// <summary>
/// This demiplane can be added to a map by the admins, which will redirect the next opened demiplane key to that map
/// </summary>
[RegisterComponent]
public sealed partial class CP14DemiplaneRiftCatcherComponent : Component
{
}

View File

@@ -1,4 +1,5 @@
using Content.Server._CP14.Demiplane;
using Content.Server._CP14.RoundEnd;
using Content.Server.Interaction;
using Content.Server.Mind;
using Content.Server.Popups;
@@ -28,13 +29,74 @@ public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<CP14DemiplaneRadiusTimedPasswayComponent, MapInitEvent>(RadiusMapInit);
SubscribeLocalEvent<CP14MonolithTimedPasswayComponent, MapInitEvent>(MonolithMapInit);
SubscribeLocalEvent<CP14DemiplaneRiftOpenedComponent, CP14DemiplanPasswayUseDoAfter>(OnOpenRiftInteractDoAfter);
}
// !!!SHITCODE WARNING!!!
// This whole module is saturated with shieldcode, code duplication and other delights. Why? Because.
//TODO: Refactor this shitcode
public override void Update(float frameTime)
{
base.Update(frameTime);
DemiplaneTeleportUpdate();
var query = EntityQueryEnumerator<CP14MonolithTimedPasswayComponent>();
while (query.MoveNext(out var uid, out var passWay))
{
if (_timing.CurTime < passWay.NextTimeTeleport)
continue;
passWay.NextTimeTeleport = _timing.CurTime + passWay.Delay;
//Get all teleporting entities
HashSet<EntityUid> teleportedEnts = new();
var nearestEnts = _lookup.GetEntitiesInRange(uid, passWay.Radius);
foreach (var ent in nearestEnts)
{
if (HasComp<GhostComponent>(ent))
continue;
if (!_mind.TryGetMind(ent, out var mindId, out var mind))
continue;
if (!_interaction.InRangeUnobstructed(ent, uid))
continue;
teleportedEnts.Add(ent);
}
while (teleportedEnts.Count > passWay.MaxEntities)
{
teleportedEnts.Remove(_random.Pick(teleportedEnts));
}
//Aaaand teleport it
var monoliths = EntityQueryEnumerator<CP14MagicContainerRoundFinisherComponent>();
while (monoliths.MoveNext(out var monolithUid, out var monolith))
{
var coord = Transform(monolithUid).Coordinates;
//Shitcode select first one
foreach (var ent in teleportedEnts)
{
if (TryComp<PullerComponent>(ent, out var puller))
_demiplan.TeleportEntityToCoordinate(puller.Pulling, coord);
_demiplan.TeleportEntityToCoordinate(ent, coord);
_audio.PlayPvs(passWay.ArrivalSound, ent);
}
break;
}
_audio.PlayPvs(passWay.DepartureSound, Transform(uid).Coordinates);
QueueDel(uid);
}
}
private void DemiplaneTeleportUpdate()
{
//Radius passway
var query = EntityQueryEnumerator<CP14DemiplaneRadiusTimedPasswayComponent, CP14DemiplaneRiftComponent>();
while (query.MoveNext(out var uid, out var passWay, out var rift))
@@ -70,7 +132,7 @@ public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
var map = Transform(uid).MapUid;
if (TryComp<CP14DemiplaneComponent>(map, out var demiplan))
{
if (!_demiplan.TryGetDemiplanExitPoint((map.Value, demiplan), out _))
if (!_demiplan.TryGetDemiplaneExitPoint((map.Value, demiplan), out _))
break;
foreach (var ent in teleportedEnts) //We in demiplan, tp OUT
@@ -87,7 +149,7 @@ public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
if (rift.Demiplane is not null &&
TryComp<CP14DemiplaneComponent>(rift.Demiplane.Value, out var riftDemiplane))
{
if (!_demiplan.TryGetDemiplanEntryPoint((rift.Demiplane.Value, riftDemiplane), out _))
if (!_demiplan.TryGetDemiplaneEntryPoint((rift.Demiplane.Value, riftDemiplane), out _))
break;
foreach (var ent in teleportedEnts) //We out demiplan, tp IN
@@ -111,6 +173,11 @@ public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
radiusPassWay.Comp.NextTimeTeleport = _timing.CurTime + radiusPassWay.Comp.Delay;
}
private void MonolithMapInit(Entity<CP14MonolithTimedPasswayComponent> radiusPassWay, ref MapInitEvent args)
{
radiusPassWay.Comp.NextTimeTeleport = _timing.CurTime + radiusPassWay.Comp.Delay;
}
private void OnOpenRiftInteractDoAfter(Entity<CP14DemiplaneRiftOpenedComponent> passWay,
ref CP14DemiplanPasswayUseDoAfter args)
{

View File

@@ -0,0 +1,138 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Content.Shared._CP14.Discord;
using Content.Shared.CCVar;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server._CP14.Discord;
public sealed class DiscordAuthManager
{
[Dependency] private readonly IServerNetManager _netMgr = default!;
[Dependency] private readonly IPlayerManager _playerMgr = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private ISawmill _sawmill = default!;
private readonly HttpClient _httpClient = new();
private bool _enabled = false;
private string _apiUrl = string.Empty;
private string _apiKey = string.Empty;
public event EventHandler<ICommonSession>? PlayerVerified;
public void Initialize()
{
_sawmill = Logger.GetSawmill("discordAuth");
_cfg.OnValueChanged(CCVars.DiscordAuthEnabled, v => _enabled = v, true);
_cfg.OnValueChanged(CCVars.DiscordAuthUrl, v => _apiUrl = v, true);
_cfg.OnValueChanged(CCVars.DiscordAuthToken, v => _apiKey = v, true);
_netMgr.RegisterNetMessage<MsgDiscordAuthRequired>();
_netMgr.RegisterNetMessage<MsgDiscordAuthCheck>(OnAuthCheck);
_playerMgr.PlayerStatusChanged += OnPlayerStatusChanged;
PlayerVerified += OnPlayerVerified;
}
private void OnPlayerVerified(object? obj, ICommonSession session)
{
Timer.Spawn(0, () => _playerMgr.JoinGame(session));
}
private async void OnAuthCheck(MsgDiscordAuthCheck msg)
{
var verified = await IsVerified(msg.MsgChannel.UserId);
if (!verified)
return;
var session = _playerMgr.GetSessionById(msg.MsgChannel.UserId);
PlayerVerified?.Invoke(this, session);
}
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)
{
if (args.NewStatus != SessionStatus.Connected)
return;
if (!_enabled)
{
PlayerVerified?.Invoke(this, args.Session);
return;
}
if (args.NewStatus == SessionStatus.Connected)
{
var verified = await IsVerified(args.Session.UserId);
if (verified)
{
PlayerVerified?.Invoke(this, args.Session);
return;
}
var message = new MsgDiscordAuthRequired();
message.AuthUrl = await GenerateLink(args.Session.UserId) ?? string.Empty;
args.Session.Channel.SendMessage(message);
}
}
public async Task<bool> IsVerified(NetUserId userId, CancellationToken cancel = default)
{
_sawmill.Debug($"Player {userId} check Discord verification");
var requestUrl = $"{_apiUrl}/api/uuid?method=uid&id={userId}";
_sawmill.Debug($"Auth request url:{requestUrl}");
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
var response = await _httpClient.SendAsync(request, cancel);
_sawmill.Debug($"{await response.Content.ReadAsStringAsync(cancel)}");
_sawmill.Debug($"{(int) response.StatusCode}");
return response.StatusCode == HttpStatusCode.OK;
}
public async Task<string?> GenerateLink(NetUserId userId, CancellationToken cancel = default)
{
_sawmill.Debug($"Generating link for {userId}");
var requestUrl = $"{_apiUrl}/api/link?uid={userId}";
// try catch block to catch HttpRequestExceptions due to remote service unavailability
try
{
var response = await _httpClient.GetAsync(requestUrl, cancel);
if (!response.IsSuccessStatusCode)
return null;
var link = await response.Content.ReadFromJsonAsync<DiscordLinkResponse>(cancel);
return link!.Link;
}
catch (HttpRequestException)
{
_sawmill.Error("Remote auth service is unreachable. Check if its online!");
return null;
}
catch (Exception e)
{
_sawmill.Error($"Unexpected error verifying user via auth service. Error: {e.Message}. Stack: \n{e.StackTrace}");
return null;
}
}
sealed class DiscordLinkResponse
{
[JsonPropertyName("link")]
public string Link { get; set; } = string.Empty;
}
}

View File

@@ -57,7 +57,7 @@ public sealed partial class CP14MagicSystem : CP14SharedMagicSystem
private void OnSpellSpoken(Entity<CP14MagicEffectVerbalAspectComponent> ent, ref CP14VerbalAspectSpeechEvent args)
{
if (args.Performer is not null && args.Speech is not null)
_chat.TrySendInGameICMessage(args.Performer.Value, args.Speech, InGameICChatType.Speak, true);
_chat.TrySendInGameICMessage(args.Performer.Value, args.Speech, args.Emote ? InGameICChatType.Emote : InGameICChatType.Speak, true);
}
private void OnSpawnMagicVisualEffect(Entity<CP14MagicEffectCastingVisualComponent> ent, ref CP14StartCastMagicEffectEvent args)

View File

@@ -0,0 +1,26 @@
using Content.Server._CP14.Objectives.Systems;
using Content.Shared._CP14.RoundStatistic;
using Content.Shared.Destructible.Thresholds;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server._CP14.Objectives.Components;
[RegisterComponent, Access(typeof(CP14StatisticRangeConditionSystem))]
public sealed partial class CP14StatisticRangeConditionComponent : Component
{
[DataField(required: true)]
public ProtoId<CP14RoundStatTrackerPrototype> Statistic;
[DataField(required: true)]
public MinMax Range;
[DataField(required: true)]
public LocId ObjectiveText;
[DataField(required: true)]
public LocId ObjectiveDescription;
[DataField(required: true)]
public SpriteSpecifier? ObjectiveSprite;
}

View File

@@ -38,5 +38,5 @@ public sealed partial class CP14TownSendConditionComponent : Component
public LocId ObjectiveText;
[DataField(required: true)]
public LocId DescriptionText;
public LocId ObjectiveDescription;
}

View File

@@ -0,0 +1,54 @@
using Content.Server._CP14.Objectives.Components;
using Content.Server._CP14.RoundStatistic;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.Objectives.Systems;
public sealed class CP14StatisticRangeConditionSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
[Dependency] private readonly CP14RoundStatTrackerSystem _statistic = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14StatisticRangeConditionComponent, ObjectiveAfterAssignEvent>(OnAfterAssign);
SubscribeLocalEvent<CP14StatisticRangeConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
}
private void OnAfterAssign(Entity<CP14StatisticRangeConditionComponent> condition, ref ObjectiveAfterAssignEvent args)
{
var title = Loc.GetString(condition.Comp.ObjectiveText,
("min", condition.Comp.Range.Min),
("max", condition.Comp.Range.Max));
var description = Loc.GetString(condition.Comp.ObjectiveDescription,
("min", condition.Comp.Range.Min),
("max", condition.Comp.Range.Max));
_metaData.SetEntityName(condition.Owner, title, args.Meta);
_metaData.SetEntityDescription(condition.Owner, description, args.Meta);
if (condition.Comp.ObjectiveSprite is not null)
_objectives.SetIcon(condition.Owner, condition.Comp.ObjectiveSprite, args.Objective);
}
private void OnGetProgress(Entity<CP14StatisticRangeConditionComponent> ent, ref ObjectiveGetProgressEvent args)
{
var statValue = _statistic.GetTrack(ent.Comp.Statistic);
if (statValue is null || statValue > ent.Comp.Range.Max || statValue < ent.Comp.Range.Min)
{
args.Progress = 0;
return;
}
args.Progress = 1;
}
}

View File

@@ -26,7 +26,6 @@ public sealed class CP14TownSendConditionSystem : EntitySystem
_stealQuery = GetEntityQuery<StealTargetComponent>();
_stackQuery = GetEntityQuery<StackComponent>();
SubscribeLocalEvent<CP14TownSendConditionComponent, ObjectiveAssignedEvent>(OnAssigned);
SubscribeLocalEvent<CP14TownSendConditionComponent, ObjectiveAfterAssignEvent>(OnAfterAssign);
SubscribeLocalEvent<CP14TownSendConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
@@ -72,12 +71,6 @@ public sealed class CP14TownSendConditionSystem : EntitySystem
}
}
private void OnAssigned(Entity<CP14TownSendConditionComponent> condition, ref ObjectiveAssignedEvent args)
{
//TODO: Add ability to create mindfree objectives to Wizden
//condition.Comp.CollectionSize = _random.Next(condition.Comp.MinCollectionSize, condition.Comp.MaxCollectionSize);
}
//Set the visual, name, icon for the objective.
private void OnAfterAssign(Entity<CP14TownSendConditionComponent> condition, ref ObjectiveAfterAssignEvent args)
{
@@ -86,7 +79,7 @@ public sealed class CP14TownSendConditionSystem : EntitySystem
var group = _proto.Index(condition.Comp.CollectGroup);
var title = Loc.GetString(condition.Comp.ObjectiveText, ("itemName", Loc.GetString(group.Name)), ("count", condition.Comp.CollectionSize));
var description = Loc.GetString(condition.Comp.DescriptionText, ("itemName", Loc.GetString(group.Name)), ("count", condition.Comp.CollectionSize));
var description = Loc.GetString(condition.Comp.ObjectiveDescription, ("itemName", Loc.GetString(group.Name)), ("count", condition.Comp.CollectionSize));
_metaData.SetEntityName(condition.Owner, title, args.Meta);
_metaData.SetEntityDescription(condition.Owner, description, args.Meta);

View File

@@ -1,21 +0,0 @@
/*
* All right reserved to CrystallEdge.
*
* BUT this file is sublicensed under MIT License
*
*/
namespace Content.Server._CP14.RoundSeed;
/// <summary>
/// This is used for round seed
/// </summary>
[RegisterComponent, Access(typeof(CP14RoundSeedSystem))]
public sealed partial class CP14RoundSeedComponent : Component
{
[ViewVariables]
public static int MaxValue = 10000;
[ViewVariables]
public int Seed;
}

View File

@@ -1,53 +0,0 @@
/*
* All right reserved to CrystallEdge.
*
* BUT this file is sublicensed under MIT License
*
*/
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Server._CP14.RoundSeed;
/// <summary>
/// Provides a round seed for another systems
/// </summary>
public sealed class CP14RoundSeedSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14RoundSeedComponent, ComponentStartup>(OnComponentStartup);
}
private void OnComponentStartup(Entity<CP14RoundSeedComponent> ent, ref ComponentStartup args)
{
ent.Comp.Seed = _random.Next(CP14RoundSeedComponent.MaxValue);
}
private int SetupSeed()
{
return AddComp<CP14RoundSeedComponent>(Spawn(null, MapCoordinates.Nullspace)).Seed;
}
/// <summary>
/// Returns the round seed if assigned, otherwise assigns the round seed itself.
/// </summary>
/// <returns>seed of the round</returns>
public int GetSeed()
{
var query = EntityQuery<CP14RoundSeedComponent>();
foreach (var comp in query)
{
return comp.Seed;
}
var seed = SetupSeed();
Log.Warning($"Missing RoundSeed. Seed set to {seed}");
return seed;
}
}

View File

@@ -0,0 +1,71 @@
using System.Text;
using Content.Server.GameTicking;
using Content.Shared._CP14.RoundStatistic;
using Content.Shared.GameTicking;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.RoundStatistic;
public sealed partial class CP14RoundStatTrackerSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly Dictionary<ProtoId<CP14RoundStatTrackerPrototype>, int> _tracking = new();
public override void Initialize()
{
base.Initialize();
InitializeDemiplaneDeath();
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundReset);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndTextAppend);
ClearStatistic();
}
private void OnRoundReset(RoundRestartCleanupEvent ev)
{
ClearStatistic();
}
private void OnRoundEndTextAppend(RoundEndTextAppendEvent ev)
{
//TODO: Move to separate UI Text block
var sb = new StringBuilder();
sb.Append($"[head=3]{Loc.GetString("cp14-tracker-header")}[/head] \n");
foreach (var pair in _tracking)
{
if (!_proto.TryIndex(pair.Key, out var indexedTracker))
continue;
sb.Append($"- {Loc.GetString(indexedTracker.Text)}: {pair.Value}\n");
}
ev.AddLine(sb.ToString());
}
private void ClearStatistic()
{
_tracking.Clear();
foreach (var statTracker in _proto.EnumeratePrototypes<CP14RoundStatTrackerPrototype>())
{
_tracking.Add(statTracker.ID, 0);
}
}
public void TrackAdd(ProtoId<CP14RoundStatTrackerPrototype> proto, int dif)
{
_tracking[proto] += Math.Max(dif, 0);
}
public int? GetTrack(ProtoId<CP14RoundStatTrackerPrototype> proto)
{
if (!_tracking.TryGetValue(proto, out var stat))
{
Log.Error($"Failed to get round statistic: {proto}");
return null;
}
return stat;
}
}

View File

@@ -0,0 +1,14 @@
using Content.Shared._CP14.RoundStatistic;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.RoundStatistic.DemiplaneDeath;
/// <summary>
/// Tracks the destruction or full-blown death of this entity.
/// </summary>
[RegisterComponent]
public sealed partial class CP14DeathDemiplaneStatisticComponent : Component
{
[DataField]
public ProtoId<CP14RoundStatTrackerPrototype> Statistic = "DemiplaneDeaths";
}

View File

@@ -0,0 +1,35 @@
using Content.Server._CP14.Demiplane;
using Content.Server._CP14.RoundStatistic.DemiplaneDeath;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared.GameTicking;
namespace Content.Server._CP14.RoundStatistic;
public sealed partial class CP14RoundStatTrackerSystem
{
private void InitializeDemiplaneDeath()
{
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
SubscribeLocalEvent<CP14DeathDemiplaneStatisticComponent, EntityTerminatingEvent>(OnEntityTerminated);
SubscribeLocalEvent<CP14DeathDemiplaneStatisticComponent, CP14DemiplaneUnsafeExit>(OnDemiplaneUnsafeExit);
}
private void OnSpawnComplete(PlayerSpawnCompleteEvent ev)
{
EnsureComp<CP14DeathDemiplaneStatisticComponent>(ev.Mob);
}
private void OnDemiplaneUnsafeExit(Entity<CP14DeathDemiplaneStatisticComponent> ent, ref CP14DemiplaneUnsafeExit args)
{
TrackAdd(ent.Comp.Statistic, 1);
}
//For round remove variants, like gibs or chasm falls
private void OnEntityTerminated(Entity<CP14DeathDemiplaneStatisticComponent> ent, ref EntityTerminatingEvent args)
{
if (!HasComp<CP14DemiplaneComponent>(Transform(ent).MapUid))
return;
TrackAdd(ent.Comp.Statistic, 1);
}
}

View File

@@ -2,10 +2,12 @@ using System.Linq;
using System.Numerics;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.DoAfter;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared._CP14.Temperature;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Nutrition.Components;
using Content.Shared.Smoking;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
@@ -25,6 +27,7 @@ public sealed partial class CP14FireSpreadSystem : CP14SharedFireSpreadSystem
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
[Dependency] private readonly SmokingSystem _smoking = default!;
private readonly EntProtoId _fireProto = "CP14Fire";
@@ -35,6 +38,8 @@ public sealed partial class CP14FireSpreadSystem : CP14SharedFireSpreadSystem
base.Initialize();
SubscribeLocalEvent<FlammableComponent, CP14IgnitionDoAfter>(OnFlammableIgnited);
SubscribeLocalEvent<SmokableComponent, CP14IgnitionDoAfter>(OnDelayedIgnite);
SubscribeLocalEvent<SmokingPipeComponent, CP14IgnitionDoAfter>(OnDelayedPipeIgnite);
SubscribeLocalEvent<CP14FlammableBonusDamageComponent, MeleeHitEvent>(OnFlammableMeleeHit);
}
@@ -59,6 +64,22 @@ public sealed partial class CP14FireSpreadSystem : CP14SharedFireSpreadSystem
args.Handled = true;
}
//For smokable cigars
private void OnDelayedIgnite(Entity<SmokableComponent> ent, ref CP14IgnitionDoAfter args)
{
_smoking.SetSmokableState(ent, SmokableState.Lit, ent.Comp);
}
//For smokable pipes
private void OnDelayedPipeIgnite(Entity<SmokingPipeComponent> pipe, ref CP14IgnitionDoAfter args)
{
if (!TryComp<SmokableComponent>(pipe, out var smokable))
return;
if (_smoking.TryTransferReagents(pipe, (pipe.Owner, smokable)))
_smoking.SetSmokableState(pipe, SmokableState.Lit, smokable);
}
public override void Update(float frameTime)
{
base.Update(frameTime);

View File

@@ -30,11 +30,11 @@ public sealed partial class CP14WorkbenchSystem
if (!_proto.TryIndex(recipeId, out var indexedRecipe))
continue;
if (indexedRecipe.KnowledgeRequired is not null)
{
if (!_knowledge.HasKnowledge(user, indexedRecipe.KnowledgeRequired.Value))
continue;
}
//if (indexedRecipe.KnowledgeRequired is not null)
//{
// if (!_knowledge.HasKnowledge(user, indexedRecipe.KnowledgeRequired.Value))
// continue;
//}
var entry = new CP14WorkbenchUiRecipesEntry(recipeId, CanCraftRecipe(indexedRecipe, placedEntities, user));

View File

@@ -87,99 +87,16 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
return;
}
if (recipe.KnowledgeRequired is not null)
_knowledge.UseKnowledge(args.User, recipe.KnowledgeRequired.Value);
var resultEntities = new HashSet<EntityUid>();
for (int i = 0; i < recipe.ResultCount; i++)
{
var resultEntity = Spawn(recipe.Result);
resultEntities.Add(resultEntity);
if (recipe.TryMergeSolutions)
{
_solutionContainer.TryGetSolution(resultEntity, recipe.Solution, out var resultSolution, out _);
if (resultSolution is not null)
{
resultSolution.Value.Comp.Solution.MaxVolume = 0;
_solutionContainer.RemoveAllSolution(resultSolution
.Value); //If we combine ingredient solutions, we do not use the default solution prescribed in the entity.
}
}
}
foreach (var requiredIngredient in recipe.Entities)
foreach (var req in recipe.Requirements)
{
var requiredCount = requiredIngredient.Value;
foreach (var placedEntity in placedEntities)
{
if (!TryComp<MetaDataComponent>(placedEntity, out var metaData))
continue;
if (metaData.EntityPrototype is null)
continue;
var placedProto = metaData.EntityPrototype.ID;
if (placedProto == requiredIngredient.Key && requiredCount > 0)
{
// Trying merge solutions
if (recipe.TryMergeSolutions)
{
_solutionContainer.TryGetSolution(placedEntity,
recipe.Solution,
out var ingredientSoln,
out var ingredientSolution);
if (ingredientSoln is not null &&
ingredientSolution is not null)
{
var splitted = _solutionContainer.SplitSolution(ingredientSoln.Value,
ingredientSolution.Volume / recipe.ResultCount);
foreach (var resultEntity in resultEntities)
{
_solutionContainer.TryGetSolution(resultEntity,
recipe.Solution,
out var resultSolution,
out _);
if (resultSolution is not null)
{
resultSolution.Value.Comp.Solution.MaxVolume +=
ingredientSoln.Value.Comp.Solution.MaxVolume / recipe.ResultCount;
_solutionContainer.TryAddSolution(resultSolution.Value, splitted);
}
}
}
}
requiredCount--;
Del(placedEntity);
}
}
}
foreach (var requiredStack in recipe.Stacks)
{
var requiredCount = requiredStack.Value;
foreach (var placedEntity in placedEntities)
{
if (!_stackQuery.TryGetComponent(placedEntity, out var stack))
continue;
if (stack.StackTypeId != requiredStack.Key)
continue;
var count = (int)MathF.Min(requiredCount, stack.Count);
if (stack.Count - count <= 0)
Del(placedEntity);
else
_stack.SetCount(placedEntity, stack.Count - count, stack);
requiredCount -= count;
}
req.PostCraft(EntityManager, placedEntities, args.User);
}
//We teleport result to workbench AFTER craft.
@@ -219,56 +136,12 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
private bool CanCraftRecipe(CP14WorkbenchRecipePrototype recipe, HashSet<EntityUid> entities, EntityUid user)
{
//Knowledge check
if (recipe.KnowledgeRequired is not null && !_knowledge.HasKnowledge(user, recipe.KnowledgeRequired.Value))
return false;
//Ingredients check
var indexedIngredients = IndexIngredients(entities);
foreach (var requiredIngredient in recipe.Entities)
foreach (var req in recipe.Requirements)
{
if (!indexedIngredients.TryGetValue(requiredIngredient.Key, out var availableQuantity) ||
availableQuantity < requiredIngredient.Value)
return false;
}
foreach (var (key, value) in recipe.Stacks)
{
var count = 0;
foreach (var ent in entities)
{
if (_stackQuery.TryGetComponent(ent, out var stack))
{
if (stack.StackTypeId != key)
continue;
count += stack.Count;
}
}
if (count < value)
if (!req.CheckRequirement(EntityManager, _proto, entities, user))
return false;
}
return true;
}
private Dictionary<EntProtoId, int> IndexIngredients(HashSet<EntityUid> ingredients)
{
var indexedIngredients = new Dictionary<EntProtoId, int>();
foreach (var ingredient in ingredients)
{
var protoId = _metaQuery.GetComponent(ingredient).EntityPrototype?.ID;
if (protoId == null)
continue;
if (indexedIngredients.ContainsKey(protoId))
indexedIngredients[protoId]++;
else
indexedIngredients[protoId] = 1;
}
return indexedIngredients;
}
}

View File

@@ -16,6 +16,7 @@
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj">
<Private>false</Private>
</ProjectReference>

View File

@@ -0,0 +1,15 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
public static readonly CVarDef<bool> DiscordAuthEnabled =
CVarDef.Create("cp14.discord_auth_enabled", false, CVar.SERVERONLY);
public static readonly CVarDef<string> DiscordAuthUrl =
CVarDef.Create("cp14.discord_auth_url", "http://localhost:8000/sponsors", CVar.SERVERONLY | CVar.CONFIDENTIAL);
public static readonly CVarDef<string> DiscordAuthToken =
CVarDef.Create("cp14.discord_auth_token", "token", CVar.SERVERONLY | CVar.CONFIDENTIAL);
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
public static readonly CVarDef<bool> QueueEnabled =
CVarDef.Create("cp14.join_queue_enabled", true, CVar.SERVERONLY);
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.Configuration;
namespace Content.Shared.CCVar;
public sealed partial class CCVars
{
public static readonly CVarDef<string> SponsorsApiUrl =
CVarDef.Create("cp14.sponsor_api_url", "http://localhost:8000/sponsors", CVar.SERVERONLY | CVar.CONFIDENTIAL);
public static readonly CVarDef<string> SponsorsApiKey =
CVarDef.Create("cp14.sponsor_api_key", "token", CVar.SERVERONLY | CVar.CONFIDENTIAL);
}

View File

@@ -14,10 +14,10 @@ public abstract partial class CP14SharedDemiplaneSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<CP14DemiplaneRiftOpenedComponent, InteractHandEvent>(OnDemiplanPasswayInteract);
SubscribeLocalEvent<CP14DemiplaneRiftOpenedComponent, InteractHandEvent>(OnDemiplanePasswayInteract);
}
private void OnDemiplanPasswayInteract(Entity<CP14DemiplaneRiftOpenedComponent> passway, ref InteractHandEvent args)
private void OnDemiplanePasswayInteract(Entity<CP14DemiplaneRiftOpenedComponent> passway, ref InteractHandEvent args)
{
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
args.User,
@@ -33,6 +33,16 @@ public abstract partial class CP14SharedDemiplaneSystem : EntitySystem
MovementThreshold = 0.2f,
});
}
public virtual bool TryTeleportIntoDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
return true;
}
public virtual bool TryTeleportOutDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
return true;
}
}
[Serializable, NetSerializable]

View File

@@ -0,0 +1,29 @@
using Robust.Shared.Audio;
namespace Content.Shared._CP14.DemiplaneTraveling;
/// <summary>
/// teleports a certain number of entities to coordinate with a delay
/// </summary>
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class CP14MonolithTimedPasswayComponent : Component
{
[DataField]
public int MaxEntities = 3;
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(10f);
[DataField]
public float Radius = 3f;
[DataField]
[AutoPausedField]
public TimeSpan NextTimeTeleport = TimeSpan.Zero;
[DataField("arrivalSound")]
public SoundSpecifier ArrivalSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
[DataField("departureSound")]
public SoundSpecifier DepartureSound = new SoundPathSpecifier("/Audio/Effects/teleport_departure.ogg");
}

View File

@@ -0,0 +1,18 @@
using Lidgren.Network;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Discord;
public sealed class MsgDiscordAuthCheck : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Command;
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
}
}

View File

@@ -0,0 +1,21 @@
using Lidgren.Network;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Discord;
public sealed class MsgDiscordAuthRequired : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Command;
public string AuthUrl { get; set; } = string.Empty;
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
AuthUrl = buffer.ReadString();
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.Write(AuthUrl);
}
}

View File

@@ -95,7 +95,8 @@ public abstract partial class CP14SharedMagicSystem
var ev = new CP14VerbalAspectSpeechEvent
{
Performer = args.Performer,
Speech = ent.Comp.StartSpeech,
Speech = Loc.GetString(ent.Comp.StartSpeech),
Emote = ent.Comp.Emote
};
RaiseLocalEvent(ent, ref ev);
}
@@ -108,7 +109,8 @@ public abstract partial class CP14SharedMagicSystem
var ev = new CP14VerbalAspectSpeechEvent
{
Performer = args.Performer,
Speech = ent.Comp.EndSpeech,
Speech = Loc.GetString(ent.Comp.EndSpeech),
Emote = ent.Comp.Emote
};
RaiseLocalEvent(ent, ref ev);
}

View File

@@ -7,10 +7,13 @@ namespace Content.Shared._CP14.MagicSpell.Components;
public sealed partial class CP14MagicEffectVerbalAspectComponent : Component
{
[DataField]
public string StartSpeech = string.Empty;
public string StartSpeech = string.Empty; //Not LocId!
[DataField]
public string EndSpeech = string.Empty;
public string EndSpeech = string.Empty; //Not LocId!
[DataField]
public bool Emote = false;
}
/// <summary>
@@ -22,4 +25,6 @@ public sealed class CP14VerbalAspectSpeechEvent : EntityEventArgs
public EntityUid? Performer { get; init; }
public string? Speech { get; init; }
public bool Emote { get; init; }
}

View File

@@ -0,0 +1,26 @@
using Content.Shared._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellDemiplaneInfiltration : CP14SpellEffect
{
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.User is null)
return;
if (!entManager.TryGetComponent<CP14DemiplaneRiftComponent>(args.Target, out var rift))
return;
if (rift.Demiplane is null)
return;
if (!entManager.TryGetComponent<CP14DemiplaneComponent>(rift.Demiplane.Value, out var demiplane))
return;
var demiplaneSystem = entManager.System<CP14SharedDemiplaneSystem>();
demiplaneSystem.TryTeleportIntoDemiplane((rift.Demiplane.Value, demiplane), args.User.Value);
}
}

View File

@@ -0,0 +1,31 @@
using Content.Shared.Throwing;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellThrowFromUser : CP14SpellEffect
{
[DataField]
public float ThrowPower = 10f;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
var targetEntity = args.Target.Value;
var throwing = entManager.System<ThrowingSystem>();
var xfom = entManager.System<SharedTransformSystem>();
if (!entManager.TryGetComponent<TransformComponent>(args.User, out var userTransform))
return;
if (!entManager.TryGetComponent<TransformComponent>(targetEntity, out var targetTransform))
return;
var worldPos = xfom.GetWorldPosition(args.User.Value);
var foo = xfom.GetWorldPosition(args.Target.Value) - worldPos;
throwing.TryThrow(targetEntity, foo * 2.5f, ThrowPower, args.User, doSpin: true);
}
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.Materials;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Material;
[RegisterComponent]
public sealed partial class CP14MaterialComponent : Component
{
[DataField(required: true)]
public Dictionary<ProtoId<MaterialPrototype>, int> Materials = new();
}

View File

@@ -0,0 +1,39 @@
using System.Text;
using Content.Shared.Examine;
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Material;
public sealed partial class CP14MaterialSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14MaterialComponent, ExaminedEvent>(OnMaterialExamined);
}
private void OnMaterialExamined(Entity<CP14MaterialComponent> ent, ref ExaminedEvent args)
{
TryComp<StackComponent>(ent, out var stack);
var sb = new StringBuilder();
sb.Append($"{Loc.GetString("cp14-material-examine")}\n");
foreach (var material in ent.Comp.Materials)
{
if (!_proto.TryIndex(material.Key, out var indexedMaterial))
continue;
var count = material.Value;
if (stack is not null)
count *= stack.Count;
sb.Append($"[color={indexedMaterial.Color.ToHex()}]{Loc.GetString(indexedMaterial.Name)}[/color] ({count})\n");
}
args.PushMarkup(sb.ToString());
}
}

View File

@@ -20,7 +20,7 @@ public sealed partial class CP14ModularCraftPartPrototype : IPrototype
public EntProtoId? SourcePart;
[DataField]
public float DestroyProb = 0.25f;
public float DestroyProb = 0.0f;
[DataField(serverOnly: true)]
public List<CP14ModularCraftModifier> Modifiers = new();

View File

@@ -0,0 +1,12 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.RoundStatistic;
[Prototype("statisticTracker")]
public sealed partial class CP14RoundStatTrackerPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
[DataField(required: true)]
public LocId Text;
}

View File

@@ -3,8 +3,6 @@
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.Stacks;
using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
@@ -26,27 +24,12 @@ public sealed class CP14WorkbenchRecipePrototype : IPrototype
[DataField]
public SoundSpecifier? OverrideCraftSound;
[DataField]
public Dictionary<EntProtoId, int> Entities = new();
[DataField]
public Dictionary<ProtoId<StackPrototype>, int> Stacks = new();
[DataField(required: true)]
public List<CP14WorkbenchCraftRequirement> Requirements = new();
[DataField(required: true)]
public EntProtoId Result;
[DataField]
public int ResultCount = 1;
[DataField]
public bool TryMergeSolutions = false;
[DataField]
public string Solution = "food";
/// <summary>
/// If the player does not have this knowledge, the recipe will not be displayed in the workbench.
/// </summary>
[DataField]
public ProtoId<CP14KnowledgePrototype>? KnowledgeRequired;
}

View File

@@ -0,0 +1,48 @@
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class KnowledgeRequired : CP14WorkbenchCraftRequirement
{
/// <summary>
/// If the player does not have this knowledge, the recipe will not be displayed in the workbench.
/// </summary>
[DataField(required: true)]
public ProtoId<CP14KnowledgePrototype> Knowledge;
public override bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
{
var knowledgeSystem = entManager.System<SharedCP14KnowledgeSystem>();
return knowledgeSystem.HasKnowledge(user, Knowledge);
}
public override void PostCraft(EntityManager entManager, HashSet<EntityUid> placedEntities, EntityUid user)
{
var knowledgeSystem = entManager.System<SharedCP14KnowledgeSystem>();
knowledgeSystem.UseKnowledge(user, Knowledge);
}
public override string GetRequirementTitle(IPrototypeManager protoManager)
{
return !protoManager.TryIndex(Knowledge, out var indexedKnowledge)
? "Error knowledge"
: $"{Loc.GetString("cp14-knowledge")}: {Loc.GetString(indexedKnowledge.Name)}";
}
public override EntityPrototype? GetRequirementEntityView(IPrototypeManager protoManager)
{
return null;
}
public override SpriteSpecifier? GetRequirementTexture(IPrototypeManager protoManager)
{
return new SpriteSpecifier.Texture(new("/Textures/Interface/students-cap.svg.192dpi.png"));
}
}

View File

@@ -0,0 +1,112 @@
/*
* This file is sublicensed under MIT License
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared._CP14.Material;
using Content.Shared.Materials;
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class MaterialResource : CP14WorkbenchCraftRequirement
{
[DataField(required: true)]
public ProtoId<MaterialPrototype> Material;
[DataField]
public int Count = 1;
public override bool CheckRequirement(EntityManager entManager, IPrototypeManager protoManager, HashSet<EntityUid> placedEntities, EntityUid user)
{
var count = 0;
foreach (var ent in placedEntities)
{
if (!entManager.TryGetComponent<CP14MaterialComponent>(ent, out var material))
continue;
entManager.TryGetComponent<StackComponent>(ent, out var stack);
foreach (var (key, value) in material.Materials)
{
if (key != Material)
continue;
if (stack is null)
{
count += value;
}
else
{
count += value * stack.Count;
}
}
}
if (count < Count)
return false;
return true;
}
public override void PostCraft(EntityManager entManager, HashSet<EntityUid> placedEntities, EntityUid user)
{
var stackSystem = entManager.System<SharedStackSystem>();
var requiredCount = Count;
foreach (var placedEntity in placedEntities)
{
if (!entManager.TryGetComponent<CP14MaterialComponent>(placedEntity, out var material))
continue;
entManager.TryGetComponent<StackComponent>(placedEntity, out var stack);
foreach (var mat in material.Materials)
{
if (mat.Key != Material)
continue;
if (stack is null)
{
var value = (int)MathF.Min(requiredCount, mat.Value);
requiredCount -= value;
entManager.DeleteEntity(placedEntity);
continue;
}
else
{
var materialValue = mat.Value * stack.Count;
var countToRemove = (int)MathF.Min(requiredCount, materialValue);
var newStackCount = (int)MathF.Ceiling((materialValue - countToRemove) / (float)mat.Value);
if (newStackCount <= 0)
entManager.DeleteEntity(placedEntity);
else
stackSystem.SetCount(placedEntity, newStackCount, stack);
requiredCount -= countToRemove;
}
}
}
}
public override string GetRequirementTitle(IPrototypeManager protoManager)
{
if (!protoManager.TryIndex(Material, out var indexedMaterial))
return "Error material";
return $"{Loc.GetString(indexedMaterial.Name)} x{Count}";
}
public override EntityPrototype? GetRequirementEntityView(IPrototypeManager protoManager)
{
return null;
}
public override SpriteSpecifier? GetRequirementTexture(IPrototypeManager protoManager)
{
return !protoManager.TryIndex(Material, out var indexedMaterial) ? null : indexedMaterial.Icon;
}
}

View File

@@ -0,0 +1,94 @@
/*
* This file is sublicensed under MIT License
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class ProtoIdResource : CP14WorkbenchCraftRequirement
{
[DataField(required: true)]
public EntProtoId ProtoId;
[DataField]
public int Count = 1;
public override bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
{
var indexedIngredients = IndexIngredients(entManager, placedEntities);
return indexedIngredients.TryGetValue(ProtoId, out var availableQuantity) && availableQuantity >= Count;
}
public override void PostCraft(EntityManager entManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
{
var requiredCount = Count;
foreach (var placedEntity in placedEntities)
{
if (!entManager.TryGetComponent<MetaDataComponent>(placedEntity, out var metaData))
continue;
if (metaData.EntityPrototype is null)
continue;
var placedProto = metaData.EntityPrototype.ID;
if (placedProto != ProtoId || requiredCount <= 0)
continue;
requiredCount--;
entManager.DeleteEntity(placedEntity);
}
}
public override string GetRequirementTitle(IPrototypeManager protoManager)
{
if (!protoManager.TryIndex(ProtoId, out var indexedProto))
return "Error entity";
return $"{indexedProto.Name} x{Count}";
}
public override EntityPrototype? GetRequirementEntityView(IPrototypeManager protoManager)
{
if (!protoManager.TryIndex(ProtoId, out var indexedProto))
return null;
return indexedProto;
}
public override SpriteSpecifier? GetRequirementTexture(IPrototypeManager protoManager)
{
return null;
}
private Dictionary<EntProtoId, int> IndexIngredients(EntityManager entManager, HashSet<EntityUid> ingredients)
{
var indexedIngredients = new Dictionary<EntProtoId, int>();
foreach (var ingredient in ingredients)
{
if (!entManager.TryGetComponent<MetaDataComponent>(ingredient, out var metaData))
continue;
var protoId = metaData.EntityPrototype?.ID;
if (protoId == null)
continue;
if (indexedIngredients.ContainsKey(protoId))
indexedIngredients[protoId]++;
else
indexedIngredients[protoId] = 1;
}
return indexedIngredients;
}
}

View File

@@ -0,0 +1,86 @@
/*
* This file is sublicensed under MIT License
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class StackResource : CP14WorkbenchCraftRequirement
{
[DataField(required: true)]
public ProtoId<StackPrototype> Stack;
[DataField]
public int Count = 1;
public override bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
{
var count = 0;
foreach (var ent in placedEntities)
{
if (!entManager.TryGetComponent<StackComponent>(ent, out var stack))
continue;
if (stack.StackTypeId != Stack)
continue;
count += stack.Count;
}
if (count < Count)
return false;
return true;
}
public override void PostCraft(EntityManager entManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
{
var stackSystem = entManager.System<SharedStackSystem>();
var requiredCount = Count;
foreach (var placedEntity in placedEntities)
{
if (!entManager.TryGetComponent<StackComponent>(placedEntity, out var stack))
continue;
if (stack.StackTypeId != Stack)
continue;
var count = (int)MathF.Min(requiredCount, stack.Count);
if (stack.Count - count <= 0)
entManager.DeleteEntity(placedEntity);
else
stackSystem.SetCount(placedEntity, stack.Count - count, stack);
requiredCount -= count;
}
}
public override string GetRequirementTitle(IPrototypeManager protoManager)
{
if (!protoManager.TryIndex(Stack, out var indexedStack))
return "Error stack";
return $"{Loc.GetString(indexedStack.Name)} x{Count}";
}
public override EntityPrototype? GetRequirementEntityView(IPrototypeManager protoManager)
{
return null;
}
public override SpriteSpecifier? GetRequirementTexture(IPrototypeManager protoManager)
{
return !protoManager.TryIndex(Stack, out var indexedStack) ? null : indexedStack.Icon;
}
}

View File

@@ -0,0 +1,46 @@
/*
* This file is sublicensed under MIT License
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Workbench;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract partial class CP14WorkbenchCraftRequirement
{
/// <summary>
/// Here a check is made that the recipe as a whole can be fulfilled at the current moment. Do not add anything that affects gameplay here, and only perform checks here.
/// </summary>
/// <returns></returns>
public abstract bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user);
/// <summary>
/// An event that is triggered after crafting. This is the place to put important things like removing items, spending stacks or other things.
/// </summary>
public abstract void PostCraft(EntityManager entManager,
HashSet<EntityUid> placedEntities,
EntityUid user);
/// <summary>
/// This text will be displayed in the description of the craft recipe. Write something like Wooden planks: х10 here
/// </summary>
public abstract string GetRequirementTitle(IPrototypeManager protoManager);
/// <summary>
/// You can specify an icon generated from an entity. It will support layering, colour changes and other layer options. Return null to disable.
/// </summary>
public abstract EntityPrototype? GetRequirementEntityView(IPrototypeManager protoManager);
/// <summary>
/// You can specify the texture directly. Return null to disable.
/// </summary>
public abstract SpriteSpecifier? GetRequirementTexture(IPrototypeManager protoManager);
}

View File

@@ -58,8 +58,12 @@
copyright: 'by egomassive of Freesound.org.'
source: "https://freesound.org/people/egomassive/sounds/536728/"
- files: ["ghost_ambi.ogg"]
license: "CC0-1.0"
copyright: 'by Litruv of Freesound.org.'
source: "https://freesound.org/people/Litruv/sounds/175944/"
- files: ["frog1.ogg, frog2.ogg, frog3.ogg"]
license: "CC-BY-4.0"
copyright: 'by iainmccurdy of Freesound.org. Cropped and mixed from stereo to mono.'
source: "https://freesound.org/people/iainmccurdy/sounds/743820/"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -15,9 +15,10 @@ max_connections = 100
[game]
hostname = "⚔️ CrystallEdge Alpha ⚔️ "
desc = "History of the City of Sword and Magic. A social economic sandbox reinventing the Space Station 14 concept in"
desc = "History of the City of Sword and Magic. A social economic sandbox reinventing the Space Station 14 concept in fantasy style"
lobbyenabled = true
soft_max_players = 40
lobbyduration = 300
[server]
rules_file = "CP14SandboxRU"
@@ -50,4 +51,11 @@ deadmin_on_join = true
flavor_text = true
[tips]
dataset = "CP14Tips"
dataset = "CP14Tips"
[ooc]
show_ooc_patron_color = false
enable_during_round = true
[cp14]
discord_auth_enabled = true

View File

@@ -0,0 +1,6 @@
cp14-discord-info = To play on the server you need to go to our server in discord to authorise your account.
cp14-discord-auth-quit-btn = Exit
cp14-discord-auth-title = Authorisation
cp14-discord-auth-link = https://discord.com/invite/Sud2DMfhCC
cp14-discord-auth-browser-btn = Discord Server
cp14-discord-auth-text = Authorize

View File

@@ -7,3 +7,6 @@ cp14-round-end-monolith-50 = The demiplane link crystal is half discharged.
cp14-round-end-monolith-discharged = You feel the connection to the other worlds begin to break.... The demiplane link crystal power will run out in { $time } { $units } if you don't charge it with mana completely...
cp14-round-end-monolith-recharged = Contact with the other worlds is being re-established. The demiplane link crystal is fully charged.
cp14-round-end = The demiplane link crystal was completely discharged. The connection to the other worlds has been severed. The "interesting" part of the story is over here....
cp14-demiplane-echoes = Echoes of voices

View File

@@ -0,0 +1,5 @@
queue-title = Queue
queue-quit = Disconnect
queue-position = Position
queue-total = Total
queue-priority-join = Priority join

View File

@@ -1 +1,2 @@
cp14-knowledge-info-title = Character knowledge
cp14-knowledge-info-title = Character knowledge
cp14-knowledge = Knowledge

View File

@@ -42,6 +42,15 @@ cp14-loadout-guard-spells = Guard's spells
cp14-loadout-bank-head = Bank employee hat
cp14-loadout-commandant-head = Commandant's hat
cp14-loadout-commandant-cloak = Commandant's cloak
cp14-loadout-banker-outer = Banker's waistcoat
cp14-loadout-bank-shirt = Bank Employee shirt
cp14-loadout-bank-pants = Bank Employee pants
cp14-loadout-bank-shoes = Bank Employee shoes
# Guildmaster
cp14-loadout-guildmaster-cloak = Guildmaster cloak
cp14-loadout-guildmaster-shirt = Guildmaster shirt
cp14-loadout-guildmaster-pants = Guildmaster pants
cp14-loadout-guildmaster-shoes = Guildmaster shoes
cp14-loadout-guildmaster-spells = Guildmaster spells

View File

@@ -14,9 +14,11 @@ cp14-lock-shape-tavern-dorm5 = tavern room №5
cp14-lock-shape-alchemist1 = alchemist's lab №1
cp14-lock-shape-alchemist2 = alchemist's lab №2
cp14-lock-shape-alchemist3 = alchemist's lab №3
cp14-lock-shape-blacksmith1 = forge №1
cp14-lock-shape-blacksmith2 = forge №2
cp14-lock-shape-blacksmith3 = forge №3
cp14-lock-shape-personalhouse1 = house №1
cp14-lock-shape-personalhouse2 = house №2
@@ -33,9 +35,12 @@ cp14-lock-shape-personalhouse12 = house №12
cp14-lock-shape-personalhouse13 = house №13
cp14-lock-shape-personalhouse14 = house №14
cp14-lock-shape-personalhouse15 = house №15
cp14-lock-shape-personalhouse16 = house №16
cp14-lock-shaper-guard-entrance = barracks, entrance
cp14-lock-shaper-guard-staff = barracks
cp14-lock-shaper-guard-commander = guardhouse
cp14-lock-shaper-guard-weapon-storage = weapons storage
cp14-lock-shape-guildmaster = guildmaster
cp14-lock-shape-guildmaster = guildmaster
cp14-lock-shape-demiplane-crystal = demiplane crystal

View File

@@ -0,0 +1 @@
cp14-kick-emote = makes a strong kick

View File

@@ -2,10 +2,9 @@ cp14-magic-type-fire = Fire
cp14-magic-type-water = Water
cp14-magic-type-earth = Earth
cp14-magic-type-healing = Healing
cp14-magic-type-light-darkness = Light and darkness
cp14-magic-type-light = Light
cp14-magic-type-darkness = Darkness
cp14-magic-type-meta = Metamagic
cp14-magic-type-gate = Gate
cp14-magic-type-movement = Movement
cp14-magic-type-necro = Necromancy
cp14-magic-manacost = Manacost

View File

@@ -1,3 +1,5 @@
cp14-material-examine = Consists of materials:
cp14-material-wooden-planks = wooden planks
cp14-material-dirt-block = dirt
cp14-material-stone-block = stone

View File

@@ -4,4 +4,7 @@ cp14-objective-town-send-title = Extract { $count } { $itemName }
cp14-objective-town-send-desc = Your task is to mine and ship { $count } { $itemName } to the city on a merchant ship.
cp14-objective-bank-earning-title = Accumulate in the vault{ $coins }
cp14-objective-bank-earning-desc = There must be at least{ $coins } in the bank vault. You can use any methods of earning money that do not violate the law.
cp14-objective-bank-earning-desc = There must be at least{ $coins } in the bank vault. You can use any methods of earning money that do not violate the law.
cp14-objective-no-demiplane-death-title = Prevent deaths in the demiplanes
cp14-objective-no-demiplane-death-desc = I need to control the work of the adventurers so they don't die in the demiplanes. No more {$max} deaths!

View File

@@ -3,4 +3,5 @@ cp14-stamp-salary = Intendant of the Guard
cp14-stamp-denied = Denied
cp14-stamp-approved = Approved
cp14-stamp-bank = Commandant
cp14-stamp-guard-commander = Guard commander
cp14-stamp-guard-commander = Guard commander
cp14-stamp-guildmaster = Guildmaster

View File

@@ -0,0 +1,4 @@
cp14-tracker-header = Round statistic:
cp14-tracker-demiplane-open = Demiplanes opened
cp14-tracker-demiplane-deaths = Players died in demiplanes

View File

@@ -1,2 +0,0 @@
cp14-world-edge-pre-remove-message = [color=red]CAUTION![/color] You are leaving the game zone! If you do not return within [color=red]{$second}[/color] seconds, you will be permanently removed from the round!
cp14-world-edge-cancel-removing-message = The exit round has been canceled.

View File

@@ -427,6 +427,9 @@ ent-CP14ClothingCloakGuardBlue = { ent-CP14ClothingCloakGuardBase }
ent-CP14ClothingCloakGuardCommander = бронированный плащ командира гвардии
.desc = Это чрезвычайно прочная и легкая накидка, разработанная специально для командиров Имперской гвардии.
ent-CP14ClothingCloakFurCoat = Меховая шуба
.desc = Тепло под дождем, тепло под снегом, тепло на ветру. Славно.
ent-CP14ClothingEyesMonocle = монокль
.desc = Аристократично и красиво.

View File

@@ -0,0 +1,6 @@
cp14-discord-info = Для игры на сервере вам необходимо пройти на наш сервер в дискорд для авторизации вашего аккаунта.
cp14-discord-auth-quit-btn = Выход
cp14-discord-auth-title = Авторизация
cp14-discord-auth-link = https://discord.com/invite/Sud2DMfhCC
cp14-discord-auth-browser-btn = Сервер Discord
cp14-discord-auth-text = Авторизоваться

View File

@@ -7,3 +7,6 @@ cp14-round-end-monolith-50 = Кристалл связи с демипланам
cp14-round-end-monolith-discharged = Вы чувствуете, что связь с другими мирами начинает рваться... Сила кристалла связи иссякнет через { $time } { $units }, если не запитать его маной полностью...
cp14-round-end-monolith-recharged = Связь с другими мирами восстанавливается. Энергия кристалла связи восстановлена.
cp14-round-end = Кристалл связи с демипланами окончательно разрядился. Связь с другими мирами оборвалась. "Интересная" часть этой истории окончена...
cp14-demiplane-echoes = Эхо голосов

View File

@@ -0,0 +1,5 @@
queue-title = Очередь
queue-quit = Отключиться
queue-position = Позиция
queue-total = Всего
queue-priority-join = Приоритетный вход

View File

@@ -1 +1,2 @@
cp14-knowledge-info-title = Знания персонажа
cp14-knowledge-info-title = Знания персонажа
cp14-knowledge = Знания

View File

@@ -44,6 +44,15 @@ cp14-loadout-guard-spells = Заклинания стражи
cp14-loadout-bank-head = Шляпа работника банка
cp14-loadout-commandant-head = Шляпа коменданта
cp14-loadout-commandant-cloak = Накидка коменданта
cp14-loadout-banker-outer = Жилет банкира
cp14-loadout-bank-shirt = Рубашка работника банка
cp14-loadout-bank-pants = Штаны работника банка
cp14-loadout-bank-shoes = Ботинки работника банка
# Guildmaster
cp14-loadout-guildmaster-cloak = Накидка гильдмастера
cp14-loadout-guildmaster-shirt = Рубашка гильдмастера
cp14-loadout-guildmaster-pants = Штаны гильдмастера
cp14-loadout-guildmaster-shoes = Ботинки гильдмастера
cp14-loadout-guildmaster-spells = Заклинания гильдмастера

View File

@@ -14,9 +14,11 @@ cp14-lock-shape-tavern-dorm5 = комната таверны №5
cp14-lock-shape-alchemist1 = лаборатория алхимика №1
cp14-lock-shape-alchemist2 = лаборатория алхимика №2
cp14-lock-shape-alchemist3 = лаборатория алхимика №3
cp14-lock-shape-blacksmith1 = кузня №1
cp14-lock-shape-blacksmith2 = кузня №2
cp14-lock-shape-blacksmith3 = кузня №3
cp14-lock-shape-personalhouse1 = дом №1
cp14-lock-shape-personalhouse2 = дом №2
@@ -33,9 +35,12 @@ cp14-lock-shape-personalhouse12 = дом №12
cp14-lock-shape-personalhouse13 = дом №13
cp14-lock-shape-personalhouse14 = дом №14
cp14-lock-shape-personalhouse15 = дом №15
cp14-lock-shape-personalhouse16 = дом №16
cp14-lock-shaper-guard-entrance = казармы, вход
cp14-lock-shaper-guard-staff = казармы
cp14-lock-shaper-guard-commander = дом главы стражи
cp14-lock-shaper-guard-weapon-storage = хранилище оружия
cp14-lock-shape-guildmaster = гильдмастер
cp14-lock-shape-guildmaster = гильдмастер
cp14-lock-shape-demiplane-crystal = кристалл демиплана

View File

@@ -0,0 +1 @@
cp14-kick-emote = совершает сильный пинок

View File

@@ -2,10 +2,9 @@ cp14-magic-type-fire = Огонь
cp14-magic-type-water = Вода
cp14-magic-type-earth = Земля
cp14-magic-type-healing = Исцеление
cp14-magic-type-light-darkness = Свет и тьма
cp14-magic-type-light = Свет
cp14-magic-type-darkness = Тьма
cp14-magic-type-meta = Метамагия
cp14-magic-type-gate = Пространство
cp14-magic-type-movement = Движение
cp14-magic-type-necro = Некромантия
cp14-magic-manacost = Затраты маны

View File

@@ -1,3 +1,5 @@
cp14-material-examine = Состоит из материалов:
cp14-material-wooden-planks = деревянные доски
cp14-material-dirt-block = земля
cp14-material-stone-block = камень

View File

@@ -4,4 +4,7 @@ cp14-objective-town-send-title = Добыть { $count } { $itemName }
cp14-objective-town-send-desc = Ваша задача - добыть и отправить { $count } { $itemName } в город на торговом корабле.
cp14-objective-bank-earning-title = Накопить в хранилище{ $coins }
cp14-objective-bank-earning-desc = В банковском хранилище должно находиться не меньше{ $coins }. Вы можете использовать любые методы заработка, не нарушающие закон.
cp14-objective-bank-earning-desc = В банковском хранилище должно находиться не меньше{ $coins }. Вы можете использовать любые методы заработка, не нарушающие закон.
cp14-objective-no-demiplane-death-title = Не допустить смертей в демипланах
cp14-objective-no-demiplane-death-desc = Мне нужно контролировать работу авантюристов, чтобы они не погибали в демипланах. Не больше {$max} смертей!

View File

@@ -3,4 +3,5 @@ cp14-stamp-salary = Интендант гвардии
cp14-stamp-denied = Отказано
cp14-stamp-approved = Утверждено
cp14-stamp-bank = Комендант
cp14-stamp-guard-commander = Командир стражи
cp14-stamp-guard-commander = Командир стражи
cp14-stamp-guildmaster = Гильдмастер

View File

@@ -0,0 +1,4 @@
cp14-tracker-header = Статистика раунда:
cp14-tracker-demiplane-open = Открыто демипланов
cp14-tracker-demiplane-deaths = Умерло игроков в демипланах

View File

@@ -1,2 +0,0 @@
cp14-world-edge-pre-remove-message = [color=red]ВНИМАНИЕ![/color] Вы покидаете игровую зону! Если вы не вернетесь назад в течении [color=red]{$second}[/color] секунд, вы будете окончательно удалены из раунда!
cp14-world-edge-cancel-removing-message = Выход из раунда отменен.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -140,8 +140,8 @@
sprite: _CP14/Objects/Materials/copper_bar.rsi
state: bar_3
price:
min: 40
max: 60
min: 200
max: 300
services:
- !type:CP14BuyItemsService
product:

View File

@@ -6,8 +6,8 @@
sprite: _CP14/Objects/Materials/copper_bar.rsi
state: bar_3
price:
min: 20
max: 30
min: 100
max: 150
service: !type:CP14SellStackService
stackId: CP14CopperBar
count: 10

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