mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-14 19:29:57 +01:00
@@ -2,6 +2,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client._WL.Records; // WL-Records
|
||||
using Content.Client._WL.Skills.Ui; // WL-Skills
|
||||
using Content.Client.Electrocution;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
@@ -12,6 +13,7 @@ using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Sprite;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Shared._WL.Skills; // WL-Skills
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Corvax.CCCVars;
|
||||
@@ -132,6 +134,7 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
// One at a time.
|
||||
private LoadoutWindow? _loadoutWindow;
|
||||
private SkillsWindow? _skillsWindow; // WL-Skills
|
||||
|
||||
private TTSTab? _ttsTab; // Corvax-TTS
|
||||
|
||||
@@ -1135,6 +1138,13 @@ namespace Content.Client.Lobby.UI
|
||||
_loadoutWindow?.Dispose();
|
||||
}
|
||||
|
||||
// WL-Skills-start
|
||||
public void RefreshSkills()
|
||||
{
|
||||
_skillsWindow?.Dispose();
|
||||
}
|
||||
// WL-Skills-end
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the entire dummy entity for preview.
|
||||
/// </summary>
|
||||
@@ -1199,6 +1209,7 @@ namespace Content.Client.Lobby.UI
|
||||
RefreshAntags();
|
||||
RefreshJobs();
|
||||
RefreshLoadouts();
|
||||
RefreshSkills(); // WL-Skills
|
||||
RefreshSpecies();
|
||||
RefreshTraits();
|
||||
RefreshFlavorText();
|
||||
@@ -1423,6 +1434,17 @@ namespace Content.Client.Lobby.UI
|
||||
Margin = new Thickness(3f, 3f, 0f, 0f),
|
||||
};
|
||||
|
||||
// WL-Skills-start
|
||||
var skillsWindowBtn = new Button()
|
||||
{
|
||||
Text = Loc.GetString("skill-window"),
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Margin = new Thickness(3f, 3f, 0f, 0f),
|
||||
ToolTip = Loc.GetString("skill-window-tooltip")
|
||||
};
|
||||
// WL-Skills-end
|
||||
|
||||
var collection = IoCManager.Instance!;
|
||||
var protoManager = collection.Resolve<IPrototypeManager>();
|
||||
|
||||
@@ -1452,10 +1474,28 @@ namespace Content.Client.Lobby.UI
|
||||
};
|
||||
}
|
||||
|
||||
// WL-Skills-start
|
||||
skillsWindowBtn.OnPressed += args =>
|
||||
{
|
||||
OpenSkills(job);
|
||||
};
|
||||
// WL-Skills-end
|
||||
|
||||
_jobPriorities.Add((job.ID, subnameSelector, selector));
|
||||
jobContainer.AddChild(selector);
|
||||
jobContainer.AddChild(loadoutWindowBtn);
|
||||
|
||||
// WL-Skills-Edit-start
|
||||
var buttonsContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = HAlignment.Right
|
||||
};
|
||||
buttonsContainer.AddChild(loadoutWindowBtn);
|
||||
buttonsContainer.AddChild(skillsWindowBtn);
|
||||
|
||||
jobContainer.AddChild(buttonsContainer);
|
||||
category.AddChild(jobContainer);
|
||||
// WL-Skills-Edit-end
|
||||
}
|
||||
//WL-Changes-end
|
||||
}
|
||||
@@ -1523,6 +1563,61 @@ namespace Content.Client.Lobby.UI
|
||||
UpdateJobPriorities();
|
||||
}
|
||||
|
||||
// WL-Skills-start
|
||||
private void OpenSkills(JobPrototype? jobProto)
|
||||
{
|
||||
_skillsWindow?.Dispose();
|
||||
_skillsWindow = null;
|
||||
|
||||
if (jobProto == null || Profile == null)
|
||||
return;
|
||||
|
||||
JobOverride = jobProto;
|
||||
|
||||
var currentSkills = Profile.Skills.GetValueOrDefault(jobProto.ID, new Dictionary<byte, int>());
|
||||
var defaultSkills = jobProto.DefaultSkills.ToDictionary(
|
||||
kvp => (byte)kvp.Key,
|
||||
kvp => kvp.Value
|
||||
);
|
||||
|
||||
var bonusPoints = jobProto.BonusSkillPoints;
|
||||
var racialBonus = CalculateRacialBonus(Profile.Species, Profile.Age);
|
||||
var totalPoints = bonusPoints + racialBonus;
|
||||
|
||||
_skillsWindow = new SkillsWindow(jobProto.ID, currentSkills, defaultSkills, totalPoints);
|
||||
_skillsWindow.OnSkillChanged += (jobId, skillKey, newLevel) =>
|
||||
{
|
||||
Profile = Profile.WithSkill(jobId, skillKey, newLevel);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_skillsWindow.OnClose += () =>
|
||||
{
|
||||
JobOverride = null;
|
||||
ReloadPreview();
|
||||
};
|
||||
|
||||
_skillsWindow.OpenCenteredLeft();
|
||||
JobOverride = jobProto;
|
||||
ReloadPreview();
|
||||
}
|
||||
|
||||
private int CalculateRacialBonus(string species, int age)
|
||||
{
|
||||
var bonus = 0;
|
||||
foreach (var racialBonusProto in _prototypeManager.EnumeratePrototypes<RacialSkillBonusPrototype>())
|
||||
{
|
||||
if (racialBonusProto.Species != species)
|
||||
continue;
|
||||
|
||||
bonus = racialBonusProto.GetBonusForAge(age);
|
||||
break;
|
||||
}
|
||||
|
||||
return bonus;
|
||||
}
|
||||
// WL-Skills-end
|
||||
|
||||
private void OnFlavorTextChange(string content)
|
||||
{
|
||||
if (Profile is null)
|
||||
@@ -1604,6 +1699,8 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
_loadoutWindow?.Dispose();
|
||||
_loadoutWindow = null;
|
||||
_skillsWindow?.Dispose(); // WL-Skills
|
||||
_skillsWindow = null; // WL-Skills
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
@@ -1623,6 +1720,7 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
Profile = Profile?.WithAge(newAge);
|
||||
ReloadPreview();
|
||||
RefreshSkills(); // WL-Skills
|
||||
}
|
||||
|
||||
// WL-Height-Start
|
||||
@@ -1679,6 +1777,7 @@ namespace Content.Client.Lobby.UI
|
||||
RefreshJobs();
|
||||
// In case there's species restrictions for loadouts
|
||||
RefreshLoadouts();
|
||||
RefreshSkills(); // WL-Skills
|
||||
UpdateSexControls(); // update sex for new species
|
||||
UpdateSpeciesGuidebookIcon();
|
||||
ReloadPreview();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client._WL.Skills.Ui; // WL-Skills
|
||||
using Content.Client._WL.DynamicText.UI; // WL-Chages
|
||||
using Content.Client.CharacterInfo;
|
||||
using Content.Client.Gameplay;
|
||||
@@ -6,6 +8,8 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Character.Controls;
|
||||
using Content.Client.UserInterface.Systems.Character.Windows;
|
||||
using Content.Client.UserInterface.Systems.Objectives.Controls;
|
||||
using Content.Shared._WL.Skills; // WL-Skills
|
||||
using Content.Shared._WL.Skills.Components; // WL-Skills
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
@@ -19,7 +23,6 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using static Content.Client.CharacterInfo.CharacterInfoSystem;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
@@ -31,6 +34,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!; // WL-Skills
|
||||
|
||||
//WL-Changes-Start
|
||||
[Dependency] private readonly DynamicTextUIController _dynamicText = default!;
|
||||
@@ -47,6 +51,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
}
|
||||
|
||||
private CharacterWindow? _window;
|
||||
private SkillsWindow? _skillsWindow; // WL-Skills
|
||||
private MenuButton? CharacterButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CharacterButton;
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
@@ -60,6 +65,10 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
_window.OnOpen += ActivateButton;
|
||||
|
||||
//WL-Changes-Start
|
||||
_window.SkillsButton.OnPressed += OnSkillsButtonPressed;
|
||||
if (_player.LocalEntity.HasValue && _ent.HasComponent<SkillsComponent>(_player.LocalEntity))
|
||||
_window.SkillsButton.Disabled = false;
|
||||
|
||||
_window.DynamicTextButton.OnPressed += _ =>
|
||||
{
|
||||
_dynamicText.OpenWindow();
|
||||
@@ -267,4 +276,57 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
_window.Open();
|
||||
}
|
||||
}
|
||||
|
||||
// WL-Skills-start
|
||||
private void OnSkillsButtonPressed(ButtonEventArgs args)
|
||||
{
|
||||
OpenSkillsWindow();
|
||||
}
|
||||
|
||||
private void OpenSkillsWindow()
|
||||
{
|
||||
if (_player.LocalEntity is not { } entity)
|
||||
return;
|
||||
|
||||
if (_skillsWindow != null)
|
||||
{
|
||||
_skillsWindow.Close();
|
||||
_skillsWindow = null;
|
||||
}
|
||||
|
||||
var skillsSystem = _ent.System<SharedSkillsSystem>();
|
||||
if (!_ent.TryGetComponent<SkillsComponent>(entity, out var skillsComp))
|
||||
return;
|
||||
|
||||
var jobId = skillsComp.CurrentJob;
|
||||
var defaultSkills = skillsSystem.GetDefaultSkillsForJob(jobId);
|
||||
var totalPoints = skillsSystem.GetTotalPoints(entity, jobId, skillsComp);
|
||||
var currentSkills = skillsComp.Skills.ToDictionary(
|
||||
kvp => (byte)kvp.Key,
|
||||
kvp => kvp.Value
|
||||
);
|
||||
|
||||
var skillsWindow = new SkillsWindow(
|
||||
jobId ?? "unknown",
|
||||
currentSkills,
|
||||
defaultSkills,
|
||||
totalPoints,
|
||||
true
|
||||
);
|
||||
|
||||
_skillsWindow = skillsWindow;
|
||||
|
||||
skillsWindow.OnSkillChanged += (changedJobId, skillKey, newLevel) =>
|
||||
{
|
||||
if (_player.LocalEntity is not { } localEntity)
|
||||
return;
|
||||
|
||||
var skillType = (SkillType)skillKey;
|
||||
var ev = new SelectSkillPressedEvent(_ent.GetNetEntity(localEntity), skillType, newLevel, changedJobId);
|
||||
_entityNetworkManager.SendSystemNetworkMessage(ev);
|
||||
};
|
||||
|
||||
skillsWindow.OpenCentered();
|
||||
}
|
||||
// WL-Skills-end
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
<!--WL-Change-End-->
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- WL-Skills-start -->
|
||||
<Button Name="SkillsButton" Access="Public" Text="{Loc 'character-info-skills-button'}" Disabled="True" Margin="0 10 0 5" HorizontalAlignment="Center" MinWidth="200"/>
|
||||
<!-- WL-Skills-end -->
|
||||
<Label Name="ObjectivesLabel" Access="Public" Text="{Loc 'character-info-objectives-label'}" HorizontalAlignment="Center"/>
|
||||
<BoxContainer Orientation="Vertical" Name="Objectives" Access="Public"/>
|
||||
<cc:Placeholder Name="RolePlaceholder" Access="Public" PlaceholderText="{Loc 'character-info-roles-antagonist-text'}"/>
|
||||
|
||||
44
Content.Client/_WL/Administration/UI/SkillsAdminEui.cs
Normal file
44
Content.Client/_WL/Administration/UI/SkillsAdminEui.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared._WL.Skills.UI;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client._WL.Administration.UI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SkillsAdminEui : BaseEui
|
||||
{
|
||||
private readonly SkillsAdminWindow _window;
|
||||
|
||||
public SkillsAdminEui()
|
||||
{
|
||||
_window = new SkillsAdminWindow();
|
||||
|
||||
_window.OnClose += () => SendMessage(new SkillsAdminEuiClosedMessage());
|
||||
_window.OnSkillChanged += (skillKey, newLevel) =>
|
||||
SendMessage(new SkillsAdminEuiSkillChangedMessage(skillKey, newLevel));
|
||||
_window.OnPointsChanged += (newBonus) =>
|
||||
SendMessage(new SkillsAdminEuiPointsChangedMessage(newBonus));
|
||||
_window.OnResetAll += () => SendMessage(new SkillsAdminEuiResetMessage());
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
base.HandleState(state);
|
||||
|
||||
if (state is SkillsAdminEuiState adminState)
|
||||
{
|
||||
_window.UpdateState(adminState);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Content.Client/_WL/Administration/UI/SkillsAdminWindow.xaml
Normal file
74
Content.Client/_WL/Administration/UI/SkillsAdminWindow.xaml
Normal file
@@ -0,0 +1,74 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
MinSize="900 750"
|
||||
Title="{Loc 'skills-admin-window-title'}">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="10">
|
||||
|
||||
<!-- Header Info -->
|
||||
<PanelContainer Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#232323" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
Margin="10 5">
|
||||
<Label Text="{Loc 'skills-admin-target'}" Margin="10 0" />
|
||||
<Label Name="TargetNameLabel" HorizontalExpand="True" />
|
||||
<Label Text="{Loc 'skills-admin-job'}" />
|
||||
<Label Name="JobLabel" Margin="10 0" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Points Control -->
|
||||
<PanelContainer Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#232323" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical" Margin="10 5">
|
||||
<Label Text="{Loc 'skills-admin-points-control'}"
|
||||
FontColorOverride="Gold"
|
||||
HorizontalAlignment="Center" />
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 5">
|
||||
<Label Text="{Loc 'skills-admin-bonus-points'}" MinWidth="150" />
|
||||
<LineEdit Name="BonusPointsEdit"
|
||||
PlaceHolder="0"
|
||||
HorizontalExpand="True"
|
||||
Margin="5 0" />
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 5">
|
||||
<Label Text="{Loc 'skills-admin-spent-points'}" MinWidth="150" />
|
||||
<Label Name="SpentPointsLabel" Text="0" HorizontalExpand="True" Margin="5 0" />
|
||||
</BoxContainer>
|
||||
|
||||
<Button Name="ApplyPointsButton"
|
||||
Text="{Loc 'skills-admin-apply-points'}"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0 5" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Skills List -->
|
||||
<Label Text="{Loc 'skills-admin-skills-list'}"
|
||||
FontColorOverride="Gold"
|
||||
HorizontalAlignment="Center" />
|
||||
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
Name="SkillsContainer"
|
||||
HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 10">
|
||||
<Button Name="ResetButton"
|
||||
Text="{Loc 'skills-admin-reset-all'}"
|
||||
Margin="5 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -0,0 +1,94 @@
|
||||
using Content.Client._WL.Skills.Ui;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._WL.Skills;
|
||||
using Content.Shared._WL.Skills.UI;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._WL.Administration.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SkillsAdminWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
private readonly SharedSkillsSystem _skillsSystem;
|
||||
|
||||
public event Action<byte, int>? OnSkillChanged;
|
||||
public event Action<int>? OnPointsChanged;
|
||||
public event Action? OnResetAll;
|
||||
|
||||
private Dictionary<byte, int> _currentSkills = new();
|
||||
private int _bonusPoints;
|
||||
private int _spentPoints;
|
||||
|
||||
public SkillsAdminWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_skillsSystem = _entMan.System<SharedSkillsSystem>();
|
||||
|
||||
CloseButton.OnPressed += _ => Close();
|
||||
ApplyPointsButton.OnPressed += OnApplyPoints;
|
||||
ResetButton.OnPressed += _ => OnResetAll?.Invoke();
|
||||
}
|
||||
|
||||
public void UpdateState(SkillsAdminEuiState state)
|
||||
{
|
||||
TargetNameLabel.Text = state.EntityName;
|
||||
JobLabel.Text = state.CurrentJob;
|
||||
|
||||
_currentSkills = new Dictionary<byte, int>(state.CurrentSkills);
|
||||
_bonusPoints = state.BonusPoints;
|
||||
_spentPoints = state.SpentPoints;
|
||||
|
||||
UpdatePointsDisplay();
|
||||
PopulateSkills();
|
||||
}
|
||||
|
||||
private void UpdatePointsDisplay()
|
||||
{
|
||||
BonusPointsEdit.Text = _bonusPoints.ToString();
|
||||
SpentPointsLabel.Text = _spentPoints.ToString();
|
||||
}
|
||||
|
||||
private void PopulateSkills()
|
||||
{
|
||||
SkillsContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var skillType in Enum.GetValues<SkillType>())
|
||||
{
|
||||
var costs = _skillsSystem.GetSkillCost(skillType);
|
||||
var color = _skillsSystem.GetSkillColor(skillType);
|
||||
byte skillKey = (byte)skillType;
|
||||
|
||||
var currentLevel = _currentSkills.GetValueOrDefault(skillKey, 1);
|
||||
|
||||
var skillSelector = new SkillSelector(skillType, currentLevel, costs, color, 1)
|
||||
{
|
||||
Margin = new Thickness(0, 5),
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
skillSelector.IsLocked = false;
|
||||
skillSelector.UpdateAvailability(int.MaxValue, _skillsSystem);
|
||||
|
||||
skillSelector.OnSkillLevelChanged += (newLevel) =>
|
||||
{
|
||||
_currentSkills[skillKey] = newLevel;
|
||||
OnSkillChanged?.Invoke(skillKey, newLevel);
|
||||
};
|
||||
|
||||
SkillsContainer.AddChild(skillSelector);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplyPoints(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
if (int.TryParse(BonusPointsEdit.Text, out var newBonus))
|
||||
{
|
||||
OnPointsChanged?.Invoke(newBonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Content.Client/_WL/Skills/SkillsSystem.cs
Normal file
7
Content.Client/_WL/Skills/SkillsSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared._WL.Skills;
|
||||
|
||||
namespace Content.Client._WL.Skills;
|
||||
|
||||
public sealed class SkillsSystem : SharedSkillsSystem
|
||||
{
|
||||
}
|
||||
21
Content.Client/_WL/Skills/Ui/SkillSelector.xaml
Normal file
21
Content.Client/_WL/Skills/Ui/SkillSelector.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 2">
|
||||
<PanelContainer Name="ColorPanel" VerticalExpand="True" MinWidth="4" Margin="2 0 8 0"/>
|
||||
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="SkillNameLabel" MinWidth="250" VerticalAlignment="Center"/>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" HorizontalAlignment="Right">
|
||||
<Button Name="Level1Button" MinWidth="125" Margin="2 0"/>
|
||||
<Button Name="Level2Button" MinWidth="125" Margin="2 0"/>
|
||||
<Button Name="Level3Button" MinWidth="125" Margin="2 0"/>
|
||||
<Button Name="Level4Button" MinWidth="125" Margin="2 0"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Margin="5 2 0 2">
|
||||
<Label Name="SkillDescriptionLabel" StyleClasses="LabelSubText" />
|
||||
<Label Name="SkillCostLabel" StyleClasses="LabelSubText" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
207
Content.Client/_WL/Skills/Ui/SkillSelector.xaml.cs
Normal file
207
Content.Client/_WL/Skills/Ui/SkillSelector.xaml.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared._WL.Skills;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._WL.Skills.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SkillSelector : Control
|
||||
{
|
||||
public Action<int>? OnSkillLevelChanged;
|
||||
|
||||
private readonly int[] _costs;
|
||||
private int _currentLevel;
|
||||
private int _defaultLevel;
|
||||
private bool _isLocked;
|
||||
|
||||
private readonly bool _upgradeOnly;
|
||||
|
||||
public bool IsLocked
|
||||
{
|
||||
get => _isLocked;
|
||||
set
|
||||
{
|
||||
_isLocked = value;
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
|
||||
public SkillSelector(SkillType skillType, int currentLevel, int[] costs, Color skillColor,
|
||||
int defaultLevel = 1, bool upgradeOnly = false)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_currentLevel = currentLevel;
|
||||
_costs = costs;
|
||||
_defaultLevel = defaultLevel;
|
||||
|
||||
_upgradeOnly = upgradeOnly;
|
||||
|
||||
ColorPanel.PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = skillColor,
|
||||
ContentMarginTopOverride = 0,
|
||||
ContentMarginBottomOverride = 0,
|
||||
ContentMarginLeftOverride = 0,
|
||||
ContentMarginRightOverride = 0
|
||||
};
|
||||
|
||||
UpdateButtonTexts();
|
||||
|
||||
SkillNameLabel.Text = Loc.GetString($"skill-{skillType.ToString().ToLower()}");
|
||||
SkillDescriptionLabel.Text = Loc.GetString($"skill-{skillType.ToString().ToLower()}-desc");
|
||||
|
||||
UpdateUI();
|
||||
|
||||
Level1Button.OnPressed += _ => SetLevel(1);
|
||||
Level2Button.OnPressed += _ => SetLevel(2);
|
||||
Level3Button.OnPressed += _ => SetLevel(3);
|
||||
Level4Button.OnPressed += _ => SetLevel(4);
|
||||
|
||||
if (_upgradeOnly)
|
||||
{
|
||||
Level1Button.Disabled = true;
|
||||
Level2Button.Disabled = currentLevel <= 2;
|
||||
Level3Button.Disabled = currentLevel <= 3;
|
||||
Level4Button.Disabled = currentLevel <= 4;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateButtonTexts()
|
||||
{
|
||||
Level1Button.Text = Loc.GetString("skill-level-1") + $" ({GetDisplayCost(0)})";
|
||||
Level2Button.Text = Loc.GetString("skill-level-2") + $" ({GetDisplayCost(1)})";
|
||||
Level3Button.Text = Loc.GetString("skill-level-3") + $" ({GetDisplayCost(2)})";
|
||||
Level4Button.Text = Loc.GetString("skill-level-4") + $" ({GetDisplayCost(3)})";
|
||||
}
|
||||
|
||||
private string GetDisplayCost(int levelIndex)
|
||||
{
|
||||
var level = levelIndex + 1;
|
||||
if (level <= _defaultLevel)
|
||||
return "0";
|
||||
|
||||
return _costs[levelIndex].ToString();
|
||||
}
|
||||
|
||||
public void UpdateAvailability(int unspentPoints, SharedSkillsSystem skillsSystem)
|
||||
{
|
||||
if (_isLocked)
|
||||
return;
|
||||
|
||||
for (int targetLevel = 1; targetLevel <= 4; targetLevel++)
|
||||
{
|
||||
var button = GetLevelButton(targetLevel);
|
||||
if (button == null) continue;
|
||||
|
||||
if (targetLevel <= _currentLevel)
|
||||
{
|
||||
button.Disabled = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var upgradeCost = CalculateTotalUpgradeCost(_currentLevel, targetLevel);
|
||||
button.Disabled = unspentPoints < upgradeCost;
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateTotalUpgradeCost(int fromLevel, int toLevel)
|
||||
{
|
||||
var totalCost = 0;
|
||||
for (int level = fromLevel; level < toLevel; level++)
|
||||
{
|
||||
if (level >= _defaultLevel && level < _costs.Length)
|
||||
totalCost += _costs[level];
|
||||
}
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
public int GetPaidSpentCost()
|
||||
{
|
||||
if (_currentLevel <= _defaultLevel)
|
||||
return 0;
|
||||
|
||||
return CalculateTotalUpgradeCost(_defaultLevel, _currentLevel);
|
||||
}
|
||||
|
||||
private Button? GetLevelButton(int level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
1 => Level1Button,
|
||||
2 => Level2Button,
|
||||
3 => Level3Button,
|
||||
4 => Level4Button,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
UpdateLevelButtons();
|
||||
|
||||
Level1Button.Disabled = _isLocked || _currentLevel <= 1;
|
||||
Level2Button.Disabled = _isLocked || _currentLevel <= 2;
|
||||
Level3Button.Disabled = _isLocked || _currentLevel <= 3;
|
||||
Level4Button.Disabled = _isLocked || _currentLevel <= 4;
|
||||
|
||||
var paidSpent = GetPaidSpentCost();
|
||||
if (paidSpent > 0)
|
||||
{
|
||||
SkillCostLabel.Text = Loc.GetString("skill-total-cost", ("cost", paidSpent));
|
||||
}
|
||||
else if (_currentLevel > _defaultLevel)
|
||||
{
|
||||
SkillCostLabel.Text = Loc.GetString("skill-free-upgraded");
|
||||
}
|
||||
else
|
||||
{
|
||||
SkillCostLabel.Text = Loc.GetString("skill-free-default");
|
||||
}
|
||||
|
||||
if (_isLocked)
|
||||
{
|
||||
SkillNameLabel.Modulate = Color.Gray;
|
||||
SkillDescriptionLabel.Modulate = Color.Gray;
|
||||
SkillCostLabel.Modulate = Color.Gray;
|
||||
SkillCostLabel.Text = Loc.GetString("skill-locked-default");
|
||||
}
|
||||
else
|
||||
{
|
||||
SkillNameLabel.Modulate = Color.White;
|
||||
SkillDescriptionLabel.Modulate = Color.LightGray;
|
||||
SkillCostLabel.Modulate = Color.LightGray;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLevelButtons()
|
||||
{
|
||||
Level1Button.Pressed = _currentLevel >= 1;
|
||||
Level2Button.Pressed = _currentLevel >= 2;
|
||||
Level3Button.Pressed = _currentLevel >= 3;
|
||||
Level4Button.Pressed = _currentLevel >= 4;
|
||||
|
||||
Level1Button.StyleClasses.Add(StyleNano.ButtonOpenRight);
|
||||
Level2Button.StyleClasses.Add(StyleNano.ButtonOpenBoth);
|
||||
Level3Button.StyleClasses.Add(StyleNano.ButtonOpenBoth);
|
||||
Level4Button.StyleClasses.Add(StyleNano.ButtonOpenLeft);
|
||||
}
|
||||
|
||||
public void SetLevel(int level)
|
||||
{
|
||||
if (level == _currentLevel || _isLocked)
|
||||
return;
|
||||
|
||||
if (_upgradeOnly && level < _currentLevel)
|
||||
return;
|
||||
|
||||
_currentLevel = level;
|
||||
UpdateUI();
|
||||
OnSkillLevelChanged?.Invoke(level);
|
||||
}
|
||||
}
|
||||
50
Content.Client/_WL/Skills/Ui/SkillsEui.cs
Normal file
50
Content.Client/_WL/Skills/Ui/SkillsEui.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared._WL.Skills.UI;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client._WL.Skills.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SkillsEui : BaseEui
|
||||
{
|
||||
private readonly SkillsForcedWindow _window;
|
||||
|
||||
public SkillsEui()
|
||||
{
|
||||
_window = new SkillsForcedWindow();
|
||||
|
||||
_window.OnClose += () =>
|
||||
{
|
||||
SendMessage(new SkillsEuiClosedMessage());
|
||||
};
|
||||
|
||||
_window.OnSkillChanged += (jobId, skillKey, newLevel) =>
|
||||
{
|
||||
SendMessage(new SkillsEuiSkillChangedMessage(jobId, skillKey, newLevel));
|
||||
};
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
IoCManager.Resolve<IClyde>().RequestWindowAttention();
|
||||
_window.OpenCenteredAt(new Vector2(0.5f, 0.75f));
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
base.HandleState(state);
|
||||
|
||||
if (state is SkillsEuiState skillsState)
|
||||
{
|
||||
_window.UpdateState(skillsState);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Content.Client/_WL/Skills/Ui/SkillsForcedWindow.xaml
Normal file
10
Content.Client/_WL/Skills/Ui/SkillsForcedWindow.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="1000 800"
|
||||
Title="{Loc 'skills-forced-window-title'}">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
Name="SkillsContainer"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="10"/>
|
||||
</controls:FancyWindow>
|
||||
181
Content.Client/_WL/Skills/Ui/SkillsForcedWindow.xaml.cs
Normal file
181
Content.Client/_WL/Skills/Ui/SkillsForcedWindow.xaml.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._WL.Skills;
|
||||
using Content.Shared._WL.Skills.UI;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._WL.Skills.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SkillsForcedWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
private readonly SharedSkillsSystem _skillsSystem;
|
||||
|
||||
public event Action<string, byte, int>? OnSkillChanged;
|
||||
|
||||
private string _jobId = string.Empty;
|
||||
private Dictionary<byte, int> _currentSkills = new();
|
||||
private Dictionary<byte, int> _defaultSkills = new();
|
||||
private int _totalPoints;
|
||||
private int _spentPoints;
|
||||
|
||||
public SkillsForcedWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_skillsSystem = _entMan.System<SharedSkillsSystem>();
|
||||
}
|
||||
|
||||
public void UpdateState(SkillsEuiState state)
|
||||
{
|
||||
_jobId = state.JobId;
|
||||
_currentSkills = new Dictionary<byte, int>(state.CurrentSkills);
|
||||
_defaultSkills = new Dictionary<byte, int>(state.DefaultSkills);
|
||||
_totalPoints = state.TotalPoints;
|
||||
_spentPoints = state.SpentPoints;
|
||||
|
||||
UpdateWindow();
|
||||
}
|
||||
|
||||
private void UpdateWindow()
|
||||
{
|
||||
SkillsContainer.RemoveAllChildren();
|
||||
|
||||
var pointsLabel = new Label
|
||||
{
|
||||
Text = $"{_totalPoints - _spentPoints} / {_totalPoints}",
|
||||
HorizontalAlignment = Control.HAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
};
|
||||
SkillsContainer.AddChild(pointsLabel);
|
||||
|
||||
var warningLabel = new Label
|
||||
{
|
||||
Text = Loc.GetString("skills-forced-warning"),
|
||||
StyleClasses = { "LabelSubText" },
|
||||
HorizontalAlignment = Control.HAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 0, 20)
|
||||
};
|
||||
SkillsContainer.AddChild(warningLabel);
|
||||
|
||||
var scrollContainer = new ScrollContainer
|
||||
{
|
||||
VerticalExpand = true
|
||||
};
|
||||
var skillsList = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
scrollContainer.AddChild(skillsList);
|
||||
|
||||
PopulateSkillsList(skillsList);
|
||||
SkillsContainer.AddChild(scrollContainer);
|
||||
|
||||
var bottomContainer = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
HorizontalAlignment = Control.HAlignment.Center,
|
||||
Margin = new Thickness(0, 20, 0, 0)
|
||||
};
|
||||
|
||||
var confirmButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("skills-confirm-button"),
|
||||
HorizontalAlignment = Control.HAlignment.Center
|
||||
};
|
||||
|
||||
var warningContainer = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
HorizontalAlignment = Control.HAlignment.Center
|
||||
};
|
||||
|
||||
confirmButton.OnPressed += _ =>
|
||||
{
|
||||
if (_totalPoints - _spentPoints == 0)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
warningContainer.RemoveAllChildren();
|
||||
var warning = new Label
|
||||
{
|
||||
Text = Loc.GetString("skills-unspent-warning"),
|
||||
FontColorOverride = Color.Yellow,
|
||||
HorizontalAlignment = Control.HAlignment.Center
|
||||
};
|
||||
warningContainer.AddChild(warning);
|
||||
}
|
||||
};
|
||||
|
||||
bottomContainer.AddChild(confirmButton);
|
||||
bottomContainer.AddChild(warningContainer);
|
||||
SkillsContainer.AddChild(bottomContainer);
|
||||
}
|
||||
|
||||
private void PopulateSkillsList(BoxContainer skillsList)
|
||||
{
|
||||
var unspentPoints = _totalPoints - _spentPoints;
|
||||
foreach (var skillType in Enum.GetValues<SkillType>())
|
||||
{
|
||||
var costs = _skillsSystem.GetSkillCost(skillType);
|
||||
var color = _skillsSystem.GetSkillColor(skillType);
|
||||
byte skillKey = (byte)skillType;
|
||||
|
||||
var defaultLevel = _defaultSkills.GetValueOrDefault(skillKey, 1);
|
||||
var currentLevel = _currentSkills.GetValueOrDefault(skillKey, defaultLevel);
|
||||
|
||||
var skillSelector = new SkillSelector(skillType, currentLevel, costs, color, defaultLevel)
|
||||
{
|
||||
Margin = new Thickness(0, 5)
|
||||
};
|
||||
|
||||
skillSelector.IsLocked = defaultLevel == 4;
|
||||
|
||||
skillSelector.UpdateAvailability(unspentPoints, _skillsSystem);
|
||||
|
||||
skillSelector.OnSkillLevelChanged += (newLevel) =>
|
||||
{
|
||||
if (newLevel < defaultLevel)
|
||||
{
|
||||
skillSelector.SetLevel(currentLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
_currentSkills[skillKey] = newLevel;
|
||||
OnSkillChanged?.Invoke(_jobId, skillKey, newLevel);
|
||||
|
||||
_spentPoints = CalculateTotalSpentPoints();
|
||||
UpdateWindow();
|
||||
};
|
||||
|
||||
skillsList.AddChild(skillSelector);
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateTotalSpentPoints()
|
||||
{
|
||||
var totalSpent = 0;
|
||||
|
||||
foreach (var (skillKey, level) in _currentSkills)
|
||||
{
|
||||
var skillType = (SkillType)skillKey;
|
||||
var costs = _skillsSystem.GetSkillCost(skillType);
|
||||
var defaultLevel = _defaultSkills.GetValueOrDefault(skillKey, 1);
|
||||
|
||||
for (int currentLevel = defaultLevel; currentLevel < level; currentLevel++)
|
||||
{
|
||||
if (currentLevel < costs.Length)
|
||||
totalSpent += costs[currentLevel];
|
||||
}
|
||||
}
|
||||
|
||||
return totalSpent;
|
||||
}
|
||||
}
|
||||
12
Content.Client/_WL/Skills/Ui/SkillsWindow.xaml
Normal file
12
Content.Client/_WL/Skills/Ui/SkillsWindow.xaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="900 750" Title="{Loc 'skill-window'}" Margin="5 5">
|
||||
<BoxContainer Orientation="Vertical" Name="SkillsContainer" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 10" HorizontalAlignment="Center">
|
||||
<Label Text="{Loc 'skill-available-points'}" />
|
||||
<Label Name="PointsLabel" Text="0" />
|
||||
</BoxContainer>
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Name="SkillsList" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
151
Content.Client/_WL/Skills/Ui/SkillsWindow.xaml.cs
Normal file
151
Content.Client/_WL/Skills/Ui/SkillsWindow.xaml.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._WL.Skills;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._WL.Skills.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SkillsWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
private readonly SharedSkillsSystem _skillsSystem;
|
||||
|
||||
public event Action<string, byte, int>? OnSkillChanged;
|
||||
|
||||
private readonly string _jobId;
|
||||
private readonly Dictionary<byte, int> _currentSkills;
|
||||
private readonly Dictionary<byte, int> _defaultSkills;
|
||||
private readonly bool _upgradeOnly;
|
||||
private readonly int _totalPoints;
|
||||
private int _spentPoints;
|
||||
|
||||
private readonly Color _rowColor1 = Color.FromHex("#1B1B1E");
|
||||
private readonly Color _rowColor2 = Color.FromHex("#202025");
|
||||
private int _rowCount = 0;
|
||||
|
||||
public SkillsWindow(
|
||||
string jobId,
|
||||
Dictionary<byte, int> currentSkills,
|
||||
Dictionary<byte, int> defaultSkills,
|
||||
int totalPoints, bool upgradeOnly = false)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_skillsSystem = _entMan.System<SharedSkillsSystem>();
|
||||
|
||||
_jobId = jobId;
|
||||
_currentSkills = new Dictionary<byte, int>(currentSkills);
|
||||
_defaultSkills = new Dictionary<byte, int>(defaultSkills);
|
||||
_upgradeOnly = upgradeOnly;
|
||||
_totalPoints = totalPoints;
|
||||
_spentPoints = 0;
|
||||
|
||||
PopulateSkills();
|
||||
UpdatePoints();
|
||||
}
|
||||
|
||||
private void PopulateSkills()
|
||||
{
|
||||
SkillsList.DisposeAllChildren();
|
||||
_rowCount = 0;
|
||||
foreach (var skillType in Enum.GetValues<SkillType>())
|
||||
{
|
||||
var costs = _skillsSystem.GetSkillCost(skillType);
|
||||
var color = _skillsSystem.GetSkillColor(skillType);
|
||||
byte skillKey = (byte)skillType;
|
||||
|
||||
var defaultLevel = _defaultSkills.GetValueOrDefault(skillKey, 1);
|
||||
var currentLevel = _currentSkills.GetValueOrDefault(skillKey, defaultLevel);
|
||||
|
||||
var skillSelector = new SkillSelector(skillType, currentLevel, costs, color, defaultLevel, _upgradeOnly)
|
||||
{
|
||||
IsLocked = _upgradeOnly && defaultLevel == 4
|
||||
};
|
||||
|
||||
skillSelector.OnSkillLevelChanged += (newLevel) =>
|
||||
{
|
||||
if (newLevel < defaultLevel)
|
||||
{
|
||||
skillSelector.SetLevel(currentLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
_currentSkills[skillKey] = newLevel;
|
||||
OnSkillChanged?.Invoke(_jobId, skillKey, newLevel);
|
||||
UpdatePoints();
|
||||
};
|
||||
|
||||
var rowContainer = CreateSkillRow(skillSelector);
|
||||
SkillsList.AddChild(rowContainer);
|
||||
|
||||
_rowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private PanelContainer CreateSkillRow(SkillSelector skillSelector)
|
||||
{
|
||||
var currentRowColor = (_rowCount % 2 == 0) ? _rowColor1 : _rowColor2;
|
||||
|
||||
return new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = currentRowColor,
|
||||
ContentMarginTopOverride = 2,
|
||||
ContentMarginBottomOverride = 2,
|
||||
ContentMarginLeftOverride = 4,
|
||||
ContentMarginRightOverride = 4
|
||||
},
|
||||
Children = { skillSelector }
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdatePoints()
|
||||
{
|
||||
_spentPoints = CalculateTotalSpentPoints();
|
||||
var unspentPoints = _totalPoints - _spentPoints;
|
||||
|
||||
PointsLabel.Text = $" {unspentPoints} / {_totalPoints}";
|
||||
|
||||
UpdateSkillAvailability(unspentPoints);
|
||||
}
|
||||
|
||||
private int CalculateTotalSpentPoints()
|
||||
{
|
||||
var totalSpent = 0;
|
||||
|
||||
foreach (var (skillKey, level) in _currentSkills)
|
||||
{
|
||||
var skillType = (SkillType)skillKey;
|
||||
var costs = _skillsSystem.GetSkillCost(skillType);
|
||||
var defaultLevel = _defaultSkills.GetValueOrDefault(skillKey, 1);
|
||||
|
||||
for (int currentLevel = defaultLevel; currentLevel < level; currentLevel++)
|
||||
{
|
||||
if (currentLevel < costs.Length)
|
||||
totalSpent += costs[currentLevel];
|
||||
}
|
||||
}
|
||||
|
||||
return totalSpent;
|
||||
}
|
||||
|
||||
private void UpdateSkillAvailability(int unspentPoints)
|
||||
{
|
||||
foreach (var child in SkillsList.Children)
|
||||
{
|
||||
if (child is PanelContainer panel && panel.ChildCount > 0)
|
||||
{
|
||||
if (panel.Children.ElementAt(0) is SkillSelector selector)
|
||||
{
|
||||
selector.UpdateAvailability(unspentPoints, _skillsSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2317
Content.Server.Database/Migrations/Postgres/20251018200752_Skills.Designer.cs
generated
Normal file
2317
Content.Server.Database/Migrations/Postgres/20251018200752_Skills.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Skills : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_job_skills",
|
||||
columns: table => new
|
||||
{
|
||||
profile_job_skills_id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
profile_id = table.Column<int>(type: "integer", nullable: false),
|
||||
job_name = table.Column<string>(type: "text", nullable: false),
|
||||
skills = table.Column<string>(type: "jsonb", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_job_skills", x => x.profile_job_skills_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_job_skills_profile_profile_id",
|
||||
column: x => x.profile_id,
|
||||
principalTable: "profile",
|
||||
principalColumn: "profile_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_job_skills_profile_id_job_name",
|
||||
table: "profile_job_skills",
|
||||
columns: new[] { "profile_id", "job_name" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_job_skills");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1040,6 +1040,38 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("profile", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileJobSkills", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_job_skills_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("JobName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("job_name");
|
||||
|
||||
b.Property<int>("ProfileId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
b.Property<string>("Skills")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("skills");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_job_skills");
|
||||
|
||||
b.HasIndex("ProfileId", "JobName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("profile_job_skills", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1901,6 +1933,18 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("Preference");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileJobSkills", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
.WithMany("JobSkills")
|
||||
.HasForeignKey("ProfileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_job_skills_profile_profile_id");
|
||||
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
|
||||
@@ -2218,6 +2262,8 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
b.Navigation("Antags");
|
||||
|
||||
b.Navigation("JobSkills");
|
||||
|
||||
b.Navigation("JobSubnames");
|
||||
|
||||
b.Navigation("JobUnblockings");
|
||||
|
||||
2232
Content.Server.Database/Migrations/Sqlite/20251018200740_Skills.Designer.cs
generated
Normal file
2232
Content.Server.Database/Migrations/Sqlite/20251018200740_Skills.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Skills : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_job_skills",
|
||||
columns: table => new
|
||||
{
|
||||
profile_job_skills_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
profile_id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
job_name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
skills = table.Column<string>(type: "jsonb", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_job_skills", x => x.profile_job_skills_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_job_skills_profile_profile_id",
|
||||
column: x => x.profile_id,
|
||||
principalTable: "profile",
|
||||
principalColumn: "profile_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_job_skills_profile_id_job_name",
|
||||
table: "profile_job_skills",
|
||||
columns: new[] { "profile_id", "job_name" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_job_skills");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -985,6 +985,36 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("profile", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileJobSkills", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_job_skills_id");
|
||||
|
||||
b.Property<string>("JobName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("job_name");
|
||||
|
||||
b.Property<int>("ProfileId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
b.Property<string>("Skills")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("skills");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_job_skills");
|
||||
|
||||
b.HasIndex("ProfileId", "JobName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("profile_job_skills", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1818,6 +1848,18 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("Preference");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileJobSkills", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
.WithMany("JobSkills")
|
||||
.HasForeignKey("ProfileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_job_skills_profile_profile_id");
|
||||
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
|
||||
@@ -2135,6 +2177,8 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
b.Navigation("Antags");
|
||||
|
||||
b.Navigation("JobSkills");
|
||||
|
||||
b.Navigation("JobSubnames");
|
||||
|
||||
b.Navigation("JobUnblockings");
|
||||
|
||||
@@ -9,6 +9,8 @@ using System.Net;
|
||||
using System.Text.Json;
|
||||
using Content.Shared.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking; // WL-Skills
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; // WL-Skills
|
||||
using NpgsqlTypes;
|
||||
|
||||
namespace Content.Server.Database
|
||||
@@ -46,6 +48,7 @@ namespace Content.Server.Database
|
||||
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
|
||||
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
|
||||
public DbSet<IPIntelCache> IPIntelCache { get; set; } = null!;
|
||||
public DbSet<ProfileJobSkills> ProfileJobSkills { get; set; } = null!; // WL-Skills
|
||||
|
||||
//WL-Changes-start
|
||||
public DbSet<DiscordConnection> DiscordConnections { get; set; } = null!;
|
||||
@@ -123,6 +126,30 @@ namespace Content.Server.Database
|
||||
modelBuilder.Entity<JobSubname>()
|
||||
.HasIndex(j => new { j.ProfileId, j.JobName })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<ProfileJobSkills>(entity =>
|
||||
{
|
||||
var converter = new ValueConverter<Dictionary<byte, int>, string>(
|
||||
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null!),
|
||||
v => JsonSerializer.Deserialize<Dictionary<byte, int>>(v, (JsonSerializerOptions)null!) ?? new());
|
||||
|
||||
var comparer = new ValueComparer<Dictionary<byte, int>>(
|
||||
(l, r) => l != null && r != null && l.SequenceEqual(r),
|
||||
v => v.Aggregate(0, (a, p) => HashCode.Combine(a, p.Key.GetHashCode(), p.Value.GetHashCode())),
|
||||
v => v.ToDictionary(kv => kv.Key, kv => kv.Value));
|
||||
|
||||
entity.Property(e => e.Skills)
|
||||
.HasConversion(converter)
|
||||
.Metadata.SetValueComparer(comparer);
|
||||
|
||||
entity.HasIndex(p => new { p.ProfileId, p.JobName })
|
||||
.IsUnique();
|
||||
|
||||
entity.HasOne(e => e.Profile)
|
||||
.WithMany(e => e.JobSkills)
|
||||
.HasForeignKey(e => e.ProfileId)
|
||||
.IsRequired();
|
||||
});
|
||||
//WL-Changes-end
|
||||
|
||||
modelBuilder.Entity<AssignedUserId>()
|
||||
@@ -455,6 +482,7 @@ namespace Content.Server.Database
|
||||
public List<Antag> Antags { get; } = new();
|
||||
public List<Trait> Traits { get; } = new();
|
||||
public List<JobSubname> JobSubnames { get; } = new(); //WL-Subnames
|
||||
public List<ProfileJobSkills> JobSkills { get; } = new(); // WL-Skills
|
||||
|
||||
public List<JobUnblocking> JobUnblockings { get; } = new(); //WL-Changes
|
||||
public List<ProfileRoleLoadout> Loadouts { get; } = new();
|
||||
@@ -606,10 +634,29 @@ namespace Content.Server.Database
|
||||
/*
|
||||
* Insert extra data here like custom descriptions or colors or whatever.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// WL-Skills-start
|
||||
#region Job Skills
|
||||
|
||||
public class ProfileJobSkills
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int ProfileId { get; set; }
|
||||
public Profile Profile { get; set; } = null!;
|
||||
|
||||
public string JobName { get; set; } = string.Empty;
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public Dictionary<byte, int> Skills { get; set; } = new();
|
||||
}
|
||||
|
||||
#endregion
|
||||
// WL-Skills-end
|
||||
|
||||
public enum DbPreferenceUnavailableMode
|
||||
{
|
||||
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.
|
||||
|
||||
@@ -41,6 +41,7 @@ using System.Linq;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared._WL.Skills.Components; // Wl-Skills
|
||||
|
||||
|
||||
namespace Content.Server.Administration.Systems
|
||||
@@ -657,6 +658,26 @@ namespace Content.Server.Administration.Systems
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
// WL-Changes: Languages end
|
||||
|
||||
// Wl-Skills-start
|
||||
// Skills Management Verb
|
||||
if (_adminManager.HasAdminFlag(player, AdminFlags.Admin) && EntityManager.HasComponent<SkillsComponent>(args.Target))
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("skills-admin-verb-manage"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
var eui = new SkillsAdminEui(args.Target);
|
||||
_euiManager.OpenEui(eui, player);
|
||||
eui.StateDirty();
|
||||
},
|
||||
Impact = LogImpact.Medium
|
||||
});
|
||||
}
|
||||
// Wl-Skills-end
|
||||
}
|
||||
|
||||
#region SolutionsEui
|
||||
|
||||
@@ -17,6 +17,8 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared._WL.Skills.Components; // WL-Skills
|
||||
using Content.Shared._WL.Skills; // WL-Skills
|
||||
|
||||
namespace Content.Server.Cloning;
|
||||
|
||||
@@ -36,6 +38,7 @@ public sealed partial class CloningSystem : SharedCloningSystem
|
||||
[Dependency] private readonly SharedStorageSystem _storage = default!;
|
||||
[Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
[Dependency] private readonly SharedSkillsSystem _skills = default!; // WL-Skills
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a clone of the given humanoid mob at the specified location or in nullspace.
|
||||
@@ -62,6 +65,8 @@ public sealed partial class CloningSystem : SharedCloningSystem
|
||||
|
||||
CloneComponents(original, clone.Value, settings);
|
||||
|
||||
CopySkills(original, clone.Value); // WL-Skills
|
||||
|
||||
// Add equipment first so that SetEntityName also renames the ID card.
|
||||
if (settings.CopyEquipment != null)
|
||||
CopyEquipment(original, clone.Value, settings.CopyEquipment.Value, settings.Whitelist, settings.Blacklist);
|
||||
@@ -130,6 +135,16 @@ public sealed partial class CloningSystem : SharedCloningSystem
|
||||
RaiseLocalEvent(original, ref cloningEv); // used for datafields that cannot be directly copied using CopyComp
|
||||
}
|
||||
|
||||
// WL-Skills-start
|
||||
/// <summary>
|
||||
/// Copies all skills from the original entity to the clone.
|
||||
/// </summary>
|
||||
public void CopySkills(EntityUid original, EntityUid clone)
|
||||
{
|
||||
_skills.CopySkills(original, clone);
|
||||
}
|
||||
// WL-Skills-end
|
||||
|
||||
/// <summary>
|
||||
/// Copies the equipment the original has to the clone.
|
||||
/// This uses the original prototype of the items, so any changes to components that are done after spawning are lost!
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace Content.Server.Database
|
||||
//WL-Changes-start
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.JobSubnames)
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.JobUnblockings)
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.JobSkills)
|
||||
//WL-Changes-end
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Traits)
|
||||
@@ -166,6 +167,7 @@ namespace Content.Server.Database
|
||||
//WL-Changes-start
|
||||
.Include(p => p.JobSubnames)
|
||||
.Include(p => p.JobUnblockings)
|
||||
.Include(p => p.JobSkills)
|
||||
//WL-Changes-end
|
||||
.Include(p => p.Antags)
|
||||
.Include(p => p.Traits)
|
||||
@@ -272,6 +274,7 @@ namespace Content.Server.Database
|
||||
//WL-Changes-start
|
||||
var jobSubnames = profile.JobSubnames.ToDictionary(x => x.JobName, x => x.Subname);
|
||||
var jobUnblockings = profile.JobUnblockings.ToDictionary(k => k.JobName, v => v.ForceUnblocked);
|
||||
var jobSkills = profile.JobSkills.ToDictionary(js => js.JobName, js => js.Skills);
|
||||
//WL-Changes-end
|
||||
|
||||
var jobs = profile.Jobs.ToDictionary(j => new ProtoId<JobPrototype>(j.JobName), j => (JobPriority) j.Priority);
|
||||
@@ -364,7 +367,8 @@ namespace Content.Server.Database
|
||||
jobUnblockings,
|
||||
profile.MedicalRecord, // WL-Records
|
||||
profile.SecurityRecord, // WL-Records
|
||||
profile.EmploymentRecord // WL-Records
|
||||
profile.EmploymentRecord, // WL-Records
|
||||
jobSkills // WL-Skills
|
||||
);
|
||||
}
|
||||
|
||||
@@ -400,7 +404,18 @@ namespace Content.Server.Database
|
||||
profile.SpawnPriority = (int) humanoid.SpawnPriority;
|
||||
profile.Markings = markings;
|
||||
profile.Slot = slot;
|
||||
profile.PreferenceUnavailable = (DbPreferenceUnavailableMode) humanoid.PreferenceUnavailable;
|
||||
profile.PreferenceUnavailable = (DbPreferenceUnavailableMode)humanoid.PreferenceUnavailable;
|
||||
// WL-Skills-start
|
||||
profile.JobSkills.Clear();
|
||||
foreach (var jobSkill in humanoid.Skills)
|
||||
{
|
||||
profile.JobSkills.Add(new ProfileJobSkills
|
||||
{
|
||||
JobName = jobSkill.Key,
|
||||
Skills = jobSkill.Value
|
||||
});
|
||||
}
|
||||
// WL-Skills-end
|
||||
|
||||
profile.Jobs.Clear();
|
||||
profile.Jobs.AddRange(
|
||||
|
||||
138
Content.Server/_WL/Administration/UI/SkillsAdminEui.cs
Normal file
138
Content.Server/_WL/Administration/UI/SkillsAdminEui.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System.Linq;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Prayer;
|
||||
using Content.Shared._WL.Skills;
|
||||
using Content.Shared._WL.Skills.Components;
|
||||
using Content.Shared._WL.Skills.UI;
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Systems;
|
||||
|
||||
public sealed class SkillsAdminEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private readonly EntityUid _targetEntity;
|
||||
private readonly SharedSkillsSystem _skillsSystem;
|
||||
private readonly PrayerSystem _prayer;
|
||||
private SkillsComponent? _skillsComp;
|
||||
|
||||
public SkillsAdminEui(EntityUid targetEntity)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_targetEntity = targetEntity;
|
||||
_skillsSystem = _entMan.System<SharedSkillsSystem>();
|
||||
_prayer = _entMan.System<PrayerSystem>();
|
||||
_entMan.TryGetComponent(_targetEntity, out _skillsComp);
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
if (_skillsComp == null)
|
||||
return new SkillsAdminEuiState(false, new(), 0, 0, "", GetEntityName(_targetEntity));
|
||||
|
||||
var currentSkills = _skillsComp.Skills.ToDictionary(
|
||||
kvp => (byte)kvp.Key,
|
||||
kvp => kvp.Value
|
||||
);
|
||||
|
||||
var defaultSkills = _skillsSystem.GetDefaultSkillsForJob(_skillsComp.CurrentJob);
|
||||
|
||||
var jobName = Loc.GetString("skills-admin-skills-no-job");
|
||||
if (_proto.TryIndex(_skillsComp.CurrentJob, out var jobPrototype))
|
||||
jobName = jobPrototype.LocalizedName;
|
||||
|
||||
return new SkillsAdminEuiState(
|
||||
true,
|
||||
currentSkills,
|
||||
_skillsComp.SpentPoints,
|
||||
_skillsComp.BonusPoints,
|
||||
jobName,
|
||||
GetEntityName(_targetEntity)
|
||||
);
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case SkillsAdminEuiClosedMessage:
|
||||
Close();
|
||||
break;
|
||||
|
||||
case SkillsAdminEuiSkillChangedMessage changedMsg:
|
||||
HandleSkillChanged(changedMsg);
|
||||
break;
|
||||
|
||||
case SkillsAdminEuiPointsChangedMessage pointsMsg:
|
||||
HandlePointsChanged(pointsMsg);
|
||||
break;
|
||||
|
||||
case SkillsAdminEuiResetMessage:
|
||||
HandleResetAll();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSkillChanged(SkillsAdminEuiSkillChangedMessage message)
|
||||
{
|
||||
if (_skillsComp == null)
|
||||
return;
|
||||
|
||||
var defaultSkills = _skillsSystem.ConvertToSkillTypeDict(_skillsSystem.GetDefaultSkillsForJob(_skillsComp.CurrentJob));
|
||||
|
||||
var skillType = (SkillType)message.SkillKey;
|
||||
_skillsSystem.SetSkillLevelAdmin(_targetEntity, skillType, message.NewLevel, defaultSkills, _skillsComp);
|
||||
|
||||
if (_entMan.TryGetComponent<ActorComponent>(_targetEntity, out var actor))
|
||||
_prayer.SendSubtleMessage(actor.PlayerSession, actor.PlayerSession, string.Empty,
|
||||
Loc.GetString("skills-admin-notify-skills-changed"));
|
||||
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
private void HandlePointsChanged(SkillsAdminEuiPointsChangedMessage message)
|
||||
{
|
||||
if (_skillsComp == null)
|
||||
return;
|
||||
|
||||
var totalBonusPoints = _skillsComp.BonusPoints;
|
||||
_skillsSystem.SetBonusPoints(_targetEntity, message.NewBonusPoints, _skillsComp);
|
||||
if (_entMan.TryGetComponent<ActorComponent>(_targetEntity, out var actor))
|
||||
{
|
||||
var messageKey = message.NewBonusPoints > totalBonusPoints
|
||||
? "skills-admin-notify-points-added" : "skills-admin-notify-points-removed";
|
||||
|
||||
_prayer.SendSubtleMessage(actor.PlayerSession, actor.PlayerSession, string.Empty,
|
||||
Loc.GetString(messageKey));
|
||||
}
|
||||
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
private void HandleResetAll()
|
||||
{
|
||||
if (_skillsComp == null)
|
||||
return;
|
||||
|
||||
_skillsSystem.ResetAllSkills(_targetEntity, _skillsComp);
|
||||
|
||||
if (_entMan.TryGetComponent<ActorComponent>(_targetEntity, out var actor))
|
||||
_prayer.SendSubtleMessage(actor.PlayerSession, actor.PlayerSession, string.Empty,
|
||||
Loc.GetString("skills-admin-notify-skills-reset"));
|
||||
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
private string GetEntityName(EntityUid entity)
|
||||
{
|
||||
return _entMan.GetComponent<MetaDataComponent>(entity).EntityName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ using Content.Server._WL.Ert.Prototypes;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared._WL.Entity.Extensions;
|
||||
using Content.Shared._WL.Ert;
|
||||
using Content.Shared._WL.Math.Extensions;
|
||||
using Content.Shared._WL.Mathemathics.Extensions;
|
||||
using Content.Shared._WL.Random.Extensions;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
69
Content.Server/_WL/Skills/SkillsEui.cs
Normal file
69
Content.Server/_WL/Skills/SkillsEui.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Linq;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared._WL.Skills;
|
||||
using Content.Shared._WL.Skills.Components;
|
||||
using Content.Shared._WL.Skills.UI;
|
||||
using Content.Shared.Eui;
|
||||
|
||||
namespace Content.Server._WL.Skills.UI;
|
||||
|
||||
public sealed class SkillsEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private readonly EntityUid _entity;
|
||||
private readonly SharedSkillsSystem _skillsSystem;
|
||||
private readonly string _jobId;
|
||||
|
||||
public SkillsEui(EntityUid entity, SharedSkillsSystem skillsSystem, string jobId)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_entity = entity;
|
||||
_skillsSystem = skillsSystem;
|
||||
_jobId = jobId;
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
if (!_entMan.TryGetComponent<SkillsComponent>(_entity, out var skillsComp))
|
||||
return new SkillsEuiState(_jobId, new(), new(), 0, 0);
|
||||
|
||||
var currentSkills = skillsComp.Skills.ToDictionary(
|
||||
kvp => (byte)kvp.Key,
|
||||
kvp => kvp.Value
|
||||
);
|
||||
|
||||
var defaultSkills = _skillsSystem.GetDefaultSkillsForJob(_jobId);
|
||||
|
||||
return new SkillsEuiState(_jobId, currentSkills, defaultSkills,
|
||||
_skillsSystem.GetTotalPoints(_entity, _jobId, skillsComp), skillsComp.SpentPoints);
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case SkillsEuiClosedMessage:
|
||||
Close();
|
||||
break;
|
||||
|
||||
case SkillsEuiSkillChangedMessage changedMsg:
|
||||
HandleSkillChanged(changedMsg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSkillChanged(SkillsEuiSkillChangedMessage message)
|
||||
{
|
||||
if (!_entMan.TryGetComponent<SkillsComponent>(_entity, out var skillsComp))
|
||||
return;
|
||||
|
||||
var skillType = (SkillType)message.SkillKey;
|
||||
_skillsSystem.TrySetSkillLevel(_entity, skillType, message.NewLevel, _jobId, skillsComp);
|
||||
|
||||
StateDirty();
|
||||
}
|
||||
}
|
||||
75
Content.Server/_WL/Skills/SkillsSystem.cs
Normal file
75
Content.Server/_WL/Skills/SkillsSystem.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Content.Server._WL.Skills.UI;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared._WL.Skills;
|
||||
using Content.Shared._WL.Skills.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server._WL.Skills;
|
||||
|
||||
public sealed partial class SkillsSystem : SharedSkillsSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly EuiManager _eui = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<SelectSkillPressedEvent>(OnSelectSkill);
|
||||
SubscribeLocalEvent<SkillsComponent, SkillsAddedEvent>(OnSkillsAdded);
|
||||
}
|
||||
|
||||
private void OnSelectSkill(SelectSkillPressedEvent args)
|
||||
{
|
||||
TrySetSkillLevel(GetEntity(args.Uid), args.Skill, args.TargetLevel, args.JobId);
|
||||
}
|
||||
|
||||
private void OnSkillsAdded(EntityUid uid, SkillsComponent component, SkillsAddedEvent args)
|
||||
{
|
||||
// Disable AutoOpening for Development
|
||||
if (!_cfg.GetCVar(CCVars.GameLobbyEnabled))
|
||||
return;
|
||||
|
||||
if (!_mind.TryGetMind(uid, out _, out var mind) || mind is { UserId: null }
|
||||
|| !_player.TryGetSessionById(mind.UserId, out var session))
|
||||
return;
|
||||
|
||||
var jobId = GetJobIdFromEntity(mind);
|
||||
if (ShouldForceSkillsSelection(uid, jobId, component))
|
||||
{
|
||||
OpenForcedSkillsMenu(session, uid, jobId);
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetJobIdFromEntity(MindComponent mind)
|
||||
{
|
||||
foreach (var roleId in mind.MindRoleContainer.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<MindRoleComponent>(roleId, out var role))
|
||||
continue;
|
||||
|
||||
if (role.JobPrototype != null)
|
||||
{
|
||||
return role.JobPrototype.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void OpenForcedSkillsMenu(ICommonSession player, EntityUid entity, string? jobId)
|
||||
{
|
||||
jobId ??= "unknown";
|
||||
|
||||
var eui = new SkillsEui(entity, this, jobId);
|
||||
_eui.OpenEui(eui, player);
|
||||
|
||||
eui.StateDirty();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Shared._WL.Skills; // WL-Skills
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Corvax.TTS;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -164,7 +165,8 @@ namespace Content.Shared.Preferences
|
||||
Dictionary<string, bool> jobUnblockings,
|
||||
string medicalRecord, // WL-Records
|
||||
string securityRecord, // WL-Records
|
||||
string employmentRecord // WL-Records
|
||||
string employmentRecord, // WL-Records
|
||||
Dictionary<string, Dictionary<byte, int>> skills // WL-Skills
|
||||
//WL-Changes-end
|
||||
)
|
||||
{
|
||||
@@ -193,6 +195,7 @@ namespace Content.Shared.Preferences
|
||||
MedicalRecord = medicalRecord; // WL-Records
|
||||
SecurityRecord = securityRecord; // WL-Records
|
||||
EmploymentRecord = employmentRecord; // WL-Records
|
||||
Skills = skills; // WL-Skills
|
||||
//WL-Changes-end
|
||||
|
||||
var hasHighPrority = false;
|
||||
@@ -233,7 +236,8 @@ namespace Content.Shared.Preferences
|
||||
new(other.JobUnblockings), // WL-Heigh
|
||||
other.MedicalRecord, // WL-Records
|
||||
other.SecurityRecord, // WL-Records
|
||||
other.EmploymentRecord) // WL-Records
|
||||
other.EmploymentRecord, // WL-Records
|
||||
other.Skills) // WL-Skills
|
||||
{
|
||||
}
|
||||
|
||||
@@ -341,6 +345,8 @@ namespace Content.Shared.Preferences
|
||||
[DataField("height")] public int Height { get; private set; } = 165; // WL-Height
|
||||
public IReadOnlyDictionary<string, string> JobSubnames => _jobSubnames; //WL-changes
|
||||
public IReadOnlyDictionary<string, bool> JobUnblockings => _jobUnblockings;
|
||||
|
||||
[DataField] public Dictionary<string, Dictionary<byte, int>> Skills { get; set; } = new(); // WL-Skills
|
||||
//WL-Changes-end
|
||||
|
||||
public HumanoidCharacterProfile WithName(string name)
|
||||
@@ -469,6 +475,22 @@ namespace Content.Shared.Preferences
|
||||
_jobUnblockings = dict
|
||||
};
|
||||
}
|
||||
|
||||
public HumanoidCharacterProfile WithSkill(string jobName, byte skillKey, int level)
|
||||
{
|
||||
var newSkills = new Dictionary<string, Dictionary<byte, int>>(Skills);
|
||||
if (!newSkills.TryGetValue(jobName, out var jobSkills))
|
||||
{
|
||||
jobSkills = new Dictionary<byte, int>();
|
||||
newSkills[jobName] = jobSkills;
|
||||
}
|
||||
|
||||
var newJobSkills = new Dictionary<byte, int>(jobSkills);
|
||||
newJobSkills[skillKey] = level;
|
||||
newSkills[jobName] = newJobSkills;
|
||||
|
||||
return new(this) { Skills = newSkills };
|
||||
}
|
||||
//WL-Changes-end
|
||||
|
||||
public HumanoidCharacterProfile WithJobPriority(ProtoId<JobPrototype> jobId, JobPriority priority)
|
||||
@@ -620,6 +642,24 @@ namespace Content.Shared.Preferences
|
||||
if (!Loadouts.SequenceEqual(other.Loadouts)) return false;
|
||||
if (!_jobSubnames.SequenceEqual(other._jobSubnames)) return false; // WL-JobSubnames
|
||||
if (!_jobUnblockings.SequenceEqual(other._jobUnblockings)) return false; // WL-Changes
|
||||
// WL-Skills-start
|
||||
if (Skills.Count != other.Skills.Count) return false;
|
||||
foreach (var kv in Skills)
|
||||
{
|
||||
if (!other.Skills.TryGetValue(kv.Key, out var otherJobSkills))
|
||||
return false;
|
||||
|
||||
var jobSkills = kv.Value;
|
||||
if (jobSkills.Count != otherJobSkills.Count)
|
||||
return false;
|
||||
|
||||
foreach (var inner in jobSkills)
|
||||
{
|
||||
if (!otherJobSkills.TryGetValue(inner.Key, out var otherLevel) || otherLevel != inner.Value)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// WL-Skills-end
|
||||
return Appearance.MemberwiseEquals(other.Appearance);
|
||||
}
|
||||
|
||||
@@ -770,6 +810,47 @@ namespace Content.Shared.Preferences
|
||||
.Where(prototypeManager.HasIndex)
|
||||
.ToList();
|
||||
|
||||
// WL-Skills-Start
|
||||
var validSkills = new Dictionary<string, Dictionary<byte, int>>();
|
||||
foreach (var (jobName, jobSkillDict) in Skills)
|
||||
{
|
||||
var validJobSkills = new Dictionary<byte, int>();
|
||||
foreach (var (skillByte, level) in jobSkillDict)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(SkillType), skillByte))
|
||||
{
|
||||
var validLevel = Math.Clamp(level, 1, 4);
|
||||
validJobSkills[skillByte] = validLevel;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SkillType skill in Enum.GetValues(typeof(SkillType)))
|
||||
{
|
||||
var skillByte = (byte)skill;
|
||||
if (!validJobSkills.ContainsKey(skillByte))
|
||||
{
|
||||
validJobSkills[skillByte] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
validSkills[jobName] = validJobSkills;
|
||||
}
|
||||
|
||||
if (validSkills.Count == 0)
|
||||
{
|
||||
var defaultSkills = new Dictionary<byte, int>();
|
||||
foreach (SkillType skill in Enum.GetValues(typeof(SkillType)))
|
||||
{
|
||||
defaultSkills[(byte)skill] = 1;
|
||||
}
|
||||
|
||||
foreach (var job in _jobPriorities.Keys)
|
||||
{
|
||||
validSkills[job.Id] = new Dictionary<byte, int>(defaultSkills);
|
||||
}
|
||||
}
|
||||
// WL-Skills-End
|
||||
|
||||
Name = name;
|
||||
FlavorText = flavortext;
|
||||
OocText = oocText; // WL-OOCText
|
||||
@@ -782,6 +863,7 @@ namespace Content.Shared.Preferences
|
||||
Gender = gender;
|
||||
Appearance = appearance;
|
||||
SpawnPriority = spawnPriority;
|
||||
Skills = validSkills; // WL-Skills
|
||||
|
||||
_jobPriorities.Clear();
|
||||
|
||||
@@ -926,6 +1008,20 @@ namespace Content.Shared.Preferences
|
||||
hashCode.Add(MedicalRecord);
|
||||
hashCode.Add(SecurityRecord);
|
||||
hashCode.Add(EmploymentRecord);
|
||||
unchecked
|
||||
{
|
||||
var skillsHash = 0;
|
||||
foreach (var jobKv in Skills.OrderBy(k => k.Key))
|
||||
{
|
||||
var innerHash = 0;
|
||||
foreach (var sk in jobKv.Value.OrderBy(k => k.Key))
|
||||
{
|
||||
innerHash = HashCode.Combine(innerHash, sk.Key.GetHashCode(), sk.Value.GetHashCode());
|
||||
}
|
||||
skillsHash = HashCode.Combine(skillsHash, jobKv.Key.GetHashCode(), innerHash);
|
||||
}
|
||||
hashCode.Add(skillsHash);
|
||||
}
|
||||
//WL-Changes-end
|
||||
|
||||
return hashCode.ToHashCode();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared._WL.Skills; // WL-Skills
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
@@ -61,6 +62,14 @@ namespace Content.Shared.Roles
|
||||
return list;
|
||||
}
|
||||
|
||||
// WL-Skills-start
|
||||
[DataField("defaultSkills")]
|
||||
public Dictionary<SkillType, int> DefaultSkills { get; private set; } = new();
|
||||
|
||||
[DataField("bonusSkillPoints")]
|
||||
public int BonusSkillPoints { get; private set; } = 0;
|
||||
// WL-Skills-end
|
||||
|
||||
// WL-Changes-end
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -145,4 +145,13 @@ public sealed class WLCVars
|
||||
|
||||
public static readonly CVarDef<int> MaxDynamicTextLength =
|
||||
CVarDef.Create("ic.dynamic_text_length", 1024, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* Skills
|
||||
*/
|
||||
/// <summary>
|
||||
/// ГАНС! ЕСЛИ ОНИ ВДРУГ НЕ НУЖНЫ ТО ПЕРЕКЛЮЧИ ПЕРЕКЛЮЧАТЕЛЬ!!!
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> SkillsEnabled =
|
||||
CVarDef.Create("skills.enabled", true, CVar.SERVER | CVar.REPLICATED | CVar.ARCHIVE);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Shared.Maths.MathHelper;
|
||||
|
||||
namespace Content.Shared._WL.Math.Extensions
|
||||
namespace Content.Shared._WL.Mathemathics.Extensions // Don't name Math this again
|
||||
{
|
||||
public static class Box2Ext
|
||||
{
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace Content.Shared._WL.Skills.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SharedSkillsSystem))]
|
||||
public sealed partial class InitialSkillsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Skills that are set to exact levels when the entity is created.
|
||||
/// </summary>
|
||||
[DataField("initialSkills")]
|
||||
public Dictionary<SkillType, int> InitialSkills = new();
|
||||
|
||||
/// <summary>
|
||||
/// Skills that are set to random levels when the entity is created.
|
||||
/// </summary>
|
||||
[DataField("randomSkills")]
|
||||
public List<SkillType> RandomSkills = new();
|
||||
|
||||
/// <summary>
|
||||
/// Skills that are added to existing skills (if entity already has skills component).
|
||||
/// </summary>
|
||||
[DataField("addSkills")]
|
||||
public Dictionary<SkillType, int> AddSkills = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether to randomize ALL skills when the entity is created.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool RandomizeAllSkills = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to override existing skills or add to them.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool OverrideExisting = true;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum level for randomized skills (1-4).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int RandomMinLevel = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum level for randomized skills (1-4).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int RandomMaxLevel = 4;
|
||||
}
|
||||
25
Content.Shared/_WL/Skills/Components/SkillsComponent.cs
Normal file
25
Content.Shared/_WL/Skills/Components/SkillsComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._WL.Skills.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedSkillsSystem))]
|
||||
public sealed partial class SkillsComponent : Component
|
||||
{
|
||||
[DataField("skills"), AutoNetworkedField]
|
||||
public Dictionary<SkillType, int> Skills = new();
|
||||
|
||||
[DataField("unspentPoints"), AutoNetworkedField]
|
||||
public int UnspentPoints;
|
||||
|
||||
[DataField("spentPoints"), AutoNetworkedField]
|
||||
public int SpentPoints;
|
||||
|
||||
[DataField("bonusPoints"), AutoNetworkedField]
|
||||
public int BonusPoints;
|
||||
|
||||
[AutoNetworkedField]
|
||||
public ProtoId<JobPrototype>? CurrentJob = null;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared._WL.Skills;
|
||||
|
||||
[Prototype("racialSkillBonus")]
|
||||
public sealed partial class RacialSkillBonusPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("species", customTypeSerializer: typeof(PrototypeIdSerializer<SpeciesPrototype>), required: true)]
|
||||
public string Species { get; private set; } = default!;
|
||||
|
||||
[DataField("ageBonuses")]
|
||||
public Dictionary<int, int> AgeBonuses { get; private set; } = new();
|
||||
|
||||
public int GetBonusForAge(int age)
|
||||
{
|
||||
if (AgeBonuses.Count == 0)
|
||||
return 0;
|
||||
|
||||
int maxBonus = 0;
|
||||
foreach (var (bonusAge, bonus) in AgeBonuses)
|
||||
{
|
||||
if (bonusAge <= age)
|
||||
maxBonus += bonus;
|
||||
}
|
||||
|
||||
return maxBonus;
|
||||
}
|
||||
}
|
||||
19
Content.Shared/_WL/Skills/Prototypes/SkillPrototype.cs
Normal file
19
Content.Shared/_WL/Skills/Prototypes/SkillPrototype.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._WL.Skills;
|
||||
|
||||
[Prototype("skill")]
|
||||
public sealed partial class SkillPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("skillType", required: true)]
|
||||
public SkillType SkillType { get; private set; }
|
||||
|
||||
[DataField("costs", required: true)]
|
||||
public int[] Costs { get; private set; } = new[] { 0, 0, 0, 0 };
|
||||
|
||||
[DataField("color", required: true)]
|
||||
public Color Color { get; private set; } = Color.White;
|
||||
}
|
||||
156
Content.Shared/_WL/Skills/SharedSkillsSystem.Gets.cs
Normal file
156
Content.Shared/_WL/Skills/SharedSkillsSystem.Gets.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using Content.Shared._WL.CCVars;
|
||||
using Content.Shared._WL.Skills.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared._WL.Skills;
|
||||
|
||||
public abstract partial class SharedSkillsSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks whether the entity has a sufficient skill level to perform the action.
|
||||
/// </summary>
|
||||
/// <param name="uid">ID entities</param>
|
||||
/// <param name="skill">Required skill</param>
|
||||
/// <param name="requiredLevel">Required minimum level (1-4)</param>
|
||||
/// <param name="comp">An optional skill component</param>
|
||||
/// <returns>True if the skill is sufficient</returns>
|
||||
public bool HasSkill(EntityUid uid, SkillType skill, int requiredLevel = 1, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return true;
|
||||
|
||||
if (!Resolve(uid, ref comp))
|
||||
return false;
|
||||
|
||||
var currentLevel = comp.Skills.GetValueOrDefault(skill, 1);
|
||||
return currentLevel >= requiredLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has the correct skill level
|
||||
/// </summary>
|
||||
public bool HasExactSkill(EntityUid uid, SkillType skill, int exactLevel, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return true;
|
||||
|
||||
if (!Resolve(uid, ref comp))
|
||||
return false;
|
||||
|
||||
var currentLevel = comp.Skills.GetValueOrDefault(skill, 1);
|
||||
return currentLevel == exactLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current skill level of the entity.
|
||||
/// </summary>
|
||||
public int GetSkillLevel(EntityUid uid, SkillType skill, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return 1;
|
||||
|
||||
if (!Resolve(uid, ref comp))
|
||||
return 1;
|
||||
|
||||
return comp.Skills.GetValueOrDefault(skill, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an entity can perform an action based on the skill's success chance.
|
||||
/// </summary>
|
||||
public bool CheckSkillChance(EntityUid uid, SkillType skill, float baseSuccessChance = 0.5f, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return true;
|
||||
|
||||
if (!Resolve(uid, ref comp))
|
||||
return _random.Prob(baseSuccessChance); // Basic probability if there are no skills
|
||||
|
||||
var skillLevel = comp.Skills.GetValueOrDefault(skill, 1);
|
||||
|
||||
// Модификатор based on уровня навыка
|
||||
float skillModifier = skillLevel switch
|
||||
{
|
||||
1 => 0.8f, // Beginner - fine
|
||||
2 => 1.0f, // Amateur - basic
|
||||
3 => 1.3f, // Specialist - bonus
|
||||
4 => 1.7f, // Professional - big bonus
|
||||
_ => 1.0f
|
||||
};
|
||||
|
||||
float successChance = baseSuccessChance * skillModifier;
|
||||
return _random.Prob(Math.Clamp(successChance, 0f, 1f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an efficiency modifier based on the skill level.
|
||||
/// </summary>
|
||||
public float GetSkillEfficiency(EntityUid uid, SkillType skill, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return 1.0f;
|
||||
|
||||
if (!Resolve(uid, ref comp))
|
||||
return 1.0f; // Basic efficiency
|
||||
|
||||
var skillLevel = comp.Skills.GetValueOrDefault(skill, 1);
|
||||
|
||||
return skillLevel switch
|
||||
{
|
||||
1 => 0.7f, // Beginner - 70% efficiency
|
||||
2 => 1.0f, // Amateur - 100%
|
||||
3 => 1.4f, // Specialist - 140%
|
||||
4 => 1.8f, // Professional - 180%
|
||||
_ => 1.0f
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an entity can perform a complex action (requires multiple skills)
|
||||
/// </summary>
|
||||
public bool CanPerformComplexAction(EntityUid uid, SkillsComponent? comp = null, params (SkillType skill, int requiredLevel)[] requirements)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return true;
|
||||
|
||||
if (!Resolve(uid, ref comp))
|
||||
return false; // Can't perform without skills
|
||||
|
||||
foreach (var (skill, requiredLevel) in requirements)
|
||||
{
|
||||
if (!HasSkill(uid, skill, requiredLevel, comp))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a description of the skill level for the UI.
|
||||
/// </summary>
|
||||
public string GetSkillLevelDescription(int level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
1 => Loc.GetString("skill-level-1"),
|
||||
2 => Loc.GetString("skill-level-2"),
|
||||
3 => Loc.GetString("skill-level-3"),
|
||||
4 => Loc.GetString("skill-level-4"),
|
||||
_ => Loc.GetString("skill-level-unknown")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color to display the skill level.
|
||||
/// </summary>
|
||||
public Color GetSkillLevelColor(int level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
1 => Color.Gray, // Beginner
|
||||
2 => Color.Yellow, // Amateur
|
||||
3 => Color.Blue, // Specialist
|
||||
4 => Color.Green, // Professional
|
||||
_ => Color.White
|
||||
};
|
||||
}
|
||||
}
|
||||
569
Content.Shared/_WL/Skills/SharedSkillsSystem.Skills.cs
Normal file
569
Content.Shared/_WL/Skills/SharedSkillsSystem.Skills.cs
Normal file
@@ -0,0 +1,569 @@
|
||||
using Content.Shared._WL.CCVars;
|
||||
using Content.Shared._WL.Skills.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Shared._WL.Skills;
|
||||
|
||||
public abstract partial class SharedSkillsSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
#region Initialization
|
||||
|
||||
public void InitializeSkills()
|
||||
{
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawnComplete);
|
||||
SubscribeLocalEvent<InitialSkillsComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes skills for a player when they spawn
|
||||
/// </summary>
|
||||
private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args)
|
||||
{
|
||||
if (args.Profile is not HumanoidCharacterProfile profile || !_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return;
|
||||
|
||||
InitializeSkills(args.Mob, args.JobId, profile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes skills component with default values and applies profile skills
|
||||
/// </summary>
|
||||
private void InitializeSkills(EntityUid mob, string? jobId, HumanoidCharacterProfile profile)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return;
|
||||
|
||||
var skillsComp = EnsureComp<SkillsComponent>(mob);
|
||||
skillsComp.Skills.Clear();
|
||||
|
||||
skillsComp.CurrentJob = jobId;
|
||||
|
||||
int bonusPoints = 0;
|
||||
Dictionary<SkillType, int> defaultSkills = new();
|
||||
if (jobId != null && _prototype.TryIndex<JobPrototype>(jobId, out var jobPrototype))
|
||||
{
|
||||
defaultSkills = jobPrototype.DefaultSkills;
|
||||
bonusPoints = jobPrototype.BonusSkillPoints;
|
||||
}
|
||||
|
||||
var racialBonus = CalculateRacialBonus(profile.Species, profile.Age);
|
||||
var totalPoints = bonusPoints + racialBonus;
|
||||
foreach (SkillType skill in Enum.GetValues(typeof(SkillType)))
|
||||
{
|
||||
var defaultLevel = defaultSkills.GetValueOrDefault(skill, 1);
|
||||
skillsComp.Skills[skill] = defaultLevel;
|
||||
}
|
||||
|
||||
if (jobId != null && profile.Skills.TryGetValue(jobId, out var profileSkills))
|
||||
{
|
||||
ApplyProfileSkillsWithLimit(mob, skillsComp, profileSkills, defaultSkills, totalPoints);
|
||||
}
|
||||
|
||||
RecalculateSpentPoints(mob, skillsComp, defaultSkills);
|
||||
skillsComp.UnspentPoints = Math.Max(0, totalPoints - skillsComp.SpentPoints);
|
||||
|
||||
Dirty(mob, skillsComp);
|
||||
|
||||
var ev = new SkillsAddedEvent();
|
||||
RaiseLocalEvent(mob, ref ev);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Skill Calculation Methods
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the total number of points spent for all skills, excluding default skill costs
|
||||
/// </summary>
|
||||
public void RecalculateSpentPoints(EntityUid uid, SkillsComponent? comp = null, Dictionary<SkillType, int>? defaultSkills = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
comp.SpentPoints = 0;
|
||||
|
||||
foreach (var (skill, level) in comp.Skills)
|
||||
{
|
||||
var defaultLevel = defaultSkills?.GetValueOrDefault(skill, 1) ?? 1;
|
||||
if (level > defaultLevel)
|
||||
{
|
||||
comp.SpentPoints += GetSkillTotalCost(skill, level) - GetSkillTotalCost(skill, defaultLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates spent points using job defaults
|
||||
/// </summary>
|
||||
public void RecalculateSpentPoints(EntityUid uid, string? jobId, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
var defaultSkills = GetDefaultSkillsForJob(jobId);
|
||||
RecalculateSpentPoints(uid, comp, ConvertToSkillTypeDict(defaultSkills));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts byte-based dictionary to SkillType-based dictionary
|
||||
/// </summary>
|
||||
public Dictionary<SkillType, int> ConvertToSkillTypeDict(Dictionary<byte, int> byteDict)
|
||||
{
|
||||
var result = new Dictionary<SkillType, int>();
|
||||
foreach (var (skillKey, level) in byteDict)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(SkillType), (SkillType)skillKey))
|
||||
{
|
||||
result[(SkillType)skillKey] = level;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the cost of increasing a skill to the specified level
|
||||
/// </summary>
|
||||
public int GetSkillTotalCost(SkillType skill, int targetLevel)
|
||||
{
|
||||
var costs = GetSkillCost(skill);
|
||||
targetLevel = Math.Clamp(targetLevel, 1, 4);
|
||||
|
||||
int totalCost = 0;
|
||||
for (int i = 0; i < targetLevel; i++)
|
||||
{
|
||||
totalCost += costs[i];
|
||||
}
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the cost of the current skill level
|
||||
/// </summary>
|
||||
public int GetCurrentSkillCost(SkillType skill, int currentLevel)
|
||||
{
|
||||
return GetSkillTotalCost(skill, currentLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the cost of increasing a skill by one level
|
||||
/// </summary>
|
||||
public int GetSkillUpgradeCost(SkillType skill, int currentLevel)
|
||||
{
|
||||
var costs = GetSkillCost(skill);
|
||||
if (currentLevel >= 4) return 0;
|
||||
return costs[currentLevel];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Skill Application Methods
|
||||
|
||||
/// <summary>
|
||||
/// Applies skills from the profile, taking into account the point limit
|
||||
/// </summary>
|
||||
private void ApplyProfileSkillsWithLimit(EntityUid uid, SkillsComponent skillsComp,
|
||||
Dictionary<byte, int> profileSkills, Dictionary<SkillType, int> defaultSkills, int totalPoints)
|
||||
{
|
||||
var spentPoints = 0;
|
||||
var tempSkills = new Dictionary<SkillType, int>(skillsComp.Skills);
|
||||
|
||||
foreach (var (skillKey, level) in profileSkills)
|
||||
{
|
||||
var skillType = (SkillType)skillKey;
|
||||
if (!Enum.IsDefined(typeof(SkillType), skillType))
|
||||
continue;
|
||||
|
||||
var clampedLevel = Math.Clamp(level, 1, 4);
|
||||
var defaultLevel = defaultSkills.GetValueOrDefault(skillType, 1);
|
||||
|
||||
if (clampedLevel > defaultLevel)
|
||||
{
|
||||
var cost = GetSkillTotalCost(skillType, clampedLevel) - GetSkillTotalCost(skillType, defaultLevel);
|
||||
if (spentPoints + cost <= totalPoints)
|
||||
{
|
||||
tempSkills[skillType] = clampedLevel;
|
||||
spentPoints += cost;
|
||||
}
|
||||
else
|
||||
{
|
||||
var maxAffordableLevel = FindMaxAffordableLevel(skillType, defaultLevel, totalPoints - spentPoints);
|
||||
if (maxAffordableLevel > defaultLevel)
|
||||
{
|
||||
tempSkills[skillType] = maxAffordableLevel;
|
||||
spentPoints += GetSkillTotalCost(skillType, maxAffordableLevel) - GetSkillTotalCost(skillType, defaultLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempSkills[skillType] = defaultLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempSkills[skillType] = clampedLevel;
|
||||
}
|
||||
}
|
||||
|
||||
skillsComp.Skills = tempSkills;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the maximum available skill level within the budget
|
||||
/// </summary>
|
||||
private int FindMaxAffordableLevel(SkillType skill, int currentLevel, int availablePoints)
|
||||
{
|
||||
var maxLevel = currentLevel;
|
||||
var currentCost = 0;
|
||||
|
||||
for (int targetLevel = currentLevel + 1; targetLevel <= 4; targetLevel++)
|
||||
{
|
||||
var additionalCost = GetSkillUpgradeCost(skill, targetLevel - 1);
|
||||
if (currentCost + additionalCost <= availablePoints)
|
||||
{
|
||||
currentCost += additionalCost;
|
||||
maxLevel = targetLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return maxLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets skill level with automatic detection of increase/decrease and default level check
|
||||
/// </summary>
|
||||
public bool TrySetSkillLevel(EntityUid uid, SkillType skill, int targetLevel, string? jobId = null, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return false;
|
||||
|
||||
targetLevel = Math.Clamp(targetLevel, 1, 4);
|
||||
var currentLevel = comp.Skills.GetValueOrDefault(skill, 1);
|
||||
if (currentLevel == targetLevel)
|
||||
return true;
|
||||
|
||||
var defaultLevel = GetDefaultSkillLevelForJob(skill, jobId);
|
||||
if (targetLevel < defaultLevel)
|
||||
return false;
|
||||
|
||||
if (targetLevel > currentLevel)
|
||||
{
|
||||
return TryIncreaseToLevel(uid, skill, targetLevel, comp, defaultLevel, jobId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TryDecreaseToLevel(uid, skill, targetLevel, comp, defaultLevel, jobId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases skill to specified level
|
||||
/// </summary>
|
||||
private bool TryIncreaseToLevel(EntityUid uid, SkillType skill, int targetLevel,
|
||||
SkillsComponent comp, int defaultLevel, string? jobId = null)
|
||||
{
|
||||
var currentLevel = comp.Skills.GetValueOrDefault(skill, 1);
|
||||
|
||||
var totalCost = 0;
|
||||
for (int level = Math.Max(currentLevel, defaultLevel); level < targetLevel; level++)
|
||||
{
|
||||
totalCost += GetSkillUpgradeCost(skill, level);
|
||||
}
|
||||
|
||||
if (comp.UnspentPoints < totalCost)
|
||||
return false;
|
||||
|
||||
comp.UnspentPoints -= totalCost;
|
||||
comp.Skills[skill] = targetLevel;
|
||||
RecalculateSpentPoints(uid, jobId, comp);
|
||||
|
||||
Dirty(uid, comp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decreases skill to specified level
|
||||
/// </summary>
|
||||
private bool TryDecreaseToLevel(EntityUid uid, SkillType skill, int targetLevel,
|
||||
SkillsComponent comp, int defaultLevel, string? jobId = null)
|
||||
{
|
||||
var currentLevel = comp.Skills.GetValueOrDefault(skill, 1);
|
||||
|
||||
var totalRefund = 0;
|
||||
for (int level = targetLevel; level < Math.Min(currentLevel, 4); level++)
|
||||
{
|
||||
if (level >= defaultLevel)
|
||||
{
|
||||
totalRefund += GetSkillUpgradeCost(skill, level);
|
||||
}
|
||||
}
|
||||
|
||||
comp.UnspentPoints += totalRefund;
|
||||
comp.Skills[skill] = targetLevel;
|
||||
RecalculateSpentPoints(uid, jobId, comp);
|
||||
|
||||
Dirty(uid, comp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Default Skills Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets default skill level for job
|
||||
/// </summary>
|
||||
public int GetDefaultSkillLevelForJob(SkillType skill, string? jobId)
|
||||
{
|
||||
if (jobId != null && _prototype.TryIndex<JobPrototype>(jobId, out var jobPrototype))
|
||||
{
|
||||
return jobPrototype.DefaultSkills.GetValueOrDefault(skill, 1);
|
||||
}
|
||||
|
||||
return 1; // Default base level
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets default skills for job
|
||||
/// </summary>
|
||||
public Dictionary<byte, int> GetDefaultSkillsForJob(string? jobId)
|
||||
{
|
||||
var defaultSkills = new Dictionary<byte, int>();
|
||||
foreach (SkillType skill in Enum.GetValues(typeof(SkillType)))
|
||||
{
|
||||
defaultSkills[(byte)skill] = 1;
|
||||
}
|
||||
|
||||
if (jobId != null && _prototype.TryIndex<JobPrototype>(jobId, out var jobPrototype))
|
||||
{
|
||||
foreach (var (skill, level) in jobPrototype.DefaultSkills)
|
||||
{
|
||||
defaultSkills[(byte)skill] = level;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultSkills;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if forced skills selection should be shown - only if ONLY default skills are set
|
||||
/// </summary>
|
||||
public bool ShouldForceSkillsSelection(EntityUid uid, string? jobId = null, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return false;
|
||||
|
||||
var defaultSkills = GetDefaultSkillsForJob(jobId);
|
||||
foreach (var (skill, level) in comp.Skills)
|
||||
{
|
||||
var defaultLevel = defaultSkills.GetValueOrDefault((byte)skill, 1);
|
||||
if (level != defaultLevel)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Admin Methods
|
||||
|
||||
/// <summary>
|
||||
/// Directly sets bonus points for admin control
|
||||
/// </summary>
|
||||
public void SetBonusPoints(EntityUid uid, int points, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
comp.BonusPoints = points;
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directly sets skill level without point restrictions for admin control
|
||||
/// </summary>
|
||||
public void SetSkillLevelAdmin(EntityUid uid, SkillType skill, int level,
|
||||
Dictionary<SkillType, int>? defaultSkills, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
level = Math.Clamp(level, 1, 4);
|
||||
comp.Skills[skill] = level;
|
||||
|
||||
RecalculateSpentPoints(uid, comp, defaultSkills);
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all skills to level 1 for admin control
|
||||
/// </summary>
|
||||
public void ResetAllSkills(EntityUid uid, SkillsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
foreach (SkillType skill in Enum.GetValues(typeof(SkillType)))
|
||||
{
|
||||
comp.Skills[skill] = 1;
|
||||
}
|
||||
|
||||
comp.UnspentPoints = 0;
|
||||
comp.SpentPoints = 0;
|
||||
comp.BonusPoints = 0;
|
||||
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets total points for entity
|
||||
/// </summary>
|
||||
public int GetTotalPoints(EntityUid uid, string? jobId = null,
|
||||
SkillsComponent? comp = null, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp) || !Resolve(uid, ref humanoid))
|
||||
return 0;
|
||||
|
||||
int bonusPoints = 0;
|
||||
if (jobId != null && _prototype.TryIndex<JobPrototype>(jobId, out var jobPrototype))
|
||||
bonusPoints = jobPrototype.BonusSkillPoints;
|
||||
|
||||
var racialBonus = CalculateRacialBonus(humanoid.Species, humanoid.Age);
|
||||
return bonusPoints + racialBonus + comp.BonusPoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates racial bonus of skill points
|
||||
/// </summary>
|
||||
private int CalculateRacialBonus(string species, int age)
|
||||
{
|
||||
var bonus = 0;
|
||||
foreach (var racialBonusProto in _prototype.EnumeratePrototypes<RacialSkillBonusPrototype>())
|
||||
{
|
||||
if (racialBonusProto.Species != species)
|
||||
continue;
|
||||
|
||||
bonus = racialBonusProto.GetBonusForAge(age);
|
||||
break;
|
||||
}
|
||||
|
||||
return bonus;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initial Skills
|
||||
|
||||
private void OnMapInit(EntityUid uid, InitialSkillsComponent component, MapInitEvent args)
|
||||
{
|
||||
if (!_cfg.GetCVar(WLCVars.SkillsEnabled))
|
||||
return;
|
||||
|
||||
var skillsComp = EnsureComp<SkillsComponent>(uid);
|
||||
|
||||
if (component.RandomizeAllSkills)
|
||||
{
|
||||
RandomizeAllSkills(uid, skillsComp, component.RandomMinLevel, component.RandomMaxLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyInitialSkills(uid, skillsComp, component);
|
||||
}
|
||||
|
||||
RecalculateSpentPoints(uid, skillsComp);
|
||||
RemCompDeferred<InitialSkillsComponent>(uid);
|
||||
|
||||
Dirty(uid, skillsComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the initial skills from the component
|
||||
/// </summary>
|
||||
public void ApplyInitialSkills(EntityUid uid, SkillsComponent skillsComp, InitialSkillsComponent initial)
|
||||
{
|
||||
if (initial.OverrideExisting)
|
||||
{
|
||||
skillsComp.Skills.Clear();
|
||||
foreach (SkillType skill in Enum.GetValues(typeof(SkillType)))
|
||||
{
|
||||
skillsComp.Skills[skill] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (skill, level) in initial.InitialSkills)
|
||||
{
|
||||
skillsComp.Skills[skill] = Math.Clamp(level, 1, 4);
|
||||
}
|
||||
|
||||
foreach (var skill in initial.RandomSkills)
|
||||
{
|
||||
var randomLevel = _random.Next(initial.RandomMinLevel, initial.RandomMaxLevel + 1);
|
||||
skillsComp.Skills[skill] = Math.Clamp(randomLevel, 1, 4);
|
||||
}
|
||||
|
||||
foreach (var (skill, addLevel) in initial.AddSkills)
|
||||
{
|
||||
var currentLevel = skillsComp.Skills.GetValueOrDefault(skill, 1);
|
||||
skillsComp.Skills[skill] = Math.Clamp(currentLevel + addLevel, 1, 4);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes all entity skills within the specified range
|
||||
/// </summary>
|
||||
public void RandomizeAllSkills(EntityUid uid, SkillsComponent? comp = null, int minLevel = 1, int maxLevel = 4)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
minLevel = Math.Clamp(minLevel, 1, 4);
|
||||
maxLevel = Math.Clamp(maxLevel, 1, 4);
|
||||
|
||||
foreach (SkillType skill in Enum.GetValues(typeof(SkillType)))
|
||||
{
|
||||
comp.Skills[skill] = _random.Next(minLevel, maxLevel + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all skills and points from one entity to another
|
||||
/// </summary>
|
||||
public void CopySkills(EntityUid fromEntity, EntityUid toEntity, SkillsComponent? fromSkills = null)
|
||||
{
|
||||
if (!Resolve(fromEntity, ref fromSkills, false))
|
||||
return;
|
||||
|
||||
var toSkills = EnsureComp<SkillsComponent>(toEntity);
|
||||
|
||||
toSkills.Skills.Clear();
|
||||
foreach (var (skill, level) in fromSkills.Skills)
|
||||
{
|
||||
toSkills.Skills[skill] = level;
|
||||
}
|
||||
|
||||
toSkills.UnspentPoints = fromSkills.UnspentPoints;
|
||||
toSkills.SpentPoints = fromSkills.SpentPoints;
|
||||
|
||||
Dirty(toEntity, toSkills);
|
||||
|
||||
Log.Debug($"Copied skills from {ToPrettyString(fromEntity)} to {ToPrettyString(toEntity)}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
57
Content.Shared/_WL/Skills/SharedSkillsSystem.cs
Normal file
57
Content.Shared/_WL/Skills/SharedSkillsSystem.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared._WL.Skills;
|
||||
|
||||
public abstract partial class SharedSkillsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeSkills();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a skill prototype.
|
||||
/// </summary>
|
||||
public SkillPrototype GetSkillPrototype(SkillType skill)
|
||||
{
|
||||
var skillId = GetSkillPrototypeId(skill);
|
||||
return _prototype.Index<SkillPrototype>(skillId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the prototype ID for SkillType.
|
||||
/// </summary>
|
||||
public string GetSkillPrototypeId(SkillType skill)
|
||||
{
|
||||
foreach (var skillProto in _prototype.EnumeratePrototypes<SkillPrototype>())
|
||||
{
|
||||
if (skillProto.SkillType == skill)
|
||||
return skillProto.ID;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"No skill prototype found for SkillType: {skill}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of costs for a skill.
|
||||
/// </summary>
|
||||
public int[] GetSkillCost(SkillType skill)
|
||||
{
|
||||
var prototype = GetSkillPrototype(skill);
|
||||
return prototype.Costs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color for the skill.
|
||||
/// </summary>
|
||||
public Color GetSkillColor(SkillType skill)
|
||||
{
|
||||
var prototype = GetSkillPrototype(skill);
|
||||
return prototype.Color;
|
||||
}
|
||||
}
|
||||
25
Content.Shared/_WL/Skills/SkillsEnum.cs
Normal file
25
Content.Shared/_WL/Skills/SkillsEnum.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Content.Shared._WL.Skills;
|
||||
|
||||
public enum SkillType : byte
|
||||
{
|
||||
Bureaucracy,
|
||||
Eva,
|
||||
Piloting,
|
||||
MechPiloting,
|
||||
IT,
|
||||
Atmospherics,
|
||||
Construction,
|
||||
Electrical,
|
||||
Generators,
|
||||
Anatomy,
|
||||
Chemistry,
|
||||
Medicine,
|
||||
ComplexDevices,
|
||||
Science,
|
||||
Forensics,
|
||||
Combat,
|
||||
Weapons,
|
||||
Botany,
|
||||
Cooking,
|
||||
Mixology
|
||||
}
|
||||
23
Content.Shared/_WL/Skills/SkillsEvents.cs
Normal file
23
Content.Shared/_WL/Skills/SkillsEvents.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._WL.Skills;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct SkillsAddedEvent();
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SelectSkillPressedEvent : EntityEventArgs
|
||||
{
|
||||
public NetEntity Uid { get; }
|
||||
public SkillType Skill { get; }
|
||||
public int TargetLevel { get; }
|
||||
public string? JobId { get; }
|
||||
|
||||
public SelectSkillPressedEvent(NetEntity uid, SkillType skill, int targetLevel, string? jobId = null)
|
||||
{
|
||||
Uid = uid;
|
||||
Skill = skill;
|
||||
TargetLevel = targetLevel;
|
||||
JobId = jobId;
|
||||
}
|
||||
}
|
||||
102
Content.Shared/_WL/Skills/Ui/SkillsEuiMessages.cs
Normal file
102
Content.Shared/_WL/Skills/Ui/SkillsEuiMessages.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._WL.Skills.UI;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsEuiState : EuiStateBase
|
||||
{
|
||||
public readonly string JobId;
|
||||
public readonly Dictionary<byte, int> CurrentSkills;
|
||||
public readonly Dictionary<byte, int> DefaultSkills;
|
||||
public readonly int TotalPoints;
|
||||
public readonly int SpentPoints;
|
||||
|
||||
public SkillsEuiState(string jobId, Dictionary<byte, int> currentSkills,
|
||||
Dictionary<byte, int> defaultSkills, int totalPoints, int spentPoints)
|
||||
{
|
||||
JobId = jobId;
|
||||
CurrentSkills = currentSkills;
|
||||
DefaultSkills = defaultSkills;
|
||||
TotalPoints = totalPoints;
|
||||
SpentPoints = spentPoints;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsEuiClosedMessage : EuiMessageBase
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsEuiSkillChangedMessage : EuiMessageBase
|
||||
{
|
||||
public readonly string JobId;
|
||||
public readonly byte SkillKey;
|
||||
public readonly int NewLevel;
|
||||
|
||||
public SkillsEuiSkillChangedMessage(string jobId, byte skillKey, int newLevel)
|
||||
{
|
||||
JobId = jobId;
|
||||
SkillKey = skillKey;
|
||||
NewLevel = newLevel;
|
||||
}
|
||||
}
|
||||
|
||||
#region Admin
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsAdminEuiState : EuiStateBase
|
||||
{
|
||||
public readonly bool HasSkills;
|
||||
public readonly Dictionary<byte, int> CurrentSkills;
|
||||
public readonly int SpentPoints;
|
||||
public readonly int BonusPoints;
|
||||
public readonly string CurrentJob;
|
||||
public readonly string EntityName;
|
||||
|
||||
public SkillsAdminEuiState(bool hasSkills, Dictionary<byte, int> currentSkills,
|
||||
int spentPoints, int bonusPoints, string currentJob, string entityName)
|
||||
{
|
||||
HasSkills = hasSkills;
|
||||
CurrentSkills = currentSkills;
|
||||
SpentPoints = spentPoints;
|
||||
BonusPoints = bonusPoints;
|
||||
CurrentJob = currentJob;
|
||||
EntityName = entityName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsAdminEuiClosedMessage : EuiMessageBase
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsAdminEuiResetMessage : EuiMessageBase
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsAdminEuiSkillChangedMessage : EuiMessageBase
|
||||
{
|
||||
public readonly byte SkillKey;
|
||||
public readonly int NewLevel;
|
||||
|
||||
public SkillsAdminEuiSkillChangedMessage(byte skillKey, int newLevel)
|
||||
{
|
||||
SkillKey = skillKey;
|
||||
NewLevel = newLevel;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SkillsAdminEuiPointsChangedMessage : EuiMessageBase
|
||||
{
|
||||
public readonly int NewBonusPoints;
|
||||
|
||||
public SkillsAdminEuiPointsChangedMessage(int newBonusPoints)
|
||||
{
|
||||
NewBonusPoints = newBonusPoints;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
81
Resources/Locale/ru-RU/_WL/skills/skills.ftl
Normal file
81
Resources/Locale/ru-RU/_WL/skills/skills.ftl
Normal file
@@ -0,0 +1,81 @@
|
||||
# UI
|
||||
skills-admin-window-title = Управление навыками
|
||||
skills-admin-target = Цель:
|
||||
skills-admin-job = Должность:
|
||||
skills-admin-points-control = Управление очками
|
||||
skills-admin-bonus-points = Бонусные очки:
|
||||
skills-admin-spent-points = Потраченные очки:
|
||||
skills-admin-apply-points = Применить очки
|
||||
skills-admin-close = Закрыть
|
||||
skills-admin-reset-all = Сбросить всё
|
||||
skills-admin-skills-list = Управление навыками
|
||||
skills-admin-skills-no-job = <НЕТУ ДОЛЖНОСТИ>
|
||||
skills-admin-verb-manage = Управление навыками
|
||||
|
||||
skill-window = Очки
|
||||
skill-window-tooltip = Распределение навыков для роли.
|
||||
skill-level-1 = Новичок
|
||||
skill-level-2 = Любитель
|
||||
skill-level-3 = Специалист
|
||||
skill-level-4 = Профессионал
|
||||
|
||||
skill-available-points = Доступное количество очков:
|
||||
skill-total-cost = Потрачено очков: {$cost}
|
||||
skill-locked-default = Заблокирован
|
||||
skill-free-default = Бесплатно (дефолтный уровень)
|
||||
skill-free-upgraded = Бесплатно
|
||||
|
||||
skills-forced-window-title = Настройка навыков
|
||||
skills-forced-warning = Вы должны распределить все доступные очки навыков перед началом игры
|
||||
skills-confirm-button = Подтвердить
|
||||
skills-unspent-warning = У вас остались неиспользованные очки! Потратьте их все для продолжения.
|
||||
|
||||
character-info-skills-button = Навыки
|
||||
|
||||
skill-bureaucracy = Бюрократия
|
||||
skill-eva = Внекорабельная деятельность
|
||||
skill-piloting = Пилотирование
|
||||
skill-mechpiloting = Управление мехами
|
||||
skill-it = Информационные технологии
|
||||
skill-atmospherics = Атмосферика
|
||||
skill-construction = Строительство
|
||||
skill-electrical = Электротехника
|
||||
skill-generators = Генераторы
|
||||
skill-anatomy = Анатомия
|
||||
skill-chemistry = Химия
|
||||
skill-medicine = Медицина
|
||||
skill-complexdevices = Сложные устройства
|
||||
skill-science = Наука
|
||||
skill-forensics = Криминалистика
|
||||
skill-combat = Ближний бой
|
||||
skill-weapons = Обращение с оружием
|
||||
skill-botany = Ботаника
|
||||
skill-cooking = Готовка
|
||||
skill-mixology = Миксология
|
||||
|
||||
skill-bureaucracy-desc = Умение работать с документами и знание корпоративных регуляций.
|
||||
skill-eva-desc = Знания и опыт работы со скафандрами и в космосе. Влияет на эффективность внекорабельной деятельности.
|
||||
skill-piloting-desc = Знание устройства космических кораблей и умение их пилотировать. Определяет способности в управлении шаттлами.
|
||||
skill-mechpiloting-desc = Умение управлять мехами и экзокостюмами. Требуется для эффективного использования роботизированных костюмов.
|
||||
skill-it-desc = Знания в сферах телекоммуникаций, связи, программирования и работы с искусственным интеллектом.
|
||||
skill-atmospherics-desc = Знания в области физики газов и работы с трубопроводами. Влияет на эффективность работы с атмосферными системами.
|
||||
skill-construction-desc = Умения в сфере проектировки, строительства и работы с различными материалами. Определяет качество и скорость строительства.
|
||||
skill-electrical-desc = Знание электроприборов и проводки. Влияет на умение ремонтировать и обслуживать электрические системы.
|
||||
skill-generators-desc = Умение работать с различными генераторами энергии. Определяет эффективность обслуживания энергосистем.
|
||||
skill-anatomy-desc = Знание работы и структуры тела и органов. Влияет на понимание биологических процессов и медицинские навыки.
|
||||
skill-chemistry-desc = Опыт работы с химическим оборудованием, химикатами и их взаимодействиями. Не включает знание медицины.
|
||||
skill-medicine-desc = Умение лечить людей и ксенорасы, работа с медицинским оборудованием. Определяет эффективность медицинской помощи.
|
||||
skill-complexdevices-desc = Умение собирать сложные устройства, работать с высокотехнологическим оборудованием и обслуживать робототехнику.
|
||||
skill-science-desc = Опыт работы в области науки, разработок и умение применять научные методы. Влияет на исследовательские способности.
|
||||
skill-forensics-desc = Умение раскрывать преступления: работа с уликами, вещественными доказательствами, осмотр места преступления.
|
||||
skill-combat-desc = Знание боевых приёмов и умение вести рукопашный бой, точно бросать различные предметы.
|
||||
skill-weapons-desc = Опыт в стрельбе и ведении боя. Определяет, каким оружием может пользоваться персонаж.
|
||||
skill-botany-desc = Умение ухаживать за растениями и знания в сфере ботанической науки, селекции и мутаций.
|
||||
skill-cooking-desc = Мастерство в приготовлении различных блюд и пользовании кухонным оборудованием.
|
||||
skill-mixology-desc = Умение в разливе, смешивании напитков и пользовании барным оборудованием.
|
||||
|
||||
# System
|
||||
skills-admin-notify-skills-changed = Ваши навыки были изменены! Откройте меню навыков для просмотра изменений.
|
||||
skills-admin-notify-points-added = Вам были начислены дополнительный очки навыков!
|
||||
skills-admin-notify-points-removed = С вас были списаны очки навыков!
|
||||
skills-admin-notify-skills-reset = Ваши навыки были полностью сброшены!
|
||||
@@ -30,6 +30,12 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
Forensics: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: IAAGear
|
||||
|
||||
@@ -26,6 +26,18 @@
|
||||
- Engineering
|
||||
- External
|
||||
- Atmospherics
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 4
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 3
|
||||
IT: 2
|
||||
Atmospherics: 3
|
||||
Construction: 3
|
||||
Electrical: 3
|
||||
Generators: 3
|
||||
ComplexDevices: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: SeniorEngineerGear
|
||||
|
||||
@@ -25,6 +25,14 @@
|
||||
- Medical
|
||||
- Maintenance
|
||||
- Chemistry
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 3
|
||||
Anatomy: 3
|
||||
Medicine: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: SeniorPhysicianGear
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
access:
|
||||
- Research
|
||||
- Maintenance
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
IT: 2
|
||||
Chemistry: 2
|
||||
ComplexDevices: 2
|
||||
Science: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: SeniorResearcherGear
|
||||
|
||||
@@ -28,6 +28,16 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 4
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 3
|
||||
Anatomy: 2
|
||||
Medicine: 3
|
||||
Combat: 2
|
||||
Weapons: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: BrigmedicGear
|
||||
|
||||
@@ -29,6 +29,16 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 3
|
||||
Piloting: 3
|
||||
Forensics: 2
|
||||
Combat: 3
|
||||
Weapons: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: PilotGear
|
||||
|
||||
@@ -34,6 +34,16 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 3
|
||||
Piloting: 2
|
||||
Forensics: 2
|
||||
Combat: 3
|
||||
Weapons: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: SeniorOfficerGear
|
||||
|
||||
@@ -892,6 +892,11 @@
|
||||
attributes:
|
||||
proper: true
|
||||
gender: male
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
initialSkills:
|
||||
Mixology: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
name: Tropico
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
tags:
|
||||
- DoorBumpOpener
|
||||
- StunImmune
|
||||
- type: InitialSkills # WL-Skills
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
||||
@@ -68,7 +68,29 @@
|
||||
- NamesMilitaryFirstLeader
|
||||
- NamesMilitaryLast
|
||||
nameFormat: name-format-ert
|
||||
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
randomMaxLevel: 3
|
||||
initialSkills:
|
||||
Bureaucracy: 4
|
||||
Eva: 3
|
||||
Piloting: 4
|
||||
MechPiloting: 3
|
||||
IT: 4
|
||||
Atmospherics: 4
|
||||
Construction: 4
|
||||
Electrical: 4
|
||||
Generators: 4
|
||||
Anatomy: 4
|
||||
Chemistry: 4
|
||||
Medicine: 4
|
||||
ComplexDevices: 4
|
||||
Science: 4
|
||||
Forensics: 4
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
randomSkills: [Botany, Cooking, Mixology]
|
||||
# WL-Skills-end
|
||||
|
||||
## ERT Leader
|
||||
|
||||
@@ -107,6 +129,29 @@
|
||||
- NamesMilitaryFirstLeader
|
||||
- NamesMilitaryLast
|
||||
nameFormat: name-format-ert
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
randomMaxLevel: 3
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 4
|
||||
MechPiloting: 3
|
||||
IT: 3
|
||||
Atmospherics: 2
|
||||
Construction: 2
|
||||
Electrical: 2
|
||||
Generators: 2
|
||||
Anatomy: 2
|
||||
Chemistry: 2
|
||||
Medicine: 3
|
||||
ComplexDevices: 2
|
||||
Science: 2
|
||||
Forensics: 3
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
randomSkills: [Botany, Cooking, Mixology]
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
id: RandomHumanoidSpawnerERTLeaderEVA
|
||||
@@ -264,6 +309,29 @@
|
||||
- type: Loadout
|
||||
prototypes: [ ERTJanitorGear ]
|
||||
roleLoadout: [ RoleSurvivalExtended ]
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
randomMaxLevel: 3
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 3
|
||||
MechPiloting: 3
|
||||
IT: 2
|
||||
Atmospherics: 2
|
||||
Construction: 2
|
||||
Electrical: 2
|
||||
Generators: 2
|
||||
Anatomy: 2
|
||||
Chemistry: 3
|
||||
Medicine: 3
|
||||
ComplexDevices: 2
|
||||
Science: 2
|
||||
Forensics: 3
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
randomSkills: [Botany, Cooking, Mixology]
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
id: RandomHumanoidSpawnerERTJanitorEVA
|
||||
@@ -330,6 +398,29 @@
|
||||
- type: Loadout
|
||||
prototypes: [ ERTEngineerGear ]
|
||||
roleLoadout: [ RoleSurvivalExtended ]
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
randomMaxLevel: 3
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 4
|
||||
MechPiloting: 3
|
||||
IT: 4
|
||||
Atmospherics: 4
|
||||
Construction: 4
|
||||
Electrical: 4
|
||||
Generators: 4
|
||||
Anatomy: 2
|
||||
Chemistry: 2
|
||||
Medicine: 3
|
||||
ComplexDevices: 4
|
||||
Science: 2
|
||||
Forensics: 3
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
randomSkills: [Botany, Cooking, Mixology]
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
id: RandomHumanoidSpawnerERTEngineerEVA
|
||||
@@ -398,6 +489,29 @@
|
||||
- type: Loadout
|
||||
prototypes: [ ERTSecurityGear ]
|
||||
roleLoadout: [ RoleSurvivalExtended ]
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
randomMaxLevel: 3
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 3
|
||||
MechPiloting: 3
|
||||
IT: 3
|
||||
Atmospherics: 2
|
||||
Construction: 2
|
||||
Electrical: 2
|
||||
Generators: 2
|
||||
Anatomy: 2
|
||||
Chemistry: 2
|
||||
Medicine: 3
|
||||
ComplexDevices: 2
|
||||
Science: 2
|
||||
Forensics: 3
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
randomSkills: [Botany, Cooking, Mixology]
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
id: RandomHumanoidSpawnerERTSecurityEVA
|
||||
@@ -487,6 +601,29 @@
|
||||
- type: Loadout
|
||||
prototypes: [ ERTMedicalGear ]
|
||||
roleLoadout: [ RoleSurvivalExtended ]
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
randomMaxLevel: 3
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 3
|
||||
MechPiloting: 3
|
||||
IT: 2
|
||||
Atmospherics: 2
|
||||
Construction: 2
|
||||
Electrical: 2
|
||||
Generators: 2
|
||||
Anatomy: 4
|
||||
Chemistry: 4
|
||||
Medicine: 4
|
||||
ComplexDevices: 2
|
||||
Science: 2
|
||||
Forensics: 3
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
randomSkills: [Botany, Cooking, Mixology]
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
id: RandomHumanoidSpawnerERTMedicalEVA
|
||||
|
||||
@@ -103,6 +103,8 @@
|
||||
normalState: human_small_fire # Corvax WL /tg/ resprite
|
||||
alternateState: human_big_fire # Corvax WL /tg/ resprite
|
||||
- type: FlashImmunity
|
||||
- type: InitialSkills # WL-Skills
|
||||
randomizeAllSkills: true # WL-Skills
|
||||
- type: Inventory
|
||||
femaleDisplacements:
|
||||
jumpsuit:
|
||||
|
||||
@@ -267,6 +267,31 @@
|
||||
- NamesNinjaTitle
|
||||
- NamesNinja
|
||||
nameFormat: name-format-ninja
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
initialSkills:
|
||||
Eva: 3
|
||||
MechPiloting: 3
|
||||
Combat: 4
|
||||
randomSkills:
|
||||
- Bureaucracy
|
||||
- Piloting
|
||||
- IT
|
||||
- Atmospherics
|
||||
- Construction
|
||||
- Electrical
|
||||
- Generators
|
||||
- Anatomy
|
||||
- Chemistry
|
||||
- Medicine
|
||||
- ComplexDevices
|
||||
- Science
|
||||
- Forensics
|
||||
- Weapons
|
||||
- Botany
|
||||
- Cooking
|
||||
- Mixology
|
||||
# WL-Skills-end
|
||||
mindRoles:
|
||||
- MindRoleNinja
|
||||
- type: DynamicRuleCost
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
id: Nukeops
|
||||
components:
|
||||
- type: GameRule
|
||||
minPlayers: 20
|
||||
minPlayers: 0
|
||||
- type: LoadMapRule
|
||||
mapPath: /Maps/Nonstations/nukieplanet.yml
|
||||
- type: AntagSelection
|
||||
@@ -135,6 +135,26 @@
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- Syndicate
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 4
|
||||
MechPiloting: 3
|
||||
IT: 3
|
||||
Atmospherics: 3
|
||||
Construction: 4
|
||||
Electrical: 4
|
||||
Generators: 3
|
||||
Anatomy: 3
|
||||
Chemistry: 3
|
||||
Medicine: 3
|
||||
ComplexDevices: 4
|
||||
Science: 2
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
# WL-Skills-end
|
||||
mindRoles:
|
||||
- MindRoleNukeopsCommander
|
||||
- prefRoles: [ NukeopsMedic ]
|
||||
@@ -152,6 +172,26 @@
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- Syndicate
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 4
|
||||
MechPiloting: 3
|
||||
IT: 3
|
||||
Atmospherics: 3
|
||||
Construction: 4
|
||||
Electrical: 4
|
||||
Generators: 3
|
||||
Anatomy: 3
|
||||
Chemistry: 3
|
||||
Medicine: 3
|
||||
ComplexDevices: 4
|
||||
Science: 2
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
# WL-Skills-end
|
||||
mindRoles:
|
||||
- MindRoleNukeopsMedic
|
||||
- prefRoles: [ Nukeops ]
|
||||
@@ -171,6 +211,26 @@
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- Syndicate
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
initialSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 4
|
||||
MechPiloting: 3
|
||||
IT: 3
|
||||
Atmospherics: 3
|
||||
Construction: 4
|
||||
Electrical: 4
|
||||
Generators: 3
|
||||
Anatomy: 3
|
||||
Chemistry: 3
|
||||
Medicine: 3
|
||||
ComplexDevices: 4
|
||||
Science: 2
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
# WL-Skills-end
|
||||
mindRoles:
|
||||
- MindRoleNukeops
|
||||
- type: DynamicRuleCost
|
||||
@@ -198,7 +258,7 @@
|
||||
id: Traitor
|
||||
components:
|
||||
- type: GameRule
|
||||
minPlayers: 5
|
||||
minPlayers: 0
|
||||
delay:
|
||||
min: 240
|
||||
max: 420
|
||||
@@ -214,6 +274,33 @@
|
||||
lateJoinAdditional: false
|
||||
mindRoles:
|
||||
- MindRoleTraitor
|
||||
# WL-Skills-start
|
||||
components:
|
||||
- type: InitialSkills
|
||||
overrideExisting: false
|
||||
initialSkills:
|
||||
Eva: 3
|
||||
MechPiloting: 3
|
||||
addSkills:
|
||||
Bureaucracy: 1
|
||||
Piloting: 1
|
||||
IT: 1
|
||||
Atmospherics: 1
|
||||
Construction: 1
|
||||
Electrical: 1
|
||||
Generators: 1
|
||||
Anatomy: 1
|
||||
Chemistry: 1
|
||||
Medicine: 1
|
||||
ComplexDevices: 1
|
||||
Science: 1
|
||||
Forensics: 1
|
||||
Combat: 1
|
||||
Weapons: 1
|
||||
Botany: 1
|
||||
Cooking: 1
|
||||
Mixology: 1
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
id: TraitorReinforcement
|
||||
@@ -228,6 +315,26 @@
|
||||
- prefRoles: [ Traitor ]
|
||||
mindRoles:
|
||||
- MindRoleTraitorReinforcement
|
||||
# WL-Skills-start
|
||||
components:
|
||||
- type: InitialSkills
|
||||
initialSkills:
|
||||
Eva: 3
|
||||
Piloting: 3
|
||||
MechPiloting: 3
|
||||
IT: 3
|
||||
Atmospherics: 3
|
||||
Construction: 3
|
||||
Electrical: 3
|
||||
Anatomy: 3
|
||||
Chemistry: 3
|
||||
Medicine: 3
|
||||
ComplexDevices: 3
|
||||
Science: 3
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
randomSkills: [Bureaucracy, Generators, Forensics, Botany, Cooking, Mixology]
|
||||
# WL-Skills-end
|
||||
|
||||
- type: entity
|
||||
id: Changeling
|
||||
@@ -280,6 +387,32 @@
|
||||
components:
|
||||
- type: Revolutionary
|
||||
- type: HeadRevolutionary
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
overrideExisting: false
|
||||
initialSkills:
|
||||
Eva: 3
|
||||
MechPiloting: 3
|
||||
addSkills:
|
||||
Bureaucracy: 1
|
||||
Piloting: 1
|
||||
IT: 1
|
||||
Atmospherics: 1
|
||||
Construction: 1
|
||||
Electrical: 1
|
||||
Generators: 1
|
||||
Anatomy: 1
|
||||
Chemistry: 1
|
||||
Medicine: 1
|
||||
ComplexDevices: 1
|
||||
Science: 1
|
||||
Forensics: 1
|
||||
Combat: 1
|
||||
Weapons: 1
|
||||
Botany: 1
|
||||
Cooking: 1
|
||||
Mixology: 1
|
||||
# WL-Skills-end
|
||||
mindRoles:
|
||||
- MindRoleHeadRevolutionary
|
||||
- type: DynamicRuleCost
|
||||
|
||||
@@ -31,6 +31,32 @@
|
||||
stealthy: true
|
||||
# components: # Corvax-MRP
|
||||
# - type: Pacified
|
||||
# WL-Skills-start
|
||||
- type: InitialSkills
|
||||
overrideExisting: false
|
||||
initialSkills:
|
||||
Eva: 3
|
||||
MechPiloting: 3
|
||||
addSkills:
|
||||
Bureaucracy: 1
|
||||
Piloting: 1
|
||||
IT: 1
|
||||
Atmospherics: 1
|
||||
Construction: 1
|
||||
Electrical: 1
|
||||
Generators: 1
|
||||
Anatomy: 1
|
||||
Chemistry: 1
|
||||
Medicine: 1
|
||||
ComplexDevices: 1
|
||||
Science: 1
|
||||
Forensics: 1
|
||||
Combat: 1
|
||||
Weapons: 1
|
||||
Botany: 1
|
||||
Cooking: 1
|
||||
Mixology: 1
|
||||
# WL-Skills-end
|
||||
mindRoles:
|
||||
- MindRoleThief
|
||||
briefing:
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: BoxingDay
|
||||
prototype: BoxCardboard
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Piloting: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: CargoTechGear
|
||||
|
||||
@@ -43,6 +43,13 @@
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: CommandStaff
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: QuartermasterGear
|
||||
|
||||
@@ -24,6 +24,16 @@
|
||||
- Salvage
|
||||
- Maintenance
|
||||
- External
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Eva: 3
|
||||
Piloting: 2
|
||||
Construction: 2
|
||||
Medicine: 2
|
||||
Science: 2
|
||||
Combat: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: SalvageSpecialistGear
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
supervisors: job-supervisors-everyone
|
||||
# access: # WL-Changes
|
||||
# - Maintenance
|
||||
bonusSkillPoints: 16 # WL-Skills / Why?
|
||||
|
||||
- type: startingGear
|
||||
id: PassengerGear
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
extendedAccess:
|
||||
- Kitchen
|
||||
- Hydroponics
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Chemistry: 2
|
||||
Weapons: 2
|
||||
Botany: 2
|
||||
Mixology: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: BartenderGear
|
||||
|
||||
@@ -23,6 +23,13 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: FourTwenty
|
||||
prototype: CannabisSeeds
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Chemistry: 2
|
||||
Science: 2
|
||||
Botany: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: BotanistGear
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
- !type:AgeRequirement
|
||||
minAge: 18
|
||||
# WL-Changes-AgeRequirement End
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ChaplainGear
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
extendedAccess:
|
||||
- Hydroponics
|
||||
- Bar
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Anatomy: 2
|
||||
Botany: 2
|
||||
Cooking: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ChefGear
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
interval: 10
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ SadTromboneImplant ]
|
||||
bonusSkillPoints: 10 # WL-Skills
|
||||
|
||||
- type: startingGear
|
||||
id: ClownGear
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: GarbageDay
|
||||
prototype: WeaponRevolverInspector
|
||||
bonusSkillPoints: 6 # WL-Skills
|
||||
|
||||
- type: startingGear
|
||||
id: JanitorGear
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
access:
|
||||
- Service
|
||||
- Maintenance
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 10
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: LibrarianGear
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
- type: MimePowers
|
||||
preventWriting: true
|
||||
- type: FrenchAccent
|
||||
bonusSkillPoints: 10 # WL-Skills
|
||||
|
||||
- type: startingGear
|
||||
id: MimeGear
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: MikuDay
|
||||
prototype: BoxPerformer
|
||||
bonusSkillPoints: 8 # WL-Skills
|
||||
|
||||
- type: startingGear
|
||||
id: MusicianGear
|
||||
|
||||
@@ -23,6 +23,14 @@
|
||||
- Kitchen
|
||||
extendedAccess:
|
||||
- Hydroponics
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Chemistry: 2
|
||||
Botany: 2
|
||||
Cooking: 2
|
||||
Mixology: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ServiceWorkerGear
|
||||
|
||||
@@ -41,6 +41,14 @@
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: CommandStaff
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 3
|
||||
Weapons: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: CaptainGear
|
||||
|
||||
@@ -65,6 +65,12 @@
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: CommandStaff
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
IT: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: HoPGear
|
||||
|
||||
@@ -23,6 +23,16 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: FirefighterDay
|
||||
prototype: FireAxe
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 4
|
||||
defaultSkills:
|
||||
Eva: 3
|
||||
IT: 3
|
||||
Atmospherics: 2
|
||||
Construction: 3
|
||||
Electrical: 3
|
||||
Generators: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: AtmosphericTechnicianGear
|
||||
|
||||
@@ -41,6 +41,18 @@
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: CommandStaff
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 4
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
IT: 3
|
||||
Atmospherics: 3
|
||||
Construction: 3
|
||||
Electrical: 3
|
||||
Generators: 3
|
||||
ComplexDevices: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ChiefEngineerGear
|
||||
|
||||
@@ -24,6 +24,17 @@
|
||||
- External
|
||||
extendedAccess:
|
||||
- Atmospherics
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 4
|
||||
defaultSkills:
|
||||
Eva: 3
|
||||
IT: 2
|
||||
Atmospherics: 2
|
||||
Construction: 3
|
||||
Electrical: 3
|
||||
Generators: 3
|
||||
ComplexDevices: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: StationEngineerGear
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
- Maintenance
|
||||
- Engineering
|
||||
- External
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
IT: 2
|
||||
Atmospherics: 2
|
||||
Construction: 2
|
||||
Electrical: 2
|
||||
Generators: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: TechnicalAssistantGear
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
- Medical
|
||||
- Chemistry
|
||||
- Maintenance
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Chemistry: 3
|
||||
Medicine: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ChemistGear
|
||||
|
||||
@@ -42,6 +42,15 @@
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: CommandStaff
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Anatomy: 3
|
||||
Chemistry: 2
|
||||
Medicine: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: CMOGear
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: DoctorDay
|
||||
prototype: WehMedipen
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 2
|
||||
Anatomy: 3
|
||||
Medicine: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: DoctorGear
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
access:
|
||||
- Medical
|
||||
- Maintenance
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Anatomy: 2
|
||||
Medicine: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: MedicalInternGear
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
- Maintenance
|
||||
extendedAccess:
|
||||
- Chemistry
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Eva: 3
|
||||
Anatomy: 2
|
||||
Medicine: 3
|
||||
Combat: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ParamedicGear
|
||||
|
||||
@@ -15,6 +15,29 @@
|
||||
jobEntity: StationAiBrain
|
||||
jobPreviewEntity: PlayerStationAiPreview
|
||||
applyTraits: false
|
||||
# WL-Skills-start
|
||||
defaultSkills:
|
||||
Bureaucracy: 4
|
||||
Eva: 3
|
||||
Piloting: 4
|
||||
MechPiloting: 3
|
||||
IT: 4
|
||||
Atmospherics: 4
|
||||
Construction: 4
|
||||
Electrical: 4
|
||||
Generators: 4
|
||||
Anatomy: 4
|
||||
Chemistry: 4
|
||||
Medicine: 4
|
||||
ComplexDevices: 4
|
||||
Science: 4
|
||||
Forensics: 4
|
||||
Combat: 4
|
||||
Weapons: 4
|
||||
Botany: 4
|
||||
Cooking: 4
|
||||
Mixology: 4
|
||||
# WL-Skills-end
|
||||
|
||||
- type: job
|
||||
id: Borg
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
access:
|
||||
- Research
|
||||
- Maintenance
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Science: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ResearchAssistantGear
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: CommandStaff
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
IT: 2
|
||||
ComplexDevices: 3
|
||||
Science: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ResearchDirectorGear
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
access:
|
||||
- Research
|
||||
- Maintenance
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 10
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
ComplexDevices: 2
|
||||
Science: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ScientistGear
|
||||
|
||||
@@ -34,6 +34,16 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 3
|
||||
IT: 2
|
||||
Forensics: 3
|
||||
Combat: 3
|
||||
Weapons: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: DetectiveGear
|
||||
|
||||
@@ -43,6 +43,16 @@
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: CommandStaff
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Piloting: 2
|
||||
Forensics: 2
|
||||
Combat: 3
|
||||
Weapons: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: HoSGear
|
||||
|
||||
@@ -30,6 +30,13 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 4
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Combat: 2
|
||||
Weapons: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: SecurityCadetGear
|
||||
|
||||
@@ -32,6 +32,15 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Eva: 3
|
||||
Forensics: 2
|
||||
Combat: 3
|
||||
Weapons: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: SecurityOfficerGear
|
||||
|
||||
@@ -32,6 +32,15 @@
|
||||
special:
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ MindShieldImplant ]
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Bureaucracy: 3
|
||||
Eva: 3
|
||||
Forensics: 2
|
||||
Combat: 3
|
||||
Weapons: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: WardenGear
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: BoxingDay
|
||||
prototype: ClothingHandsGlovesBoxingRigged
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Combat: 3
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: BoxerGear
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
- Maintenance
|
||||
extendedAccess:
|
||||
- Chemistry
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
Chemistry: 2
|
||||
Combat: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: PsychologistGear
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
access:
|
||||
- Service
|
||||
- Maintenance
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 8
|
||||
defaultSkills:
|
||||
Bureaucracy: 2
|
||||
IT: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ReporterGear
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
- !type:GiveItemOnHolidaySpecial
|
||||
holiday: MonkeyDay
|
||||
prototype: MonkeyCubeBox
|
||||
# WL-Skills-start
|
||||
bonusSkillPoints: 6
|
||||
defaultSkills:
|
||||
Anatomy: 2
|
||||
Medicine: 2
|
||||
# WL-Skills-end
|
||||
|
||||
- type: startingGear
|
||||
id: ZookeeperGear
|
||||
|
||||
169
Resources/Prototypes/_WL/Skills/racialskillbonus.yml
Normal file
169
Resources/Prototypes/_WL/Skills/racialskillbonus.yml
Normal file
@@ -0,0 +1,169 @@
|
||||
- type: racialSkillBonus
|
||||
id: HumanSkillBonuses
|
||||
species: Human
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 1
|
||||
40: 1
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: ReptilianSkillBonuses
|
||||
species: Reptilian
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 2
|
||||
40: 2
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: DionaSkillBonuses
|
||||
species: Diona
|
||||
ageBonuses:
|
||||
22: 1
|
||||
40: 1
|
||||
80: 2
|
||||
150: 2
|
||||
250: 2
|
||||
400: 4
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: SlimePersonSkillBonuses
|
||||
species: SlimePerson
|
||||
ageBonuses:
|
||||
22: 1
|
||||
24: 1
|
||||
30: 1
|
||||
40: 1
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: FelinidSkillBonuses
|
||||
species: Felinid
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 2
|
||||
40: 1
|
||||
55: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: HumanoidKidanesSkillBonuses
|
||||
species: HumanoidKidanes
|
||||
ageBonuses:
|
||||
22: 4
|
||||
24: 2
|
||||
30: 2
|
||||
40: 1
|
||||
55: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: VulpkaninSkillBonuses
|
||||
species: Vulpkanin
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 1
|
||||
40: 1
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: TajaranSkillBonuses
|
||||
species: Tajaran
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 1
|
||||
30: 1
|
||||
40: 1
|
||||
55: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: AndroidSkillBonuses
|
||||
species: Android
|
||||
ageBonuses:
|
||||
22: 4
|
||||
24: 2
|
||||
30: 2
|
||||
40: 2
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: MurineSkillBonuses
|
||||
species: Murine
|
||||
ageBonuses:
|
||||
22: 4
|
||||
24: 4
|
||||
30: 2
|
||||
40: 2
|
||||
55: 2
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: MothSkillBonuses
|
||||
species: Moth
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 2
|
||||
40: 1
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: CischiSkillBonuses
|
||||
species: Cischi
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 2
|
||||
40: 1
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: ArachnidSkillBonuses
|
||||
species: Arachnid
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 1
|
||||
40: 1
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: GolemSkillBonuses
|
||||
species: Golem
|
||||
ageBonuses:
|
||||
22: 1
|
||||
40: 1
|
||||
80: 2
|
||||
150: 2
|
||||
250: 2
|
||||
400: 4
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: AkulaSkillBonuses
|
||||
species: Akula
|
||||
ageBonuses:
|
||||
22: 2
|
||||
24: 2
|
||||
30: 2
|
||||
40: 2
|
||||
55: 1
|
||||
70: 1
|
||||
|
||||
- type: racialSkillBonus
|
||||
id: IpcSkillBonuses
|
||||
species: Ipc
|
||||
ageBonuses:
|
||||
1: 4
|
||||
18: 2
|
||||
30: 4
|
||||
60: 2
|
||||
119
Resources/Prototypes/_WL/Skills/skills.yml
Normal file
119
Resources/Prototypes/_WL/Skills/skills.yml
Normal file
@@ -0,0 +1,119 @@
|
||||
- type: skill
|
||||
id: Bureaucracy
|
||||
skillType: Bureaucracy
|
||||
costs: [0, 1, 2, 2]
|
||||
color: "#eed2d2"
|
||||
|
||||
- type: skill
|
||||
id: Eva
|
||||
skillType: Eva
|
||||
costs: [0, 0, 4, 0]
|
||||
color: "#c9daf8"
|
||||
|
||||
- type: skill
|
||||
id: Piloting
|
||||
skillType: Piloting
|
||||
costs: [0, 2, 2, 4]
|
||||
color: "#cad6e4"
|
||||
|
||||
- type: skill
|
||||
id: MechPiloting
|
||||
skillType: MechPiloting
|
||||
costs: [0, 0, 2, 0]
|
||||
color: "#e0c9e6"
|
||||
|
||||
- type: skill
|
||||
id: IT
|
||||
skillType: IT
|
||||
costs: [0, 1, 2, 2]
|
||||
color: "#d5a6bd"
|
||||
|
||||
- type: skill
|
||||
id: Atmospherics
|
||||
skillType: Atmospherics
|
||||
costs: [0, 2, 2, 4]
|
||||
color: "#b4e4d4"
|
||||
|
||||
- type: skill
|
||||
id: Construction
|
||||
skillType: Construction
|
||||
costs: [0, 2, 2, 2]
|
||||
color: "#f0d5c7"
|
||||
|
||||
- type: skill
|
||||
id: Electrical
|
||||
skillType: Electrical
|
||||
costs: [0, 4, 2, 2]
|
||||
color: "#fae9c1"
|
||||
|
||||
- type: skill
|
||||
id: Generators
|
||||
skillType: Generators
|
||||
costs: [0, 4, 4, 4]
|
||||
color: "#fae9c1"
|
||||
|
||||
- type: skill
|
||||
id: Anatomy
|
||||
skillType: Anatomy
|
||||
costs: [0, 4, 4, 8]
|
||||
color: "#b7dff0"
|
||||
|
||||
- type: skill
|
||||
id: Chemistry
|
||||
skillType: Chemistry
|
||||
costs: [0, 4, 4, 8]
|
||||
color: "#ffc7b0"
|
||||
|
||||
- type: skill
|
||||
id: Medicine
|
||||
skillType: Medicine
|
||||
costs: [0, 2, 4, 8]
|
||||
color: "#a5d4eb"
|
||||
|
||||
- type: skill
|
||||
id: ComplexDevices
|
||||
skillType: ComplexDevices
|
||||
costs: [0, 2, 2, 4]
|
||||
color: "#cab5dd"
|
||||
|
||||
- type: skill
|
||||
id: Science
|
||||
skillType: Science
|
||||
costs: [0, 2, 2, 4]
|
||||
color: "#eca8eb"
|
||||
|
||||
- type: skill
|
||||
id: Forensics
|
||||
skillType: Forensics
|
||||
costs: [0, 1, 4, 2]
|
||||
color: "#e78080"
|
||||
|
||||
- type: skill
|
||||
id: Combat
|
||||
skillType: Combat
|
||||
costs: [0, 2, 2, 2]
|
||||
color: "#ea9999"
|
||||
|
||||
- type: skill
|
||||
id: Weapons
|
||||
skillType: Weapons
|
||||
costs: [0, 2, 4, 8]
|
||||
color: "#caa1a1"
|
||||
|
||||
- type: skill
|
||||
id: Botany
|
||||
skillType: Botany
|
||||
costs: [0, 1, 2, 2]
|
||||
color: "#b1e7a0"
|
||||
|
||||
- type: skill
|
||||
id: Cooking
|
||||
skillType: Cooking
|
||||
costs: [0, 1, 2, 2]
|
||||
color: "#ebb59f"
|
||||
|
||||
- type: skill
|
||||
id: Mixology
|
||||
skillType: Mixology
|
||||
costs: [0, 1, 2, 2]
|
||||
color: "#dacbbe"
|
||||
Reference in New Issue
Block a user