Skill progression system [Draft] (#1056)

* fuck all

* clean up

* remove knowledge books

* Update base.yml

* cl

* fix #991

* fix

* Update subgamemodes.yml

* manual migration + recipes fix

* Update comoss.yml

* setup data

* setup skills systems

* more blacksmithing skills

* Update base.yml

* Create CP14SkillEffectAction.cs

* skill button returns

* base graph UI window

* skill tree drawing

* UI improve

* pyro setup

* skill trees selection, dragging control

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

* Create available.png

* skill description generation setup

* auto parsing skill names and descriptions

* Hydrosophistry

* water light fire and metamagic ported to skill tre

* ice dagger spell returns

* Update ice_dagger.yml

* delete old files

* Update modular_garde.yml

* ice arrow spell

* link graph with parallax

* show experience counter

* polish

* puf

* p

* pipap

* finally ready to merg?

* fix

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,13 +30,22 @@ public sealed partial class CP14WorkbenchSystem
if (!_proto.TryIndex(recipeId, out var indexedRecipe))
continue;
//if (indexedRecipe.KnowledgeRequired is not null)
//{
// if (!_knowledge.HasKnowledge(user, indexedRecipe.KnowledgeRequired.Value))
// continue;
//}
var canCraft = true;
var hidden = false;
var entry = new CP14WorkbenchUiRecipesEntry(recipeId, CanCraftRecipe(indexedRecipe, placedEntities, user));
foreach (var requirement in indexedRecipe.Requirements)
{
if (!requirement.CheckRequirement(EntityManager, _proto, placedEntities, user, indexedRecipe))
{
canCraft = false;
hidden = requirement.HideRecipe;
}
}
if (hidden)
continue;
var entry = new CP14WorkbenchUiRecipesEntry(recipeId, canCraft);
recipes.Add(entry);
}

View File

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