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:
54
Content.Client/_CP14/Skill/CP14ClientSkillSystem.cs
Normal file
54
Content.Client/_CP14/Skill/CP14ClientSkillSystem.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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"/>
|
||||
218
Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml.cs
Normal file
218
Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user