Skill progression system [Draft] (#1056)

* fuck all

* clean up

* remove knowledge books

* Update base.yml

* cl

* fix #991

* fix

* Update subgamemodes.yml

* manual migration + recipes fix

* Update comoss.yml

* setup data

* setup skills systems

* more blacksmithing skills

* Update base.yml

* Create CP14SkillEffectAction.cs

* skill button returns

* base graph UI window

* skill tree drawing

* UI improve

* pyro setup

* skill trees selection, dragging control

* refactor skill system: rename Skills to LearnedSkills, add experience tracking and learning logic

* Create available.png

* skill description generation setup

* auto parsing skill names and descriptions

* Hydrosophistry

* water light fire and metamagic ported to skill tre

* ice dagger spell returns

* Update ice_dagger.yml

* delete old files

* Update modular_garde.yml

* ice arrow spell

* link graph with parallax

* show experience counter

* polish

* puf

* p

* pipap

* finally ready to merg?

* fix

* fix 2
This commit is contained in:
Ed
2025-03-27 17:20:20 +03:00
committed by GitHub
parent 6a6350a827
commit 1e36a61339
159 changed files with 2283 additions and 1863 deletions

View File

@@ -84,6 +84,9 @@ namespace Content.Client.Input
human.AddFunction(ContentKeyFunctions.Arcade1);
human.AddFunction(ContentKeyFunctions.Arcade2);
human.AddFunction(ContentKeyFunctions.Arcade3);
//CP14 Keys
human.AddFunction(ContentKeyFunctions.CP14OpenSkillMenu);
//CP14 Keys end
// actions should be common (for ghosts, mobs, etc)
common.AddFunction(ContentKeyFunctions.OpenActionsMenu);
@@ -123,10 +126,6 @@ namespace Content.Client.Input
common.AddFunction(ContentKeyFunctions.OpenDecalSpawnWindow);
common.AddFunction(ContentKeyFunctions.OpenAdminMenu);
common.AddFunction(ContentKeyFunctions.OpenGuidebook);
//CP14 Keys
human.AddFunction(ContentKeyFunctions.CP14OpenKnowledgeMenu);
//CP14 Keys end
}
}
}

View File

@@ -312,9 +312,8 @@ namespace Content.Client.Options.UI.Tabs
//CP14
AddHeader("ui-options-header-cp14");
AddButton(ContentKeyFunctions.CP14OpenKnowledgeMenu);
AddButton(ContentKeyFunctions.CP14OpenSkillMenu);
//CP14 end
foreach (var control in _keyControls.Values)
{
UpdateKeyControl(control);

View File

@@ -1,7 +1,9 @@
using System.Numerics;
using System.Numerics;
using Content.Client.Parallax.Data;
using Content.Client.Parallax.Managers;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
@@ -17,27 +19,50 @@ public sealed class ParallaxControl : Control
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private string _parallaxPrototype = "FastSpace";
[ViewVariables(VVAccess.ReadWrite)] public Vector2 Offset { get; set; }
[ViewVariables(VVAccess.ReadWrite)] public float SpeedX { get; set; } = 0.0f;
[ViewVariables(VVAccess.ReadWrite)] public float SpeedY { get; set; } = 0.0f;
[ViewVariables(VVAccess.ReadWrite)] public float ScaleX { get; set; } = 1.0f;
[ViewVariables(VVAccess.ReadWrite)] public float ScaleY { get; set; } = 1.0f;
[ViewVariables(VVAccess.ReadWrite)] public string ParallaxPrototype
{
get => _parallaxPrototype;
set
{
_parallaxPrototype = value;
_parallaxManager.LoadParallaxByName(value);
}
}
public ParallaxControl()
{
IoCManager.InjectDependencies(this);
Offset = new Vector2(_random.Next(0, 1000), _random.Next(0, 1000));
RectClipContent = true;
_parallaxManager.LoadParallaxByName("FastSpace");
_parallaxManager.LoadParallaxByName(_parallaxPrototype);
}
protected override void Draw(DrawingHandleScreen handle)
{
foreach (var layer in _parallaxManager.GetParallaxLayers("FastSpace"))
var currentTime = (float) _timing.RealTime.TotalSeconds;
var offset = Offset + new Vector2(currentTime * SpeedX, currentTime * SpeedY);
foreach (var layer in _parallaxManager.GetParallaxLayers(_parallaxPrototype))
{
var tex = layer.Texture;
var texSize = (tex.Size.X * (int) Size.X, tex.Size.Y * (int) Size.X) * layer.Config.Scale.Floored() / 1920;
var texSize = new Vector2i(
(int)(tex.Size.X * Size.X * layer.Config.Scale.X / 1920 * ScaleX),
(int)(tex.Size.Y * Size.X * layer.Config.Scale.Y / 1920 * ScaleY)
);
var ourSize = PixelSize;
var currentTime = (float) _timing.RealTime.TotalSeconds;
var offset = Offset + new Vector2(currentTime * 100f, currentTime * 0f);
//Protection from division by zero.
texSize.X = Math.Max(texSize.X, 1);
texSize.Y = Math.Max(texSize.Y, 1);
if (layer.Config.Tiled)
{

View File

@@ -24,7 +24,7 @@ public sealed class GameTopMenuBarUIController : UIController
[Dependency] private readonly SandboxUIController _sandbox = default!;
[Dependency] private readonly GuidebookUIController _guidebook = default!;
[Dependency] private readonly EmotesUIController _emotes = default!;
[Dependency] private readonly CP14KnowledgeUIController _knowledge = default!; //CP14
[Dependency] private readonly CP14SkillUIController _skill = default!; //CP14
private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull<GameTopMenuBar>();
@@ -48,7 +48,7 @@ public sealed class GameTopMenuBarUIController : UIController
_action.UnloadButton();
_sandbox.UnloadButton();
_emotes.UnloadButton();
_knowledge.UnloadButton(); //CP14
_skill.UnloadButton(); //CP14
}
public void LoadButtons()
@@ -62,6 +62,6 @@ public sealed class GameTopMenuBarUIController : UIController
_action.LoadButton();
_sandbox.LoadButton();
_emotes.LoadButton();
_knowledge.LoadButton(); //CP14
_skill.LoadButton(); //CP14
}
}

View File

@@ -35,11 +35,11 @@
/>
<!-- CP14 UI part -->
<ui:MenuButton
Name="CP14KnowledgeButton"
Name="CP14SkillButton"
Access="Internal"
Icon="{xe:Tex '/Textures/Interface/students-cap.svg.192dpi.png'}"
ToolTip="{Loc 'cp14-game-hud-open-knowledge-menu-button-tooltip'}"
BoundKey = "{x:Static is:ContentKeyFunctions.CP14OpenKnowledgeMenu}"
ToolTip="{Loc 'cp14-game-hud-open-skill-menu-button-tooltip'}"
BoundKey = "{x:Static is:ContentKeyFunctions.CP14OpenSkillMenu}"
MinSize="42 64"
HorizontalExpand="True"
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"

View File

@@ -1,43 +0,0 @@
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Knowledge.Events;
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Knowledge;
public sealed class ClientCP14KnowledgeSystem : SharedCP14KnowledgeSystem
{
[Dependency] private readonly IPlayerManager _players = default!;
public event Action<KnowledgeData>? OnKnowledgeUpdate;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<CP14KnowledgeInfoEvent>(OnCharacterKnowledgeEvent);
}
public void RequestKnowledgeInfo()
{
var entity = _players.LocalEntity;
if (entity is null)
return;
RaiseNetworkEvent(new CP14RequestKnowledgeInfoEvent(GetNetEntity(entity.Value)));
}
private void OnCharacterKnowledgeEvent(CP14KnowledgeInfoEvent msg, EntitySessionEventArgs args)
{
var entity = GetEntity(msg.NetEntity);
var data = new KnowledgeData(entity, msg.AllKnowledge);
OnKnowledgeUpdate?.Invoke(data);
}
public readonly record struct KnowledgeData(
EntityUid Entity,
HashSet<ProtoId<CP14KnowledgePrototype>> AllKnowledge
);
}

View File

@@ -0,0 +1,54 @@
using Content.Shared._CP14.Skill;
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Robust.Client.Player;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Skill;
public sealed partial class CP14ClientSkillSystem : CP14SharedSkillSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public event Action<EntityUid>? OnSkillUpdate;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14SkillStorageComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(Entity<CP14SkillStorageComponent> ent, ref AfterAutoHandleStateEvent args)
{
OnSkillUpdate?.Invoke(ent.Owner);
}
public void RequestSkillData()
{
var localPlayer = _playerManager.LocalEntity;
if (!HasComp<CP14SkillStorageComponent>(localPlayer))
return;
OnSkillUpdate?.Invoke(localPlayer.Value);
}
public void RequestLearnSkill(EntityUid? target, CP14SkillPrototype? skill)
{
if (skill == null || target == null)
return;
var netEv = new CP14TryLearnSkillMessage(GetNetEntity(target.Value), skill.ID);
RaiseNetworkEvent(netEv);
if (_proto.TryIndex(skill.Tree, out var indexedTree))
{
_audio.PlayGlobal(indexedTree.LearnSound, target.Value, AudioParams.Default.WithVolume(6f));
}
}
}

View File

@@ -0,0 +1,24 @@
<Control xmlns="https://spacestation14.io" HorizontalExpand="True">
<BoxContainer Name="MainContainer"
Orientation="Horizontal"
HorizontalExpand="True">
<Button Name="MainButton"
HorizontalExpand="True"
VerticalExpand="True"
StyleClasses="OpenRight"
Margin="0 0 -1 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
Margin="-5 0 0 0">
<Label Name="SkillTreeLabel" />
</BoxContainer>
</BoxContainer>
</Button>
<PanelContainer Name="ColorPanel"
VerticalExpand="True"
SetWidth="7"
Margin="0 1 0 0" />
</BoxContainer>
</Control>

View File

@@ -0,0 +1,22 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._CP14.Skill.Ui;
[GenerateTypedNameReferences]
public sealed partial class CP14SkillTreeButtonControl : Control
{
public event Action? OnPressed;
public CP14SkillTreeButtonControl(Color color, string label)
{
RobustXamlLoader.Load(this);
ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = color };
SkillTreeLabel.Text = label;
MainButton.OnPressed += args => OnPressed?.Invoke();
}
}

View File

@@ -0,0 +1,7 @@
<ui:CP14SkillTreeGraphControl
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._CP14.Skill.Ui"
xmlns:ui="clr-namespace:Content.Client._CP14.Skill.Ui"
HorizontalExpand="True"
VerticalExpand="True"
MouseFilter="Stop"/>

View File

@@ -0,0 +1,218 @@
using System.Linq;
using System.Numerics;
using Content.Shared._CP14.Skill;
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Skill.Ui;
[GenerateTypedNameReferences]
public sealed partial class CP14SkillTreeGraphControl : BoxContainer
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly CP14SharedSkillSystem _skillSystem;
private Entity<CP14SkillStorageComponent>? _player;
private IEnumerable<CP14SkillPrototype> _allSkills;
private CP14SkillPrototype? _hoveredNode;
private CP14SkillPrototype? _selectedNode;
public CP14SkillTreePrototype? Tree;
private bool dragging = false;
private Vector2 _previousMousePosition = Vector2.Zero;
private Vector2 _globalOffset = new (60,60);
private const float GridSize = 25f;
public event Action<CP14SkillPrototype?>? OnNodeSelected;
public event Action<Vector2>? OnOffsetChanged;
public CP14SkillTreeGraphControl()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_skillSystem = _entManager.System<CP14SharedSkillSystem>();
_allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>();
_proto.PrototypesReloaded += _ => _allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>().OrderBy(skill => _skillSystem.GetSkillName(skill)).ToList();
OnOffsetChanged?.Invoke(_globalOffset);
}
public void UpdateState(Entity<CP14SkillStorageComponent>? player)
{
_player = player;
OnOffsetChanged?.Invoke(_globalOffset);
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Handled)
return;
if (args.Function == EngineKeyFunctions.UIClick)
{
dragging = true;
if (_hoveredNode == null)
return;
OnNodeSelected?.Invoke(_hoveredNode);
UserInterfaceManager.ClickSound();
_selectedNode = _hoveredNode;
}
if (args.Function == EngineKeyFunctions.UIRightClick)
{
_globalOffset = new Vector2(60, 60); // Reset offset on right click
OnOffsetChanged?.Invoke(_globalOffset);
}
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (args.Handled || args.Function != EngineKeyFunctions.UIClick)
return;
dragging = false;
}
protected override void ExitedTree()
{
base.ExitedTree();
OnNodeSelected?.Invoke(null);
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
_hoveredNode = null;
if (_player == null || Tree == null)
{
return;
}
var cursor = (UserInterfaceManager.MousePositionScaled.Position * UIScale) - GlobalPixelPosition;
if (dragging)
{
var delta = cursor - _previousMousePosition;
_globalOffset += delta;
OnOffsetChanged?.Invoke(_globalOffset);
}
_previousMousePosition = cursor;
var playerSkills = _player.Value.Comp.LearnedSkills;
//Draw connection lines
foreach (var skill in _allSkills)
{
if (skill.Tree != Tree)
continue;
var fromPos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset;
foreach (var prerequisite in skill.Prerequisites)
{
if (!_proto.TryIndex(prerequisite, out var prerequisiteSkill))
continue;
if (prerequisiteSkill.Tree != Tree)
continue;
var learned = playerSkills.Contains(skill.ID);
var toPos = prerequisiteSkill.SkillUiPosition * GridSize * UIScale + _globalOffset;
var color = learned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f));
handle.DrawLine(fromPos, toPos, color);
}
}
//Draw skill icons over lines
foreach (var skill in _allSkills)
{
if (skill.Tree != Tree)
continue;
//TODO: Not optimized, recalculates every frame. Needs to be calculated on state update and cached.
var learned = playerSkills.Contains(skill.ID);
var available = _skillSystem.CanLearnSkill(_player.Value, skill.ID);
var canBeLearned = learned || skill.Prerequisites.All(prerequisite => playerSkills.Contains(prerequisite));
var pos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset;
// Base skill icon
var baseTexture = skill.Icon.Frame0();
var baseSize = new Vector2(baseTexture.Width, baseTexture.Height) * 2;
var baseQuad = new UIBox2(pos - baseSize / 2, pos + baseSize / 2);
var hovered = (cursor - pos).LengthSquared() <= (baseSize.X / 2) * (baseSize.X / 2);
// Frame
var frameTexture = Tree.FrameIcon.Frame0();
var frameSize = new Vector2(frameTexture.Width, frameTexture.Height) * 2;
var frameQuad = new UIBox2(pos - frameSize / 2, pos + frameSize / 2);
handle.DrawTextureRect(frameTexture, frameQuad, canBeLearned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)));
// Selected Skill
if (_selectedNode == skill)
{
var selectedTexture = Tree.SelectedIcon.Frame0();
var selectedSize = new Vector2(selectedTexture.Width, selectedTexture.Height) * 2;
var selectedQuad = new UIBox2(pos - selectedSize / 2, pos + selectedSize / 2);
handle.DrawTextureRect(selectedTexture, selectedQuad, Color.White);
}
// Hovered Skill
if (hovered)
{
_hoveredNode = skill;
var hoveredTexture = Tree.HoveredIcon.Frame0();
var hoveredSize = new Vector2(hoveredTexture.Width, hoveredTexture.Height) * 2;
var hoveredQuad = new UIBox2(pos - hoveredSize / 2, pos + hoveredSize / 2);
handle.DrawTextureRect(hoveredTexture, hoveredQuad, Color.White);
}
// Learned Skill
if (learned)
{
var learnedTexture = Tree.LearnedIcon.Frame0();
var learnedSize = new Vector2(learnedTexture.Width, learnedTexture.Height) * 2;
var learnedQuad = new UIBox2(pos - learnedSize / 2, pos + learnedSize / 2);
handle.DrawTextureRect(learnedTexture, learnedQuad, Color.White);
}
else if (available)
{
var availableTexture = Tree.AvailableIcon.Frame0();
var availableSize = new Vector2(availableTexture.Width, availableTexture.Height) * 2;
var availableQuad = new UIBox2(pos - availableSize / 2, pos + availableSize / 2);
handle.DrawTextureRect(availableTexture, availableQuad, Color.White);
}
var iconColor = Color.White;
if (!learned)
iconColor = Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f));
if (!canBeLearned && !learned)
iconColor = Color.FromSrgb(new Color(0f, 0f, 0f));
handle.DrawTextureRect(baseTexture, baseQuad, iconColor);
}
}
}

View File

@@ -1,158 +0,0 @@
using Content.Client._CP14.Knowledge;
using Content.Client._CP14.UserInterface.Systems.Knowledge.Windows;
using Content.Client.Gameplay;
using Content.Client.UserInterface.Controls;
using Content.Shared.Input;
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input.Binding;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Character;
[UsedImplicitly]
public sealed class CP14KnowledgeUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>,
IOnSystemChanged<ClientCP14KnowledgeSystem>
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[UISystemDependency] private readonly ClientCP14KnowledgeSystem _knowledge = default!;
private CP14KnowledgeWindow? _window;
private MenuButton? KnowledgeButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CP14KnowledgeButton;
public void OnStateEntered(GameplayState state)
{
DebugTools.Assert(_window == null);
_window = UIManager.CreateWindow<CP14KnowledgeWindow>();
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
CommandBinds.Builder
.Bind(ContentKeyFunctions.CP14OpenKnowledgeMenu,
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.Register<CP14KnowledgeUIController>();
}
public void OnStateExited(GameplayState state)
{
if (_window != null)
{
_window.Dispose();
_window = null;
}
CommandBinds.Unregister<CP14KnowledgeUIController>();
}
public void OnSystemLoaded(ClientCP14KnowledgeSystem system)
{
system.OnKnowledgeUpdate += KnowledgeUpdated;
_player.LocalPlayerDetached += CharacterDetached;
}
public void OnSystemUnloaded(ClientCP14KnowledgeSystem system)
{
system.OnKnowledgeUpdate -= KnowledgeUpdated;
_player.LocalPlayerDetached -= CharacterDetached;
}
public void UnloadButton()
{
if (KnowledgeButton is null)
return;
KnowledgeButton.OnPressed -= KnowledgeButtonPressed;
}
public void LoadButton()
{
if (KnowledgeButton is null)
return;
KnowledgeButton.OnPressed += KnowledgeButtonPressed;
if (_window is null)
return;
_window.OnClose += DeactivateButton;
_window.OnOpen += ActivateButton;
}
private void DeactivateButton()
{
KnowledgeButton!.Pressed = false;
}
private void ActivateButton()
{
KnowledgeButton!.Pressed = true;
}
private void KnowledgeUpdated(ClientCP14KnowledgeSystem.KnowledgeData data)
{
if (_window is null)
return;
_window.KnowledgeContent.RemoveAllChildren();
var (entity, allKnowledge) = data;
foreach (var knowledge in allKnowledge)
{
if (!_proto.TryIndex(knowledge, out var indexedKnowledge))
continue;
var knowledgeButton = new Button()
{
Access = AccessLevel.Public,
Text = Loc.GetString(indexedKnowledge.Name),
ToolTip = Loc.GetString(indexedKnowledge.Desc),
TextAlign = Label.AlignMode.Center,
};
_window.KnowledgeContent.AddChild(knowledgeButton);
}
}
private void CharacterDetached(EntityUid uid)
{
CloseWindow();
}
private void KnowledgeButtonPressed(BaseButton.ButtonEventArgs args)
{
ToggleWindow();
}
private void CloseWindow()
{
_window?.Close();
}
private void ToggleWindow()
{
if (_window == null)
return;
if (KnowledgeButton != null)
{
KnowledgeButton.SetClickPressed(!_window.IsOpen);
}
if (_window.IsOpen)
{
CloseWindow();
}
else
{
_knowledge.RequestKnowledgeInfo();
_window.Open();
}
}
}

View File

@@ -1,13 +0,0 @@
<windows:CP14KnowledgeWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:windows="clr-namespace:Content.Client._CP14.UserInterface.Systems.Knowledge.Windows"
Title="{Loc 'cp14-knowledge-info-title'}"
MinWidth="400"
MinHeight="200">
<ScrollContainer>
<BoxContainer Name="KnowledgeContent" Margin="5" Access="Public" Orientation="Vertical">
</BoxContainer>
</ScrollContainer>
</windows:CP14KnowledgeWindow>

View File

@@ -0,0 +1,249 @@
using System.Linq;
using System.Numerics;
using Content.Client._CP14.Skill;
using Content.Client._CP14.Skill.Ui;
using Content.Client._CP14.UserInterface.Systems.Skill.Window;
using Content.Client.Gameplay;
using Content.Client.UserInterface.Controls;
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.Input;
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.Input.Binding;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Character;
[UsedImplicitly]
public sealed class CP14SkillUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>,
IOnSystemChanged<CP14ClientSkillSystem>
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[UISystemDependency] private readonly CP14ClientSkillSystem _skill = default!;
private CP14SkillWindow? _window;
private CP14SkillPrototype? _selectedSkill;
private MenuButton? SkillButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CP14SkillButton;
public void OnStateEntered(GameplayState state)
{
DebugTools.Assert(_window == null);
_window = UIManager.CreateWindow<CP14SkillWindow>();
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
CommandBinds.Builder
.Bind(ContentKeyFunctions.CP14OpenSkillMenu,
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.Register<CP14SkillUIController>();
_window.LearnButton.OnPressed += _ => _skill.RequestLearnSkill(_player.LocalEntity, _selectedSkill);
_window.GraphControl.OnNodeSelected += SelectNode;
_window.GraphControl.OnOffsetChanged += offset =>
{
_window.ParallaxBackground.Offset = -offset * 0.25f + new Vector2(1000,1000); //hardcoding is bad
};
}
public void OnStateExited(GameplayState state)
{
if (_window != null)
{
_window.GraphControl.OnNodeSelected -= SelectNode;
_window.Dispose();
_window = null;
}
CommandBinds.Unregister<CP14SkillUIController>();
}
public void OnSystemLoaded(CP14ClientSkillSystem system)
{
system.OnSkillUpdate += UpdateState;
_player.LocalPlayerDetached += CharacterDetached;
}
public void OnSystemUnloaded(CP14ClientSkillSystem system)
{
system.OnSkillUpdate -= UpdateState;
_player.LocalPlayerDetached -= CharacterDetached;
}
public void UnloadButton()
{
if (SkillButton is null)
return;
SkillButton.OnPressed -= SkillButtonPressed;
}
public void LoadButton()
{
if (SkillButton is null)
return;
SkillButton.OnPressed += SkillButtonPressed;
if (_window is null)
return;
_window.OnClose += DeactivateButton;
_window.OnOpen += ActivateButton;
}
private void DeactivateButton()
{
SkillButton!.Pressed = false;
}
private void ActivateButton()
{
SkillButton!.Pressed = true;
}
private void SelectNode(CP14SkillPrototype? skill)
{
if (_window is null)
return;
if (_player.LocalEntity == null)
return;
_selectedSkill = skill;
if (skill == null)
{
_window.SkillName.Text = string.Empty;
_window.SkillDescription.Text = string.Empty;
_window.SkillView.Texture = null;
_window.LearnButton.Disabled = true;
}
else
{
_window.SkillName.Text = _skill.GetSkillName(skill);
_window.SkillDescription.SetMessage(GetSkillDescription(skill));
_window.SkillView.Texture = skill.Icon.Frame0();
_window.LearnButton.Disabled = !_skill.CanLearnSkill(_player.LocalEntity.Value, skill);
_window.SkillCost.Text = skill.LearnCost.ToString();
}
}
private FormattedMessage GetSkillDescription(CP14SkillPrototype skill)
{
var msg = new FormattedMessage();
//Description
msg.TryAddMarkup(_skill.GetSkillDescription(skill) + "\n", out _);
return msg;
}
private void UpdateState(EntityUid player)
{
if (_window is null)
return;
if (!EntityManager.TryGetComponent<CP14SkillStorageComponent>(player, out var storage))
return;
_window.GraphControl.UpdateState((player, storage));
// Reselect for update state
SelectNode(_selectedSkill);
//If tree not selected, select the first one
if (_window.GraphControl.Tree == null && storage.Progress.Count > 0)
{
var firstTree = storage.Progress.First().Key;
if (_proto.TryIndex(firstTree, out var indexedTree))
{
SelectTree(indexedTree, storage); // Set the first tree from the player's progress
}
}
// Update the experience points for the selected tree
var playerProgress = storage.Progress;
if (_window.GraphControl.Tree is not null && playerProgress.TryGetValue(_window.GraphControl.Tree, out var skillpoint))
{
_window.ExpPointLabel.Text = skillpoint.ToString();
}
_window.LevelLabel.Text = $"{storage.SkillsSumExperience}/{storage.ExperienceMaxCap}";
_window.TreeTabsContainer.RemoveAllChildren();
foreach (var (tree, progress) in storage.Progress)
{
if (!_proto.TryIndex(tree, out var indexedTree))
continue;
var treeButton2 = new CP14SkillTreeButtonControl(indexedTree.Color, Loc.GetString(indexedTree.Name));
treeButton2.ToolTip = Loc.GetString(indexedTree.Desc ?? string.Empty);
treeButton2.OnPressed += () =>
{
SelectTree(indexedTree, storage);
};
_window.TreeTabsContainer.AddChild(treeButton2);
}
}
private void SelectTree(CP14SkillTreePrototype tree, CP14SkillStorageComponent storage)
{
if (_window == null)
return;
_window.GraphControl.Tree = tree;
_window.ParallaxBackground.ParallaxPrototype = tree.Parallax;
_window.TreeName.Text = Loc.GetString(tree.Name);
var playerProgress = storage.Progress;
_window.ExpPointLabel.Text = playerProgress.TryGetValue(tree, out var skillpoint) ? skillpoint.ToString() : "0";
}
private void CharacterDetached(EntityUid uid)
{
CloseWindow();
}
private void SkillButtonPressed(BaseButton.ButtonEventArgs args)
{
ToggleWindow();
}
private void CloseWindow()
{
_window?.Close();
}
private void ToggleWindow()
{
if (_window == null)
return;
if (SkillButton != null)
{
SkillButton.SetClickPressed(!_window.IsOpen);
}
if (_window.IsOpen)
{
CloseWindow();
}
else
{
_skill.RequestSkillData();
_window.Open();
}
}
}

View File

@@ -0,0 +1,89 @@
<window:CP14SkillWindow
xmlns="https://spacestation14.io"
xmlns:window="clr-namespace:Content.Client._CP14.UserInterface.Systems.Skill.Window"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:parallax="clr-namespace:Content.Client.Parallax"
xmlns:ui1="clr-namespace:Content.Client._CP14.Skill.Ui"
Title="{Loc 'cp14-skill-info-title'}"
MinSize="700 350"
SetSize="980 550">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<!-- Selected Skill -->
<BoxContainer Margin="10 10 10 10" MaxWidth="240" SetWidth="240" Orientation="Vertical"
HorizontalExpand="False" VerticalExpand="True">
<!-- Skill View -->
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
<PanelContainer.PanelOverride>
<graphics:StyleBoxTexture Modulate="#1B1B1E" PatchMarginBottom="10" PatchMarginLeft="10"
PatchMarginRight="10" PatchMarginTop="10" />
</PanelContainer.PanelOverride>
<BoxContainer HorizontalExpand="True" VerticalExpand="True" >
<TextureRect Stretch="Scale" Name="SkillView" SetSize="64 64" HorizontalAlignment="Center" VerticalAlignment="Center" MinSize="64 64"
HorizontalExpand="True" VerticalExpand="True" Access="Public"/>
</BoxContainer>
</PanelContainer>
<customControls:HSeparator StyleClasses="HighDivider" Margin="0 15 0 10" />
<!-- Skill Data -->
<BoxContainer Name="NodeViewContainer" Orientation="Vertical" VerticalExpand="True">
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
<BoxContainer Name="InfoContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer HorizontalExpand="True">
<Label Name="SkillName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
HorizontalExpand="True" HorizontalAlignment="Center" />
</BoxContainer>
<!-- Skill Cost -->
<BoxContainer HorizontalExpand="True">
<RichTextLabel HorizontalExpand="True" Access="Public" Text="{Loc 'cp14-skill-menu-learncost'}" Margin="0 10 0 10" />
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Left" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
<RichTextLabel Name="SkillCost" Access="Public" Text="0"/>
</BoxContainer>
<!-- Skill Description -->
<BoxContainer HorizontalExpand="True">
<RichTextLabel Name="SkillDescription" HorizontalExpand="True" Access="Public"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</ScrollContainer>
<Control MinHeight="5"/>
<!-- Skill Buttons -->
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Button Name="LearnButton" Text="{Loc 'cp14-skill-menu-learn-button'}" StyleClasses="OpenRight" HorizontalExpand="True" MinHeight="35" Access="Public"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<customControls:VSeparator StyleClasses="LowDivider" />
<!-- Skills Tree -->
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer HorizontalExpand="True">
<Label Name="TreeName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
HorizontalExpand="True" HorizontalAlignment="Center" />
</BoxContainer>
<PanelContainer Margin="10 10 10 10" HorizontalExpand="True" VerticalExpand="True" RectClipContent="True">
<parallax:ParallaxControl Name="ParallaxBackground" ScaleX="4" ScaleY="4" Access="Public" SpeedX="10" SpeedY="5"/>
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<ui1:CP14SkillTreeGraphControl Name="GraphControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Access="Public"/>
</BoxContainer>
<!-- Tree Tabs -->
<BoxContainer Margin="10 10 10 10" VerticalAlignment="Top" HorizontalAlignment="Right" Orientation="Vertical" VerticalExpand="True">
<BoxContainer Name="TreeTabsContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Access="Public"/>
</BoxContainer>
<!-- Experience Data -->
<BoxContainer Margin="10 10 10 10" VerticalAlignment="Bottom" HorizontalAlignment="Center" Orientation="Horizontal" VerticalExpand="True">
<RichTextLabel VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Loc 'cp14-skill-menu-skillpoints'}" StyleClasses="LabelKeyText"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
<RichTextLabel Margin="0 0 20 0" Name="ExpPointLabel" VerticalAlignment="Center" HorizontalAlignment="Left" Text="0" StyleClasses="LabelKeyText" Access="Public"/>
<RichTextLabel VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Loc 'cp14-skill-menu-level'}" StyleClasses="LabelKeyText"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
<RichTextLabel Margin="0 0 0 0" Name="LevelLabel" VerticalAlignment="Center" HorizontalAlignment="Left" Text="0" StyleClasses="LabelKeyText" Access="Public"/>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</window:CP14SkillWindow>

View File

@@ -2,12 +2,12 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._CP14.UserInterface.Systems.Knowledge.Windows;
namespace Content.Client._CP14.UserInterface.Systems.Skill.Window;
[GenerateTypedNameReferences]
public sealed partial class CP14KnowledgeWindow : DefaultWindow
public sealed partial class CP14SkillWindow : DefaultWindow
{
public CP14KnowledgeWindow()
public CP14SkillWindow()
{
RobustXamlLoader.Load(this);
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared._CP14.Farming;
using Content.Shared._CP14.Knowledge;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;

View File

@@ -81,6 +81,9 @@ public sealed partial class CP14DemiplaneSystem
while (query.MoveNext(out var uid, out var stabilizer, out var xform))
{
if (!stabilizer.Enabled)
continue;
if (TryTeleportOutDemiplane(demiplane, uid))
{
if (!safe)

View File

@@ -1,71 +0,0 @@
using Content.Shared._CP14.Knowledge.Components;
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.Administration;
using Content.Shared.Administration.Managers;
using Content.Shared.Database;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Knowledge;
// TODO: Add UI
public sealed class CP14KnowledgeAdminUtilitiesSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminManager _admin = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly CP14KnowledgeSystem _knowledge = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14KnowledgeStorageComponent, GetVerbsEvent<Verb>>(AddKnowledgeAdminVerb);
}
private void AddKnowledgeAdminVerb(Entity<CP14KnowledgeStorageComponent> entity, ref GetVerbsEvent<Verb> args)
{
if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin))
return;
// Remove knowledge
foreach (var knowledge in entity.Comp.Knowledge)
{
if (!_prototype.TryIndex(knowledge, out var indexedKnowledge))
continue;
args.Verbs.Add(new Verb
{
Text = $"{Loc.GetString(indexedKnowledge.Name)}",
Message = Loc.GetString(indexedKnowledge.Desc),
Category = VerbCategory.CP14KnowledgeRemove,
Act = () =>
{
_knowledge.TryRemove(entity.Owner, knowledge);
},
Impact = LogImpact.High,
});
}
// Add knowledge
foreach (var knowledge in _prototype.EnumeratePrototypes<CP14KnowledgePrototype>())
{
// An entity with CP14AllKnowingComponent can't be taught anything,
// it already knows everything
if (_knowledge.HasKnowledge(entity.Owner, knowledge.ID))
continue;
args.Verbs.Add(new Verb
{
Text = $"{Loc.GetString(knowledge.Name)}",
Message = Loc.GetString(knowledge.Desc),
Category = VerbCategory.CP14KnowledgeAdd,
Act = () =>
{
_knowledge.TryAdd(entity.Owner, knowledge, true);
},
Impact = LogImpact.High,
});
}
}
}

View File

@@ -1,212 +0,0 @@
using System.Text;
using Content.Server.Chat.Managers;
using Content.Server.Mind;
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Knowledge.Components;
using Content.Shared._CP14.Knowledge.Events;
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.Administration.Logs;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Mind;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Knowledge;
public sealed class CP14KnowledgeSystem : SharedCP14KnowledgeSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14AutoAddKnowledgeComponent, MapInitEvent>(AutoAddSkill);
SubscribeLocalEvent<CP14KnowledgeStorageComponent, CP14KnowledgeLearnDoAfterEvent>(KnowledgeLearnedEvent);
SubscribeLocalEvent<CP14KnowledgeLearningSourceComponent, GetVerbsEvent<Verb>>(AddKnowledgeLearningVerb);
SubscribeNetworkEvent<CP14RequestKnowledgeInfoEvent>(OnRequestKnowledgeInfoEvent);
}
private void AddKnowledgeLearningVerb(Entity<CP14KnowledgeLearningSourceComponent> ent, ref GetVerbsEvent<Verb> args)
{
var user = args.User;
foreach (var knowledge in ent.Comp.Knowledge)
{
if (!_prototype.TryIndex(knowledge, out var indexedKnowledge))
continue;
args.Verbs.Add(new Verb
{
Text = $"{Loc.GetString(indexedKnowledge.Name)}",
Message = Loc.GetString(indexedKnowledge.Desc),
Category = VerbCategory.CP14KnowledgeLearn,
Act = () =>
{
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
user,
ent.Comp.DoAfter,
new CP14KnowledgeLearnDoAfterEvent { Knowledge = knowledge },
user,
ent,
ent)
{
BreakOnDamage = true,
BreakOnMove = true,
});
},
Disabled = HasKnowledge(user, knowledge),
Impact = LogImpact.Medium,
});
}
}
private void KnowledgeLearnedEvent(Entity<CP14KnowledgeStorageComponent> ent, ref CP14KnowledgeLearnDoAfterEvent args)
{
if (args.Cancelled || args.Handled)
return;
args.Handled = true;
TryAdd(ent.Owner, args.Knowledge);
}
private void AutoAddSkill(Entity<CP14AutoAddKnowledgeComponent> ent, ref MapInitEvent args)
{
foreach (var knowledge in ent.Comp.Knowledge)
{
TryAdd(ent.Owner, knowledge);
}
RemComp(ent, ent.Comp);
}
public bool TryAdd(Entity<CP14KnowledgeStorageComponent?> entity, ProtoId<CP14KnowledgePrototype> knowledgeId, bool force = false, bool silent = false)
{
if (!Resolve(entity, ref entity.Comp, false))
return false;
if (HasKnowledge(entity, knowledgeId))
return false;
if (!_prototype.TryIndex(knowledgeId, out var knowledge))
return false;
MindComponent? mindComponent;
foreach (var dependencyKnowledgeId in knowledge.Dependencies)
{
if (!_prototype.TryIndex(dependencyKnowledgeId, out var dependencyKnowledge))
return false;
// If we teach by force - we automatically teach all the basics that are necessary for that skill.
if (force && !TryAdd(entity, dependencyKnowledge, true))
return false;
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-cant-learn-knowledge-dependencies", ("target", Loc.GetString(knowledge.Desc))));
// We cant learnt
if (HasKnowledge(entity, dependencyKnowledge))
continue;
sb.Append($"\n- {Loc.GetString(dependencyKnowledge.Desc)}");
if (silent)
return false;
if (!_mind.TryGetMind(entity, out _, out mindComponent) || mindComponent.Session is null)
return false;
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", sb.ToString()));
_chat.ChatMessageToOne(
ChatChannel.Server,
sb.ToString(),
wrappedMessage,
default,
false,
mindComponent.Session.Channel);
return false;
}
if (!entity.Comp.Knowledge.Add(knowledgeId))
return false;
_adminLogger.Add(LogType.Mind, LogImpact.Medium, $"{EntityManager.ToPrettyString(entity):player} learned new knowledge: {Loc.GetString(knowledge.Name)}");
// TODO: coding on a sleepy head is a bad idea. Remove duplicate variables. >:(
if (silent)
return true;
if (!_mind.TryGetMind(entity, out _, out mindComponent) || mindComponent.Session is null)
return false;
var message = Loc.GetString("cp14-learned-new-knowledge", ("name", Loc.GetString(knowledge.Name)));
var wrappedMessage2 = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chat.ChatMessageToOne(
ChatChannel.Server,
message,
wrappedMessage2,
default,
false,
mindComponent.Session.Channel);
return true;
}
public bool TryRemove(Entity<CP14KnowledgeStorageComponent?> entity, ProtoId<CP14KnowledgePrototype> proto, bool silent = false)
{
if (!Resolve(entity, ref entity.Comp, false))
return false;
if (!entity.Comp.Knowledge.Contains(proto))
return false;
if (!_prototype.TryIndex(proto, out var indexedKnowledge))
return false;
if (!entity.Comp.Knowledge.Remove(proto))
return false;
_adminLogger.Add(LogType.Mind, LogImpact.Medium, $"{EntityManager.ToPrettyString(entity):player} forgot knowledge: {Loc.GetString(indexedKnowledge.Name)}");
if (silent)
return true;
if (!_mind.TryGetMind(entity, out _, out var mindComp) || mindComp.Session is null)
return true;
var message = Loc.GetString("cp14-forgot-knowledge", ("name", Loc.GetString(indexedKnowledge.Name)));
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chat.ChatMessageToOne(
ChatChannel.Server,
message,
wrappedMessage,
default,
false,
mindComp.Session.Channel);
return true;
}
private void OnRequestKnowledgeInfoEvent(CP14RequestKnowledgeInfoEvent msg, EntitySessionEventArgs args)
{
if (!args.SenderSession.AttachedEntity.HasValue || args.SenderSession.AttachedEntity != GetEntity(msg.NetEntity))
return;
var entity = args.SenderSession.AttachedEntity.Value;
if (!TryComp<CP14KnowledgeStorageComponent>(entity, out var knowledgeComp))
return;
RaiseNetworkEvent(new CP14KnowledgeInfoEvent(GetNetEntity(entity),knowledgeComp.Knowledge), args.SenderSession);
}
}

View File

@@ -1,27 +0,0 @@
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Knowledge.Specials;
/// <summary>
/// a component that can be hung on an entity to immediately teach it some skills
/// </summary>
[UsedImplicitly]
public sealed partial class CP14AddKnowledgeSpecial : JobSpecial
{
[DataField(required: true), ViewVariables(VVAccess.ReadOnly)]
public List<ProtoId<CP14KnowledgePrototype>> Knowledge = [];
public override void AfterEquip(EntityUid mob)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var knowledgeSystem = entMan.System<CP14KnowledgeSystem>();
foreach (var knowledge in Knowledge)
{
knowledgeSystem.TryAdd(mob, knowledge, true, true);
}
}
}

View File

@@ -1,10 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.MagicSpell;
[RegisterComponent]
public sealed partial class CP14AutoLearnActionComponent : Component
{
[DataField(required: true)]
public HashSet<EntProtoId> Actions = new();
}

View File

@@ -45,17 +45,6 @@ public sealed partial class CP14MagicSystem : CP14SharedMagicSystem
SubscribeLocalEvent<CP14MagicEffectManaCostComponent, CP14MagicEffectConsumeResourceEvent>(OnManaConsume);
SubscribeLocalEvent<CP14MagicEffectRequiredMusicToolComponent, CP14CastMagicEffectAttemptEvent>(OnMusicCheck);
SubscribeLocalEvent<CP14AutoLearnActionComponent, MapInitEvent>(OnAutoLearnAction);
}
private void OnAutoLearnAction(Entity<CP14AutoLearnActionComponent> ent, ref MapInitEvent args)
{
foreach (var action in ent.Comp.Actions)
{
_action.AddAction(ent, action);
}
RemCompDeferred<CP14AutoLearnActionComponent>(ent);
}
private void OnProjectileHit(Entity<CP14SpellEffectOnHitComponent> ent, ref ThrowDoHitEvent args)

View File

@@ -0,0 +1,24 @@
using Content.Shared._CP14.Skill;
using Content.Shared._CP14.Skill.Components;
namespace Content.Server._CP14.Skill;
public sealed partial class CP14SkillSystem : CP14SharedSkillSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<CP14TryLearnSkillMessage>(OnClientRequestLearnSkill);
}
private void OnClientRequestLearnSkill(CP14TryLearnSkillMessage ev, EntitySessionEventArgs args)
{
var entity = GetEntity(ev.Entity);
if (args.SenderSession.AttachedEntity != entity)
return;
TryLearnSkill(entity, ev.Skill);
}
}

View File

@@ -30,13 +30,22 @@ 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;
//}
var canCraft = true;
var hidden = false;
var entry = new CP14WorkbenchUiRecipesEntry(recipeId, CanCraftRecipe(indexedRecipe, placedEntities, user));
foreach (var requirement in indexedRecipe.Requirements)
{
if (!requirement.CheckRequirement(EntityManager, _proto, placedEntities, user, indexedRecipe))
{
canCraft = false;
hidden = requirement.HideRecipe;
}
}
if (hidden)
continue;
var entry = new CP14WorkbenchUiRecipesEntry(recipeId, canCraft);
recipes.Add(entry);
}

View File

@@ -6,7 +6,6 @@
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Stack;
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Workbench;
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.Chemistry.EntitySystems;
@@ -30,7 +29,6 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedCP14KnowledgeSystem _knowledge = default!;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<StackComponent> _stackQuery;
@@ -138,7 +136,7 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
{
foreach (var req in recipe.Requirements)
{
if (!req.CheckRequirement(EntityManager, _proto, entities, user))
if (!req.CheckRequirement(EntityManager, _proto, entities, user, recipe))
return false;
}

View File

@@ -118,7 +118,7 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction MappingOpenContextMenu = "MappingOpenContextMenu";
//CP14 keys
public static readonly BoundKeyFunction CP14OpenKnowledgeMenu = "CP14OpenKnowledgeMenu";
public static readonly BoundKeyFunction CP14OpenSkillMenu = "CP14OpenSkillMenu";
//CP14 keys end
}
}

View File

@@ -1,3 +1,5 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
@@ -50,4 +52,10 @@ public sealed partial class LoadoutPrototype : IPrototype, IEquipmentLoadout
/// </summary>
[DataField]
public List<EntProtoId> Actions { get; set; } = new();
/// <summary>
/// CP14 - it is possible to give skill trees to players who have taken this loadout
/// </summary>
[DataField]
public Dictionary<ProtoId<CP14SkillTreePrototype>, FixedPoint2> SkillTree = new();
}

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Content.Shared._CP14.Skill;
using Content.Shared.Actions;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
@@ -25,6 +26,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly SharedActionsSystem _action = default!; //CP14
[Dependency] private readonly CP14SharedSkillSystem _skill = default!; //CP14
private EntityQuery<HandsComponent> _handsQuery;
private EntityQuery<InventoryComponent> _inventoryQuery;
@@ -90,7 +92,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem
{
EquipStartingGear(entity, loadout.StartingGear, raiseEvent);
EquipStartingGear(entity, (IEquipmentLoadout) loadout, raiseEvent);
CP14EquipStartingActions(entity, loadout);
CP14EquipStartingActions(entity, loadout); //CP14
}
private void CP14EquipStartingActions(EntityUid entity, LoadoutPrototype loadout)
@@ -99,6 +101,11 @@ public abstract class SharedStationSpawningSystem : EntitySystem
{
_action.AddAction(entity, action);
}
foreach (var tree in loadout.SkillTree)
{
_skill.TryAddExperience(entity, tree.Key, tree.Value);
}
}
/// <summary>

View File

@@ -96,10 +96,10 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory CP14CurrencyConvert = new("cp14-verb-categories-currency-converter", null); //CP14
public static readonly VerbCategory CP14KnowledgeAdd = new("cp14-verb-categories-knowledge-add", null); //CP14
public static readonly VerbCategory CP14AdminSkillAdd =
new ("cp14-verb-categories-admin-skill-add", null, iconsOnly: true) { Columns = 6 }; //CP14
public static readonly VerbCategory CP14KnowledgeRemove = new("cp14-verb-categories-knowledge-remove", null); //CP14
public static readonly VerbCategory CP14KnowledgeLearn = new("cp14-verb-categories-knowledge-learn", null); //CP14
public static readonly VerbCategory CP14AdminSkillRemove =
new ("cp14-verb-categories-admin-skill-remove", null, iconsOnly: true) { Columns = 6 }; //CP14
}
}

View File

@@ -1,62 +0,0 @@
using System.Text;
using Content.Shared._CP14.Knowledge.Components;
using Content.Shared.Paper;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Knowledge;
public sealed class CP14KnowledgePaperTextSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly PaperSystem _paper = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14KnowledgePaperTextComponent, MapInitEvent>(OnPaperMapInit);
}
private void OnPaperMapInit(Entity<CP14KnowledgePaperTextComponent> ent, ref MapInitEvent args)
{
if (!TryComp<PaperComponent>(ent, out var paperComponent))
return;
var paper = new Entity<PaperComponent>(ent, paperComponent);
if (!TryComp<CP14KnowledgeLearningSourceComponent>(ent, out var knowledge))
return;
if (knowledge.Knowledge.Count == 0)
return;
var content = GenerateText(knowledge);
_paper.SetContent(paper, content);
paper.Comp.EditingDisabled = true;
// Yes we need to do synchronization after such changes,
// yes predict, but we need to
Dirty(paper);
}
private string GenerateText(CP14KnowledgeLearningSourceComponent source)
{
var stringBuilder = new StringBuilder();
// Header
stringBuilder.Append(Loc.GetString("cp14-knowledge-book-pre-text"));
// Main body
foreach (var prototypeId in source.Knowledge)
{
if (!_prototype.TryIndex(prototypeId, out var indexedKnowledge))
continue;
stringBuilder.Append($"\n{Loc.GetString(indexedKnowledge.Desc)}");
}
// Footer
stringBuilder.Append($"\n\n{Loc.GetString("cp14-knowledge-book-post-text")}");
return stringBuilder.ToString();
}
}

View File

@@ -1,6 +0,0 @@
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Knowledge.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class CP14AllKnowingComponent : Component;

View File

@@ -1,15 +0,0 @@
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Knowledge.Components;
/// <summary>
/// The ability to add a <see cref="CP14KnowledgePrototype"/> to an entity
/// and quickly teach it some skills.
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14KnowledgeSystem))]
public sealed partial class CP14AutoAddKnowledgeComponent : Component
{
[DataField]
public List<ProtoId<CP14KnowledgePrototype>> Knowledge = [];
}

View File

@@ -1,17 +0,0 @@
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Knowledge.Components;
/// <summary>
/// Allows new knowledge to be learnt through interactions with an object.
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14KnowledgeSystem))]
public sealed partial class CP14KnowledgeLearningSourceComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public HashSet<ProtoId<CP14KnowledgePrototype>> Knowledge { get; private set; } = [];
[DataField]
public TimeSpan DoAfter = TimeSpan.FromSeconds(5f);
}

View File

@@ -1,8 +0,0 @@
namespace Content.Shared._CP14.Knowledge.Components;
/// <summary>
/// Automatically generates content for PaperComponent,
/// based on the knowledge that can be learnt from this object.
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14KnowledgeSystem))]
public sealed partial class CP14KnowledgePaperTextComponent : Component;

View File

@@ -1,14 +0,0 @@
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Knowledge.Components;
/// <summary>
/// A list of <see cref="CP14KnowledgePrototype"/> learned by this entity.
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14KnowledgeSystem))]
public sealed partial class CP14KnowledgeStorageComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public HashSet<ProtoId<CP14KnowledgePrototype>> Knowledge { get; private set; } = [];
}

View File

@@ -1,18 +0,0 @@
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Knowledge.Events;
[Serializable, NetSerializable]
public sealed class CP14KnowledgeInfoEvent : EntityEventArgs
{
public readonly NetEntity NetEntity;
public readonly HashSet<ProtoId<CP14KnowledgePrototype>> AllKnowledge;
public CP14KnowledgeInfoEvent(NetEntity netEntity, HashSet<ProtoId<CP14KnowledgePrototype>> allKnowledge)
{
NetEntity = netEntity;
AllKnowledge = allKnowledge;
}
}

View File

@@ -1,17 +0,0 @@
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.DoAfter;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Knowledge.Events;
[Serializable, NetSerializable]
public sealed partial class CP14KnowledgeLearnDoAfterEvent : DoAfterEvent
{
public ProtoId<CP14KnowledgePrototype> Knowledge;
public override DoAfterEvent Clone()
{
return this;
}
}

View File

@@ -1,18 +0,0 @@
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Knowledge.Events;
public sealed class CP14KnowledgeUsedEvent : EntityEventArgs
{
public readonly EntityUid User;
public readonly ProtoId<CP14KnowledgePrototype> Knowledge;
public readonly float Factor;
public CP14KnowledgeUsedEvent(EntityUid uid, ProtoId<CP14KnowledgePrototype> knowledge, float factor)
{
User = uid;
Knowledge = knowledge;
Factor = factor;
}
}

View File

@@ -1,14 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Knowledge.Events;
[Serializable, NetSerializable]
public sealed class CP14RequestKnowledgeInfoEvent : EntityEventArgs
{
public readonly NetEntity NetEntity;
public CP14RequestKnowledgeInfoEvent(NetEntity netEntity)
{
NetEntity = netEntity;
}
}

View File

@@ -1,25 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Knowledge.Prototypes;
/// <summary>
/// Abstract knowledge that may be required to use items or crafts.
/// </summary>
[Prototype("CP14Knowledge")]
public sealed partial class CP14KnowledgePrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
[DataField(required: true)]
public LocId Name { get; private set; }
[DataField]
public LocId Desc { get; private set; }
/// <summary>
/// To study this knowledge, other knowledge on which it is based may be necessary.
/// </summary>
[DataField]
public HashSet<ProtoId<CP14KnowledgePrototype>> Dependencies = [];
}

View File

@@ -1,31 +0,0 @@
using Content.Shared._CP14.Knowledge.Components;
using Content.Shared._CP14.Knowledge.Events;
using Content.Shared._CP14.Knowledge.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Knowledge;
public abstract class SharedCP14KnowledgeSystem : EntitySystem
{
public bool HasKnowledge(Entity<CP14KnowledgeStorageComponent?> entity, ProtoId<CP14KnowledgePrototype> knowledge)
{
if (HasComp<CP14AllKnowingComponent>(entity))
return true;
return Resolve(entity, ref entity.Comp, false) && entity.Comp.Knowledge.Contains(knowledge);
}
public bool TryUseKnowledge(Entity<CP14KnowledgeStorageComponent?> entity, ProtoId<CP14KnowledgePrototype> knowledge, float factor = 1f)
{
if (!Resolve(entity, ref entity.Comp, false))
return false;
if (!entity.Comp.Knowledge.Contains(knowledge))
return false;
var ev = new CP14KnowledgeUsedEvent(entity, knowledge, factor);
RaiseLocalEvent(entity, ev);
return true;
}
}

View File

@@ -0,0 +1,230 @@
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill;
public abstract partial class CP14SharedSkillSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
InitializeAdmin();
}
/// <summary>
/// Directly adds the skill to the player, bypassing any checks.
/// </summary>
public bool TryAddSkill(EntityUid target,
ProtoId<CP14SkillPrototype> skill,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return false;
if (component.LearnedSkills.Contains(skill))
return false;
if (!_proto.TryIndex(skill, out var indexedSkill))
return false;
if (indexedSkill.Effect is not null)
{
indexedSkill.Effect.AddSkill(EntityManager, target);
}
component.SkillsSumExperience += indexedSkill.LearnCost;
component.LearnedSkills.Add(skill);
Dirty(target, component);
return true;
}
/// <summary>
/// Removes the skill from the player, bypassing any checks.
/// </summary>
public bool TryRemoveSkill(EntityUid target,
ProtoId<CP14SkillPrototype> skill,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return false;
if (!component.LearnedSkills.Remove(skill))
return false;
if (!_proto.TryIndex(skill, out var indexedSkill))
return false;
if (indexedSkill.Effect is not null)
{
indexedSkill.Effect.RemoveSkill(EntityManager, target);
}
component.SkillsSumExperience -= indexedSkill.LearnCost;
Dirty(target, component);
return true;
}
/// <summary>
/// Checks if the player has the skill.
/// </summary>
public bool HaveSkill(EntityUid target,
ProtoId<CP14SkillPrototype> skill,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return false;
return component.LearnedSkills.Contains(skill);
}
/// <summary>
/// Adds experience to the specified skill tree for the player.
/// </summary>
public bool TryAddExperience(EntityUid target,
ProtoId<CP14SkillTreePrototype> tree,
FixedPoint2 exp,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return false;
if (component.Progress.TryGetValue(tree, out var currentExp))
{
// If the tree already exists, add experience to it
component.Progress[tree] = currentExp + exp;
}
else
{
// If the tree doesn't exist, initialize it with the experience
component.Progress[tree] = exp;
}
Dirty(target, component);
return true;
}
/// <summary>
/// Removes experience from the specified skill tree for the player.
/// </summary>
public bool TryRemoveExperience(EntityUid target,
ProtoId<CP14SkillTreePrototype> tree,
FixedPoint2 exp,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return false;
if (!component.Progress.TryGetValue(tree, out var currentExp))
return false;
if (currentExp < exp)
return false;
component.Progress[tree] = FixedPoint2.Max(0, component.Progress[tree] - exp);
Dirty(target, component);
return true;
}
/// <summary>
/// Checks if the player can learn the specified skill.
/// </summary>
public bool CanLearnSkill(EntityUid target,
ProtoId<CP14SkillPrototype> skill,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return false;
if (!_proto.TryIndex(skill, out var indexedSkill))
return false;
//Already learned
if (HaveSkill(target, skill, component))
return false;
//Check max cap
if (component.SkillsSumExperience + indexedSkill.LearnCost >= component.ExperienceMaxCap)
return false;
//Prerequisite check
foreach (var prerequisite in indexedSkill.Prerequisites)
{
if (!HaveSkill(target, prerequisite, component))
return false;
}
//Experience check
if (!component.Progress.TryGetValue(indexedSkill.Tree, out var currentExp))
return false;
if (currentExp < indexedSkill.LearnCost)
return false;
return true;
}
/// <summary>
/// Tries to learn the specified skill for the player.
/// </summary>
public bool TryLearnSkill(EntityUid target,
ProtoId<CP14SkillPrototype> skill,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return false;
if (!_proto.TryIndex(skill, out var indexedSkill))
return false;
if (!CanLearnSkill(target, skill, component))
return false;
if (!TryRemoveExperience(target, indexedSkill.Tree, indexedSkill.LearnCost, component))
return false;
if (!TryAddSkill(target, skill, component))
return false;
return false;
}
/// <summary>
/// Helper function to get the skill name for a given skill prototype.
/// </summary>
public string GetSkillName(ProtoId<CP14SkillPrototype> skill)
{
if (!_proto.TryIndex(skill, out var indexedSkill))
return string.Empty;
if (indexedSkill.Name != null)
return Loc.GetString(indexedSkill.Name);
if (indexedSkill.Effect != null)
return indexedSkill.Effect.GetName(EntityManager, _proto) ?? string.Empty;
return string.Empty;
}
/// <summary>
/// Helper function to get the skill description for a given skill prototype.
/// </summary>
public string GetSkillDescription(ProtoId<CP14SkillPrototype> skill)
{
if (!_proto.TryIndex(skill, out var indexedSkill))
return string.Empty;
if (indexedSkill.Desc != null)
return Loc.GetString(indexedSkill.Desc);
if (indexedSkill.Effect != null)
return indexedSkill.Effect.GetDescription(EntityManager, _proto) ?? string.Empty;
return string.Empty;
}
}

View File

@@ -0,0 +1,67 @@
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.Administration;
using Content.Shared.Administration.Managers;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill;
public abstract partial class CP14SharedSkillSystem
{
[Dependency] private readonly ISharedAdminManager _admin = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private void InitializeAdmin()
{
SubscribeLocalEvent<CP14SkillStorageComponent, GetVerbsEvent<Verb>>(OnGetAdminVerbs);
}
private void OnGetAdminVerbs(Entity<CP14SkillStorageComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin))
return;
var target = args.Target;
//Add Skill
foreach (var skill in _proto.EnumeratePrototypes<CP14SkillPrototype>())
{
if (ent.Comp.LearnedSkills.Contains(skill))
continue;
var name = Loc.GetString(GetSkillName(skill));
args.Verbs.Add(new Verb
{
Text = name,
Message = name + ": " + Loc.GetString(GetSkillDescription(skill)),
Category = VerbCategory.CP14AdminSkillAdd,
Icon = skill.Icon,
Act = () =>
{
TryAddSkill(target, skill);
},
});
}
//Remove Skill
foreach (var skill in ent.Comp.LearnedSkills)
{
if (!_proto.TryIndex(skill, out var indexedSkill))
continue;
var name = Loc.GetString(GetSkillName(skill));
args.Verbs.Add(new Verb
{
Text = name,
Message = name + ": " + Loc.GetString(GetSkillDescription(skill)),
Category = VerbCategory.CP14AdminSkillRemove,
Icon = indexedSkill.Icon,
Act = () =>
{
TryRemoveSkill(target, skill);
},
});
}
}
}

View File

@@ -0,0 +1,48 @@
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.Bed.Sleep;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill;
public abstract partial class CP14SharedSkillSystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public void GiveExperienceInRadius(EntityCoordinates position, ProtoId<CP14SkillTreePrototype> tree, FixedPoint2 points, float radius = 5)
{
var entities = _lookup.GetEntitiesInRange<CP14SkillStorageComponent>(position, radius, LookupFlags.Uncontained);
foreach (var ent in entities)
{
//Cant learn if the position is not in range or obstructed
if (!_examine.InRangeUnOccluded(ent, position, radius))
continue;
//Cant learn when dead
if (TryComp<MobStateComponent>(ent, out var mobState) && !_mobState.IsAlive(ent, mobState))
continue;
//Cant learn if the entity is sleeping
if (HasComp<SleepingComponent>(ent))
continue;
TryAddExperience(ent, tree, points);
}
}
public void GiveExperienceInRadius(EntityUid uid,
ProtoId<CP14SkillTreePrototype> tree,
FixedPoint2 points,
float radius = 5)
{
GiveExperienceInRadius(Transform(uid).Coordinates, tree, points, radius);
}
}

View File

@@ -0,0 +1,46 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Skill.Components;
/// <summary>
/// Component that stores the skills learned by a player and their progress in the skill trees.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(CP14SharedSkillSystem))]
public sealed partial class CP14SkillStorageComponent : Component
{
[DataField, AutoNetworkedField]
public List<ProtoId<CP14SkillPrototype>> LearnedSkills = new();
/// <summary>
/// The number of experience points spent on skills. Technically this could be calculated via LearnedSkills, but this is a cached value for optimization.
/// </summary>
[DataField, AutoNetworkedField]
public FixedPoint2 SkillsSumExperience = 0;
/// <summary>
/// Keeps track of progress points in the knowledge areas available to the player. Important: The absence of a specific area means that the player CANNOT progress in that area.
/// </summary>
[DataField, AutoNetworkedField]
public Dictionary<ProtoId<CP14SkillTreePrototype>, FixedPoint2> Progress = new();
/// <summary>
/// The maximum ceiling of experience points that can be spent on learning skills. Not tied to a category.
/// </summary>
[DataField, AutoNetworkedField]
public FixedPoint2 ExperienceMaxCap = 10;
}
/// <summary>
/// Raised when a player attempts to learn a skill. This is sent from the client to the server.
/// </summary>
[Serializable, NetSerializable]
public sealed class CP14TryLearnSkillMessage(NetEntity entity, ProtoId<CP14SkillPrototype> skill) : EntityEventArgs
{
public readonly NetEntity Entity = entity;
public readonly ProtoId<CP14SkillPrototype> Skill = skill;
}

View File

@@ -0,0 +1,64 @@
using System.Numerics;
using Content.Shared._CP14.Skill.Specials;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Skill.Prototypes;
/// <summary>
/// A skill that can be learned by the player. Skills are grouped into trees, and each skill has a cost to learn, prerequisites, and an effect.
/// </summary>
[Prototype("cp14Skill")]
public sealed partial class CP14SkillPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
/// <summary>
/// Skill Title. If you leave null, the name will try to generate from Effect.GetName()
/// </summary>
[DataField]
public LocId? Name;
/// <summary>
/// Skill Description. If you leave null, the description will try to generate from Effect.GetDescription()
/// </summary>
[DataField]
public LocId? Desc;
/// <summary>
/// The tree this skill belongs to. This is used to group skills together in the UI.
/// </summary>
[DataField(required: true)]
public ProtoId<CP14SkillTreePrototype> Tree = default!;
/// <summary>
/// The cost to learn this skill. This is used to determine how many progress points are needed to learn the skill.
/// </summary>
[DataField]
public float LearnCost = 1f;
/// <summary>
/// Prerequisites for this skill. This is used to determine if the player can learn the skill. If the player does not have all the prerequisites, they cannot learn the skill.
/// </summary>
[DataField]
public HashSet<ProtoId<CP14SkillPrototype>> Prerequisites = new();
/// <summary>
/// Skill UI position. This is used to determine where the skill will be displayed in the skill tree UI.
/// </summary>
[DataField(required: true)]
public Vector2 SkillUiPosition = default!;
/// <summary>
/// Icon for the skill. This is used to display the skill in the skill tree UI.
/// </summary>
[DataField(required: true)]
public SpriteSpecifier Icon = default!;
/// <summary>
/// Skill effect. This is used to determine what happens when the player learns the skill. If you leave null, the skill will not have any effect.
/// But the presence of the skill itself can affect some systems that check for the presence of certain skills.
/// </summary>
[DataField]
public CP14SkillEffect? Effect;
}

View File

@@ -0,0 +1,44 @@
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Skill.Prototypes;
/// <summary>
/// A group of skills combined into one “branch”
/// </summary>
[Prototype("cp14SkillTree")]
public sealed partial class CP14SkillTreePrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
[DataField(required: true)]
public LocId Name;
[DataField]
public SpriteSpecifier FrameIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "frame");
[DataField]
public SpriteSpecifier HoveredIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "hovered");
[DataField]
public SpriteSpecifier SelectedIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "selected");
[DataField]
public SpriteSpecifier LearnedIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "learned");
[DataField]
public SpriteSpecifier AvailableIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "available");
[DataField]
public string Parallax = "AspidParallax";
[DataField]
public LocId? Desc;
[DataField]
public Color Color;
[DataField]
public SoundSpecifier LearnSound = new SoundCollectionSpecifier("CP14LearnSkill");
}

View File

@@ -0,0 +1,52 @@
using Content.Shared.Actions;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill.Specials;
public sealed partial class AddAction : CP14SkillEffect
{
[DataField(required: true)]
public EntProtoId Action;
public override void AddSkill(EntityManager entManager, EntityUid target)
{
var actionsSystem = entManager.System<SharedActionsSystem>();
actionsSystem.AddAction(target, Action);
}
public override void RemoveSkill(EntityManager entManager, EntityUid target)
{
var actionsSystem = entManager.System<SharedActionsSystem>();
foreach (var (uid, _) in actionsSystem.GetActions(target))
{
if (!entManager.TryGetComponent<MetaDataComponent>(uid, out var metaData))
continue;
if (metaData.EntityPrototype == null)
continue;
if (metaData.EntityPrototype != Action)
continue;
actionsSystem.RemoveAction(target, uid);
}
}
public override string? GetName(EntityManager entMagager, IPrototypeManager protoManager)
{
if (!protoManager.TryIndex(Action, out var indexedAction))
return String.Empty;
return indexedAction.Name;
}
public override string? GetDescription(EntityManager entMagager, IPrototypeManager protoManager)
{
if (!protoManager.TryIndex(Action, out var indexedAction))
return String.Empty;
return indexedAction.Description;
}
}

View File

@@ -0,0 +1,17 @@
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill.Specials;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract partial class CP14SkillEffect
{
public abstract void AddSkill(EntityManager entManager, EntityUid target);
public abstract void RemoveSkill(EntityManager entManager, EntityUid target);
public abstract string? GetName(EntityManager entMagager, IPrototypeManager protoManager);
public abstract string? GetDescription(EntityManager entMagager, IPrototypeManager protoManager);
}

View File

@@ -1,39 +1,46 @@
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared._CP14.Skill;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared._CP14.Workbench.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class KnowledgeRequired : CP14WorkbenchCraftRequirement
public sealed partial class SkillRequired : CP14WorkbenchCraftRequirement
{
/// <summary>
/// If the player does not have this knowledge, the recipe will not be displayed in the workbench.
/// </summary>
public override bool HideRecipe { get; set; } = true;
[DataField(required: true)]
public ProtoId<CP14KnowledgePrototype> Knowledge;
public List<ProtoId<CP14SkillPrototype>> Skills = new();
public override bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
EntityUid user,
CP14WorkbenchRecipePrototype recipe)
{
var knowledgeSystem = entManager.System<SharedCP14KnowledgeSystem>();
var knowledgeSystem = entManager.System<CP14SharedSkillSystem>();
return knowledgeSystem.HasKnowledge(user, Knowledge);
var haveAllSkills = true;
foreach (var skill in Skills)
{
if (!knowledgeSystem.HaveSkill(user, skill))
{
haveAllSkills = false;
break;
}
}
return haveAllSkills;
}
public override void PostCraft(EntityManager entManager, HashSet<EntityUid> placedEntities, EntityUid user)
{
var knowledgeSystem = entManager.System<SharedCP14KnowledgeSystem>();
knowledgeSystem.TryUseKnowledge(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)}";
return string.Empty;
}
public override EntityPrototype? GetRequirementEntityView(IPrototypeManager protoManager)
@@ -41,8 +48,8 @@ public sealed partial class KnowledgeRequired : CP14WorkbenchCraftRequirement
return null;
}
public override SpriteSpecifier GetRequirementTexture(IPrototypeManager protoManager)
public override SpriteSpecifier? GetRequirementTexture(IPrototypeManager protoManager)
{
return new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/students-cap.svg.192dpi.png"));
return null;
}
}

View File

@@ -4,6 +4,7 @@
*/
using Content.Shared._CP14.Material;
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.Materials;
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
@@ -13,13 +14,20 @@ namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class MaterialResource : CP14WorkbenchCraftRequirement
{
public override bool HideRecipe { get; set; } = false;
[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)
public override bool CheckRequirement(
EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user,
CP14WorkbenchRecipePrototype recipe)
{
var count = 0;
foreach (var ent in placedEntities)

View File

@@ -3,6 +3,7 @@
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared._CP14.Workbench.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -10,6 +11,8 @@ namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class ProtoIdResource : CP14WorkbenchCraftRequirement
{
public override bool HideRecipe { get; set; } = false;
[DataField(required: true)]
public EntProtoId ProtoId;
@@ -19,7 +22,8 @@ public sealed partial class ProtoIdResource : CP14WorkbenchCraftRequirement
public override bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
EntityUid user,
CP14WorkbenchRecipePrototype recipe)
{
var indexedIngredients = IndexIngredients(entManager, placedEntities);

View File

@@ -3,6 +3,7 @@
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -11,6 +12,8 @@ namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class StackResource : CP14WorkbenchCraftRequirement
{
public override bool HideRecipe { get; set; } = false;
[DataField(required: true)]
public ProtoId<StackPrototype> Stack;
@@ -20,7 +23,8 @@ public sealed partial class StackResource : CP14WorkbenchCraftRequirement
public override bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user)
EntityUid user,
CP14WorkbenchRecipePrototype recipe)
{
var count = 0;
foreach (var ent in placedEntities)

View File

@@ -3,6 +3,7 @@
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -11,6 +12,8 @@ namespace Content.Shared._CP14.Workbench.Requirements;
public sealed partial class TagResource : CP14WorkbenchCraftRequirement
{
public override bool HideRecipe { get; set; } = false;
[DataField(required: true)]
public ProtoId<TagPrototype> Tag;
@@ -23,7 +26,12 @@ public sealed partial class TagResource : CP14WorkbenchCraftRequirement
[DataField(required: true)]
public SpriteSpecifier? Texture;
public override bool CheckRequirement(EntityManager entManager, IPrototypeManager protoManager, HashSet<EntityUid> placedEntities, EntityUid user)
public override bool CheckRequirement(
EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user,
CP14WorkbenchRecipePrototype recipe)
{
var tagSystem = entManager.System<TagSystem>();

View File

@@ -3,6 +3,7 @@
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared._CP14.Workbench.Prototypes;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -13,6 +14,11 @@ namespace Content.Shared._CP14.Workbench;
[MeansImplicitUse]
public abstract partial class CP14WorkbenchCraftRequirement
{
/// <summary>
/// If true, failure to fulfill this condition will hide recipes from the possible craft workbench menu
/// </summary>
public abstract bool HideRecipe { get; set; }
/// <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>
@@ -20,7 +26,8 @@ public abstract partial class CP14WorkbenchCraftRequirement
public abstract bool CheckRequirement(EntityManager entManager,
IPrototypeManager protoManager,
HashSet<EntityUid> placedEntities,
EntityUid user);
EntityUid user,
CP14WorkbenchRecipePrototype recipe);
/// <summary>
/// An event that is triggered after crafting. This is the place to put important things like removing items, spending stacks or other things.

View File

@@ -72,3 +72,8 @@
license: "CC0-1.0"
copyright: 'by magnuswaker on Freesound.org'
source: "https://freesound.org/people/magnuswaker/sounds/563491/"
- files: ["skill_up1.ogg", "skill_up2.ogg", "skill_up3.ogg", "skill_up3.ogg"]
license: "CC0-1.0"
copyright: 'by IENBA on Freesound.org'
source: "https://freesound.org/people/IENBA/sounds/752275/"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -0,0 +1,2 @@
cp14-learned-new-knowledge = You learned how to create [bold]"{$name}[/bold]"!
cp14-forgot-knowledge = You forgot how to create [bold]"{$name}[/bold]"!

View File

@@ -1,18 +0,0 @@
cp14-skill-label = [bold]Skill requirements[/bold]
cp14-skill-examined = You need to have one of the following skills to use {$item}:
cp14-skill-examined-need-all = You need to have all of the following skills in order to use {$item}:
cp14-skill-examined-skill = [color={$color}] - {$skill} [/color]
cp14-verb-categories-knowledge-add = Add knowledge:
cp14-verb-categories-knowledge-remove = Delete knowledge:
cp14-verb-categories-knowledge-learn = Learn knowledge:
cp14-learned-new-knowledge = You have learned the knowledge of [bold]"{$name}[/bold]"!
cp14-forgot-knowledge = You've lost your knowledge of [bold]"{$name}[/bold]"!
cp14-cant-learn-knowledge-dependencies =
You were unable to understand {$target}...
You lack the following knowledge:
cp14-knowledge-book-pre-text = Here you will find detailed instructions that explain
cp14-knowledge-book-post-text = (To explore this knowledge, right-click on the book, and select Learn Knowledge)

View File

@@ -1,6 +0,0 @@
cp14-skill-issue-drops = The {$item} falls awkwardly out of your hands!
cp14-skill-issue-recoil = You don't hold the recoil!
cp14-skill-issue-self-harm = You're accidentally hitting on yourself!
cp14-skill-issue-sharp-self-harm = You accidentally cut yourself!
cp14-skill-issue-sharp-weapon-harm = You accidentally damaged the {$item}!
cp14-skill-issue-push = You accidentally drop {$item}!

View File

@@ -1,24 +0,0 @@
# T1
cp14-knowledge-sewing-name = Clothing sewing
cp14-knowledge-sewing-desc = how to make simple clothes on sewing table
cp14-knowledge-wallpaper-name = Wallpaper production
cp14-knowledge-wallpaper-desc = how to create wallpaper on sewing table
cp14-knowledge-woodwork-name = Woodwork
cp14-knowledge-woodwork-desc = how to make simple objects out of wood
cp14-knowledge-metall-melting-name = Metall melting
cp14-knowledge-metall-melting-desc = how to smelt ore into valuable metals
cp14-knowledge-metall-forging-name = Metall forging
cp14-knowledge-metall-forging-desc = how to forge metal into different shapes
cp14-knowledge-glasswork-name = Glasswork
cp14-knowledge-glasswork-desc = how to work with glass.
# T2
cp14-knowledge-sewing2-name = Advanced clothing sewing
cp14-knowledge-sewing2-desc = how to create quality clothes on sewing table

View File

@@ -12,6 +12,7 @@ cp14-loadout-general-shoes = Shoes
cp14-loadout-general-back = Back
cp14-loadout-general-trinkets = Trinkets
cp14-loadout-general-spells = Spells
cp14-loadout-skill-tree = Specialization
cp14-loadout-general-keys = Keys
# Apprentice

View File

@@ -0,0 +1,22 @@
cp14-skill-tree-blaksmithing-name = Blacksmithing
cp14-skill-tree-blaksmithing-desc = Explore and create new items from metal.
cp14-skill-tree-pyrokinetic-name = Pyrokinetic
cp14-skill-tree-pyrokinetic-desc = Master the magic of fire, allowing you to warm, illuminate, and destroy.
cp14-skill-tree-hydrosophistry-name = Hydrosophistry
cp14-skill-tree-hydrosophistry-desc = Master the magic of water and frost to create items from water and ice, and freeze enemies.
cp14-skill-tree-metamagic-name = Metamagic
cp14-skill-tree-metamagic-desc = Explore ways to subtly manipulate magic to affect spells and items.
cp14-skill-tree-illusion-name = Illusoriness
cp14-skill-tree-illusion-desc = Explore the nature of light to create illusions, light sources and shadows.
cp14-skill-tree-healing-name = Lifecation
cp14-skill-tree-healing-desc = Explore the ways in which magic affects living creatures.
# Body
cp14-skill-tree-atlethic-name = Atlethic
cp14-skill-tree-atlethic-desc = Develop your body by pushing the boundaries of what is available.

View File

@@ -0,0 +1,10 @@
cp14-verb-categories-admin-skill-add = Add skill
cp14-verb-categories-admin-skill-remove = Remove skill
cp14-skill-info-title = Skills
cp14-game-hud-open-skill-menu-button-tooltip = Skill tree
cp14-skill-menu-learn-button = Learn skill
cp14-skill-menu-learncost = [color=yellow]Points required:[/color]
cp14-skill-menu-skillpoints = Experience points:
cp14-skill-menu-level = Level:

View File

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

View File

@@ -0,0 +1,2 @@
cp14-learned-new-knowledge = Вы научились создавать [bold]"{$name}[/bold]"!
cp14-forgot-knowledge = Вы забыли как создавать [bold]"{$name}[/bold]"!

View File

@@ -1,18 +0,0 @@
cp14-skill-label = [bold]Требования к навыкам[/bold]
cp14-skill-examined = Вам нужно владеть одним из следующих навыков, чтобы использовать {$item}:
cp14-skill-examined-need-all = Вам нужно владеть всеми следующими навыками, чтобы использовать {$item}:
cp14-skill-examined-skill = [color={$color}] - {$skill} [/color]
cp14-verb-categories-knowledge-add = Научить знаниям:
cp14-verb-categories-knowledge-remove = Удалить знания:
cp14-verb-categories-knowledge-learn = Изучить знания:
cp14-learned-new-knowledge = Вы изучили знания о [bold]"{$name}[/bold]"!
cp14-forgot-knowledge = Вы потеряли свои знания о [bold]"{$name}[/bold]"!
cp14-cant-learn-knowledge-dependencies =
Вы не смогли понять {$target}...
Вам не хватает понимания:
cp14-knowledge-book-pre-text = Здесь подробно описаны инструкции, объясняющие
cp14-knowledge-book-post-text = (Чтобы изучить эти знания, нажмите ПКМ по книге, и выберите "Изучить знания")

View File

@@ -1,6 +0,0 @@
cp14-skill-issue-drops = {$item} неловко вываливается из ваших рук!
cp14-skill-issue-recoil = Вы не удерживаете отдачу!
cp14-skill-issue-self-harm = Вы случайно попадаете по себе!
cp14-skill-issue-sharp-self-harm = Вы случайно порезались!
cp14-skill-issue-sharp-weapon-harm = Вы случайно повредили {$item}!
cp14-skill-issue-push = Вы случайно роняете {$item}!

View File

@@ -1,24 +0,0 @@
# T1
cp14-knowledge-sewing-name = Шитье одежды
cp14-knowledge-sewing-desc = как создавать простую одежду на ткацком станке
cp14-knowledge-wallpaper-name = Изготовление обоев
cp14-knowledge-wallpaper-desc = как создавать обои на ткацком станке
cp14-knowledge-woodwork-name = Работа по дереву
cp14-knowledge-woodwork-desc = как создавать простые предметы из дерева
cp14-knowledge-metall-melting-name = Переплавка металлов
cp14-knowledge-metall-melting-desc = как переплавлять руду в ценные металлы
cp14-knowledge-metall-forging-name = Ковка металлов
cp14-knowledge-metall-forging-desc = как перековывать металл в различные формы
cp14-knowledge-glasswork-name = Работа с стеклом
cp14-knowledge-glasswork-desc = как работать со стеклом
# T2
cp14-knowledge-sewing2-name = Продвинутое шитье одежды
cp14-knowledge-sewing2-desc = как создавать качественную одежду на ткацком станке

View File

@@ -12,6 +12,7 @@ cp14-loadout-general-shoes = Обувь
cp14-loadout-general-back = Спина
cp14-loadout-general-trinkets = Безделушки
cp14-loadout-general-spells = Заклинания
cp14-loadout-skill-tree = Специализация
cp14-loadout-general-keys = Ключи
# Apprentice

View File

@@ -0,0 +1,24 @@
cp14-skill-tree-blaksmithing-name = Кузнечное дело
cp14-skill-tree-blaksmithing-desc = Исследуйте и создавайте новые предметы из металла.
# Magic
cp14-skill-tree-pyrokinetic-name = Пирокинетика
cp14-skill-tree-pyrokinetic-desc = Овладейте магией огня, позволяющей вам согревать, освещать и уничтожать.
cp14-skill-tree-hydrosophistry-name = Гидрософистика
cp14-skill-tree-hydrosophistry-desc = Овладейте магией воды и мороза, чтобы создавать предметы из воды и льда, и замораживать врагов.
cp14-skill-tree-metamagic-name = Метамагия
cp14-skill-tree-metamagic-desc = Исследуйте способы тонкой манипуляции магии, чтобы влиять на заклинания и предметы.
cp14-skill-tree-illusion-name = Иллюзорность
cp14-skill-tree-illusion-desc = Исследуйте природу света, чтобы создавать иллюзии, источники света и тени.
cp14-skill-tree-healing-name = Жизнетворение
cp14-skill-tree-healing-desc = Изучайте способы влияния магии на живые создания.
# Body
cp14-skill-tree-atlethic-name = Атлетика
cp14-skill-tree-atlethic-desc = Развивайте свое тело, расширяя границы доступного.

View File

@@ -0,0 +1,10 @@
cp14-verb-categories-admin-skill-add = Выдать навык
cp14-verb-categories-admin-skill-remove = Удалить навык
cp14-skill-info-title = Навыки
cp14-game-hud-open-skill-menu-button-tooltip = Деревья навыков
cp14-skill-menu-learn-button = Изучить навык
cp14-skill-menu-learncost = [color=yellow]Требуется очков:[/color]
cp14-skill-menu-skillpoints = Очков опыта:
cp14-skill-menu-level = Уровень:

View File

@@ -38457,14 +38457,6 @@ entities:
- type: Transform
pos: 6.771952,-30.18434
parent: 2
- proto: CP14BookKnowledgeWallpaperCraft
entities:
- uid: 7131
components:
- type: Transform
parent: 7130
- type: Physics
canCollide: False
- proto: CP14BookshelfWoodenFilled
entities:
- uid: 7133
@@ -81110,9 +81102,6 @@ entities:
parent: 2
- type: Storage
storedItems:
7131:
position: 0,0
_rotation: South
7132:
position: 1,0
_rotation: South
@@ -81122,7 +81111,6 @@ entities:
showEnts: False
occludes: True
ents:
- 7131
- 7132
- uid: 14258
components:

View File

@@ -329,27 +329,6 @@ entities:
- type: Transform
pos: -2.5,-9.5
parent: 2
- proto: CP14BookKnowledgeGlasswork
entities:
- uid: 67
components:
- type: Transform
pos: 1.7399222,-0.32284212
parent: 2
- proto: CP14BookKnowledgeMetallForging
entities:
- uid: 68
components:
- type: Transform
pos: 2.2238452,-0.34533072
parent: 2
- proto: CP14BookKnowledgeMetallMelting
entities:
- uid: 322
components:
- type: Transform
pos: 2.696513,-0.36781955
parent: 2
- proto: CP14WoodenChest
entities:
- uid: 205

View File

@@ -66852,13 +66852,6 @@ entities:
rot: 6.283185307179586 rad
pos: 36.98452,-65.53377
parent: 2
- proto: CP14BookKnowledgeClothingSewing
entities:
- uid: 14134
components:
- type: Transform
pos: -29.079868,-22.234352
parent: 2
- proto: CP14BowCombat
entities:
- uid: 17516

View File

@@ -4,7 +4,6 @@
name: admin observer
categories: [ HideSpawnMenu ]
components:
- type: CP14AllKnowing
- type: ContentEye
maxZoom: 8.916104, 8.916104
- type: Tag

View File

@@ -164,35 +164,6 @@
amount: 5
- id: CP14PenFeather
amount: 3
- type: storePositionBuy
id: CP14BookKnowledgeMetallMelting
code: KNOWLEDGE_MELTING
price: 100
factions:
- SpiceStream
service: !type:CP14BuyItemsService
product: CP14BookKnowledgeMetallMelting
- type: storePositionBuy
id: CP14BookKnowledgeMetallForging
code: KNOWLEDGE_FORGING
price: 100
factions:
- SpiceStream
service: !type:CP14BuyItemsService
product: CP14BookKnowledgeMetallForging
- type: storePositionBuy
id: CP14BookKnowledgeGlasswork
code: KNOWLEDGE_GLASSWORK
price: 100
factions:
- SpiceStream
service: !type:CP14BuyItemsService
product: CP14BookKnowledgeGlasswork
- type: storePositionBuy
id: EnergyCrystals

View File

@@ -0,0 +1,109 @@
- type: entity
id: CP14ActionSpellIceArrow
name: Ice arrow
description: You create a temporary sharp ice arrow that can be used as a disposable bow projectile.
components:
- type: Sprite
sprite: _CP14/Actions/Spells/water.rsi
state: ice_arrow
- type: CP14MagicEffectManaCost
manaCost: 20
- type: CP14MagicEffect
effects:
- !type:CP14SpellSpawnEntityOnTarget
spawns:
- CP14ImpactEffectWaterCreation
- !type:CP14SpellSpawnInHandEntity
spawns:
- CP14IceArrow
- type: CP14MagicEffectVerbalAspect
startSpeech: "Sagitta glaciei..."
endSpeech: "in manu mea"
- type: CP14MagicEffectSomaticAspect
- type: CP14MagicEffectCastingVisual
proto: CP14RuneWaterCreation
- type: InstantAction
itemIconStyle: BigAction
sound: !type:SoundPathSpecifier
path: /Audio/Magic/rumble.ogg
icon:
sprite: _CP14/Actions/Spells/water.rsi
state: ice_arrow
event: !type:CP14DelayedInstantActionEvent
cooldown: 5
breakOnMove: false
- type: entity
id: CP14IceArrow
parent: BaseItem
name: ice arrow
description: A sharp ice arrow created with magic. It melts and will soon disappear, but you can shoot it once with your bow.
categories: [ ForkFiltered ]
components:
- type: Sprite
sprite: _CP14/Objects/Weapons/Ranged/Projectiles/ice_arrow.rsi
state: icon
- type: Item
storedRotation: 0
- type: Fixtures
fixtures:
fix1:
shape: !type:PhysShapeCircle
radius: 0.2
density: 5
mask:
- ItemMask
restitution: 0.3
friction: 0.2
projectile:
shape:
!type:PhysShapeAabb
bounds: "-0.1,-0.1,0.1,0.1"
hard: false
mask:
- Impassable
- BulletImpassable
- type: EmbeddableProjectile
sound: /Audio/Weapons/star_hit.ogg
embedOnThrow: false
- type: ThrowingAngle
angle: 0
- type: Ammo
muzzleFlash: null
- type: Tag
tags:
- CP14Arrow
- type: Projectile
deleteOnCollide: false
onlyCollideWhenShot: true
damage:
types:
Piercing: 17
Cold: 5
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 10
behaviors:
- !type:PlaySoundBehavior
sound:
collection: GlassBreak
- !type:DoActsBehavior
acts: ["Destruction"]
- type: TimedDespawn
lifetime: 60
- type: DamageOnLand
damage:
types:
Blunt: 10
- type: entity
parent: CP14BaseSpellScrollWater
id: CP14SpellScrollIceArrow
name: ice arrow spell scroll
components:
- type: CP14SpellStorage
spells:
- CP14ActionSpellIceArrow

View File

@@ -0,0 +1,146 @@
- type: entity
id: CP14ActionSpellIceDagger
name: Ice dagger
description: You form an icy sharp dagger that will do for temporary use.
components:
- type: Sprite
sprite: _CP14/Actions/Spells/water.rsi
state: ice_dagger
- type: CP14MagicEffectManaCost
manaCost: 15
- type: CP14MagicEffect
effects:
- !type:CP14SpellSpawnEntityOnTarget
spawns:
- CP14ImpactEffectWaterCreation
- !type:CP14SpellSpawnInHandEntity
spawns:
- CP14IceDagger
- type: CP14MagicEffectVerbalAspect
startSpeech: "Un pittore sulla..."
endSpeech: "soglia della mano"
- type: CP14MagicEffectSomaticAspect
- type: CP14MagicEffectCastingVisual
proto: CP14RuneWaterCreation
- type: InstantAction
itemIconStyle: BigAction
sound: !type:SoundPathSpecifier
path: /Audio/Magic/rumble.ogg
icon:
sprite: _CP14/Actions/Spells/water.rsi
state: ice_dagger
event: !type:CP14DelayedInstantActionEvent
cooldown: 20
breakOnMove: false
- type: entity
name: ice dagger
parent: BaseItem
id: CP14IceDagger
description: A sharp ice arrow created with magic. It melts and will soon disappear, but you can shoot it once with your bow.
categories: [ ForkFiltered ]
components:
- type: Sharp
- type: Sprite
sprite: _CP14/Objects/Weapons/Melee/ice_dagger.rsi
state: icon
- type: MeleeWeapon
resetOnHandSelected: false #Fast swap
range: 1.0 # 1.5 standart
cPAnimationOffset: -0.75 #-1 standart
attackRate: 1.5 # 1 standart
angle: 45
wideAnimationRotation: 135
wideAnimation: CP14WeaponArcSlash
damage:
types:
Slash: 2
Piercing: 5
Cold: 3
soundHit:
collection: MetalThud
cPAnimationLength: 0.25
- type: Clothing
equipDelay: 0.25
unequipDelay: 0.25
quickEquip: false
breakOnMove: false
slots:
- belt
- type: CP14MeleeParriable
- type: Item
shape:
- 0,0,0,1
storedOffset: 0, 5
storedRotation: 45
size: Normal
- type: ExaminableDamage
messages: CP14WeaponMessages
- type: DisarmMalus
- type: ThrowingAngle
angle: 135
- type: EmbeddableProjectile
offset: -0.15,-0.15
removalTime: 0.5
- type: LandAtCursor
- type: DamageOtherOnHit
damage:
types:
Piercing: 10
Cold: 5
- type: DamageOnLand
damage:
types:
Blunt: 5
- type: Fixtures
fixtures:
fix1:
shape: !type:PolygonShape
vertices:
- -0.40,-0.30
- -0.30,-0.40
- 0.40,0.30
- 0.30,0.40
density: 10
mask:
- ItemMask
restitution: 0.3
friction: 0.2
- type: CP14SharpeningStone
- type: UseDelay
- type: Tool
qualities:
- Slicing
useSound:
path: /Audio/Items/Culinary/chop.ogg
- type: Utensil
types:
- Knife
- type: CP14WallpaperRemover
- type: CP14MeleeSelfDamage
damageToSelf:
types:
Blunt: 1 # 10 hits
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 10
behaviors:
- !type:PlaySoundBehavior
sound:
collection: GlassBreak
- !type:DoActsBehavior
acts: ["Destruction"]
- type: TimedDespawn
lifetime: 60
- type: entity
parent: CP14BaseSpellScrollWater
id: CP14SpellScrollIceDagger
name: ice dagger spell scroll
components:
- type: CP14SpellStorage
spells:
- CP14ActionSpellIceDagger

View File

@@ -19,8 +19,9 @@
- type: Loadout
prototypes:
- CP14MobSkeletonHalberd
- type: CP14AutoLearnAction
actions:
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionSpellKick
- type: entity
@@ -42,8 +43,9 @@
- type: Loadout
prototypes:
- CP14MobSkeletonDodger
- type: CP14AutoLearnAction
actions:
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionSpellSprint
- type: entity
@@ -68,8 +70,9 @@
- type: CP14MagicEnergyDraw
energy: 1
delay: 1
- type: CP14AutoLearnAction
actions:
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionSpellIceShards
- CP14ActionSpellFreeze
- CP14ActionSpellFlameCreation
@@ -84,8 +87,9 @@
- type: Loadout
prototypes:
- CP14MobSkeletonBard
- type: CP14AutoLearnAction
actions:
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionSpellHealBallade
- CP14ActionSpellMagicBallade
- CP14ActionSpellSpeedBallade

View File

@@ -231,6 +231,7 @@
types:
Cold: 0.25
Bloodloss: 0.25
- type: CP14SkillStorage
- type: entity
@@ -309,7 +310,6 @@
Asphyxiation: -1.0
- type: FireVisuals
alternateState: Standing #TODO - custom visuals
- type: CP14KnowledgeStorage
- type: entity
save: false

View File

@@ -31,10 +31,6 @@
- type: CP14MagicEnergyDraw #Half of standard mana regeneration
energy: 0.5
delay: 3
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionSpellManaConsumeElf
- type: Inventory
templateId: CP14Human
displacements:

View File

@@ -115,10 +115,6 @@
- type: CP14MagicEnergyDraw #Cool x3 mana regen!
energy: 1.5
delay: 3
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionSpellSprintGoblin
- type: Hands
handDisplacement:
sizeMaps:

View File

@@ -74,10 +74,6 @@
- id: CP14FoodMeatHuman # Replace it with silva meat, heh.
amount: 5
#- type: CP14MagicEnergyPhotosynthesis # Silva special feature #Disabled until sunlight fixed
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionSpellPlantGrowthSilva
#- type: CP14MagicEnergyDraw #Enabled default mana regen until sunlight fixed
# enable: false
- type: Body

View File

@@ -114,7 +114,6 @@
- type: Inventory
templateId: CP14Human
- type: TransferMindOnGib
- type: CP14KnowledgeStorage
- type: CP14DemiplaneStabilizer
enabled: false

View File

@@ -62,14 +62,4 @@
- type: GuideHelp
openOnActivation: true
guides:
- SS14
- type: entity
parent: CP14BookBase
id: CP14BookKnowledgeBase
abstract: true
categories: [ ForkFiltered ]
name: knowledge book
description: This book holds valuable knowledge that you can learn... if you're ready for it.
components:
- type: CP14KnowledgePaperText
- SS14

View File

@@ -1,68 +0,0 @@
# T0
- type: entity
parent: CP14BookKnowledgeBase
id: CP14BookKnowledgeWoodWork
suffix: Wood Work
components:
- type: CP14KnowledgeLearningSource
knowledge:
- WoodWork
- type: entity
parent: CP14BookKnowledgeBase
id: CP14BookKnowledgeMetallMelting
suffix: Metall Melting
components:
- type: CP14KnowledgeLearningSource
knowledge:
- MetallMelting
- type: entity
parent: CP14BookKnowledgeBase
id: CP14BookKnowledgeMetallForging
suffix: Metall Forging
components:
- type: CP14KnowledgeLearningSource
knowledge:
- MetallForging
- type: entity
parent: CP14BookKnowledgeBase
id: CP14BookKnowledgeGlasswork
suffix: Glasswork
components:
- type: CP14KnowledgeLearningSource
knowledge:
- Glasswork
# T1
- type: entity
parent: CP14BookKnowledgeBase
id: CP14BookKnowledgeClothingSewing
suffix: Clothing Sewing
components:
- type: CP14KnowledgeLearningSource
knowledge:
- ClothingSewing
- type: entity
parent: CP14BookKnowledgeBase
id: CP14BookKnowledgeWallpaperCraft
suffix: Wallpaper Craft
components:
- type: CP14KnowledgeLearningSource
knowledge:
- WallpaperCraft
# T2
- type: entity
parent: CP14BookKnowledgeBase
id: CP14BookKnowledgeAdvancedClothingSewing
suffix: Advanced Clothing Sewing
components:
- type: CP14KnowledgeLearningSource
knowledge:
- AdvancedClothingSewing

View File

@@ -96,17 +96,6 @@
- id: CP14BookRandom
amount: !type:RangeNumberSelector
range: 0, 15
#Random knowledge
- !type:GroupSelector
prob: 0.15
children:
- id: CP14BookKnowledgeWoodWork
- id: CP14BookKnowledgeMetallMelting
- id: CP14BookKnowledgeMetallForging
- id: CP14BookKnowledgeGlasswork
- id: CP14BookKnowledgeClothingSewing
- id: CP14BookKnowledgeWallpaperCraft
- id: CP14BookKnowledgeAdvancedClothingSewing
- type: entity
id: CP14BookshelfWoodenFilled

View File

@@ -20,8 +20,9 @@
- CP14MindRoleVampire
components:
- type: CP14Vampire
- type: CP14AutoLearnAction
actions:
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionVampireBite
- CP14ActionSpellVampireHypnosis
- CP14ActionSpellVampireBloodStep

View File

@@ -1,42 +0,0 @@
# T0
- type: CP14Knowledge
id: WoodWork
name: cp14-knowledge-woodwork-name
desc: cp14-knowledge-woodwork-desc
- type: CP14Knowledge
id: MetallMelting
name: cp14-knowledge-metall-melting-name
desc: cp14-knowledge-metall-melting-desc
- type: CP14Knowledge
id: MetallForging
name: cp14-knowledge-metall-forging-name
desc: cp14-knowledge-metall-forging-desc
- type: CP14Knowledge
id: Glasswork
name: cp14-knowledge-glasswork-name
desc: cp14-knowledge-glasswork-desc
# T1
- type: CP14Knowledge
id: ClothingSewing
name: cp14-knowledge-sewing-name
desc: cp14-knowledge-sewing-desc
- type: CP14Knowledge
id: WallpaperCraft
name: cp14-knowledge-wallpaper-name
desc: cp14-knowledge-wallpaper-desc
# T2
- type: CP14Knowledge
id: AdvancedClothingSewing
name: cp14-knowledge-sewing2-name
desc: cp14-knowledge-sewing2-desc
dependencies:
- ClothingSewing

View File

@@ -1,4 +0,0 @@
- type: roleLoadout
id: CP14VampireSpells
groups:
- CP14GeneralSpells

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