Knowledge system (#770)

* remove all requirements

* clean up and renaming to knowledge

* update code

* add admin knowledge verbs

* move shoes under pants

* knowledge based recipes

* clean up

* sewing knowledges

* knowledge dependencies

* knowledge learning objects

* more knowledge

* metallforging skill

* knowledge books

* start knowledges, T1 and T2 books in demiplanes

* remove coins from demiplanes

* roundstart knowledge
This commit is contained in:
Ed
2025-01-17 00:08:13 +03:00
committed by GitHub
parent 5af56d4655
commit 0783eb1380
59 changed files with 1002 additions and 685 deletions

View File

@@ -95,5 +95,11 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory CP14RitualBook = new("cp14-verb-categories-ritual-book", null); //CP14
public static readonly VerbCategory CP14CurrencyConvert = new("cp14-verb-categories-currency-converter", null); //CP14
public static readonly VerbCategory CP14KnowledgeAdd = new("cp14-verb-categories-knowledge-add", null); //CP14
public static readonly VerbCategory CP14KnowledgeRemove = new("cp14-verb-categories-knowledge-remove", null); //CP14
public static readonly VerbCategory CP14KnowledgeLearn = new("cp14-verb-categories-knowledge-learn", null); //CP14
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,94 @@
using System.Text;
using Content.Shared._CP14.Knowledge.Components;
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.DoAfter;
using Content.Shared.MagicMirror;
using Content.Shared.Paper;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Knowledge;
public abstract partial class SharedCP14KnowledgeSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly PaperSystem _paper = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14KnowledgePaperTextComponent, MapInitEvent>(OnPaperMapInit);
}
private void OnPaperMapInit(Entity<CP14KnowledgePaperTextComponent> ent, ref MapInitEvent args)
{
if (!TryComp<PaperComponent>(ent, out var paper))
return;
if (!TryComp<CP14KnowledgeLearningSourceComponent>(ent, out var knowledge))
return;
if (knowledge.Knowledges.Count <= 0)
return;
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-knowledge-book-pre-text"));
foreach (var k in knowledge.Knowledges)
{
if (!_proto.TryIndex(k, out var indexedKnowledge))
continue;
sb.Append($"\n{Loc.GetString(indexedKnowledge.Desc)}");
}
sb.Append($"\n\n{Loc.GetString("cp14-knowledge-book-post-text")}");
_paper.SetContent((ent, paper), sb.ToString());
paper.EditingDisabled = true;
}
public bool UseKnowledge(EntityUid uid,
ProtoId<CP14KnowledgePrototype> knowledge,
float factor = 1f,
CP14KnowledgeStorageComponent? knowledgeStorage = null)
{
if (!Resolve(uid, ref knowledgeStorage, false))
return false;
if (!knowledgeStorage.Knowledges.Contains(knowledge))
return false;
var ev = new CP14KnowledgeUsedEvent(uid, knowledge, factor);
RaiseLocalEvent(uid, ev);
return true;
}
public bool HasKnowledge(EntityUid uid,
ProtoId<CP14KnowledgePrototype> knowledge,
CP14KnowledgeStorageComponent? knowledgeStorage = null)
{
if (!Resolve(uid, ref knowledgeStorage, false))
return false;
return knowledgeStorage.Knowledges.Contains(knowledge);
}
}
public sealed class CP14KnowledgeUsedEvent : EntityEventArgs
{
public readonly EntityUid User;
public readonly ProtoId<CP14KnowledgePrototype> Knowledge;
public readonly float Factor;
public CP14KnowledgeUsedEvent(EntityUid uid, ProtoId<CP14KnowledgePrototype> knowledge, float factor)
{
User = uid;
Knowledge = knowledge;
Factor = factor;
}
}
[Serializable, NetSerializable]
public sealed partial class CP14KnowledgeLearnDoAfterEvent : DoAfterEvent
{
public ProtoId<CP14KnowledgePrototype> Knowledge;
public override DoAfterEvent Clone() => this;
}

View File

@@ -1,26 +0,0 @@
using Content.Shared._CP14.Skills.Prototypes;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skills.Components;
/// <summary>
/// a component that can be hung on an entity to immediately teach it some skills
/// </summary>
[UsedImplicitly]
public sealed partial class CP14AddSkillSpecial : JobSpecial
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public List<ProtoId<CP14SkillPrototype>> Skills = new();
public override void AfterEquip(EntityUid mob)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var skillSystem = entMan.System<SharedCP14SkillSystem>();
foreach (var skill in Skills)
{
skillSystem.TryLearnSkill(mob, skill);
}
}
}

View File

@@ -1,14 +0,0 @@
using Content.Shared._CP14.Skills.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skills.Components;
/// <summary>
/// The ability to add a skill to an entity and quickly teach it some skills
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14SkillSystem))]
public sealed partial class CP14AutoAddSkillComponent : Component
{
[DataField]
public List<ProtoId<CP14SkillPrototype>> Skills = new();
}

View File

@@ -1,27 +0,0 @@
using Content.Shared._CP14.Skills.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skills.Components;
/// <summary>
/// Limits the use of this entity behind certain skills
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14SkillSystem))]
public sealed partial class CP14SkillRequirementComponent : Component
{
/// <summary>
/// Is it necessary to have ALL the skills on the list to be able to use this entity?
/// If not, one of any skill will suffice
/// </summary>
[DataField]
public bool NeedAll = false;
/// <summary>
/// the chances of something going wrong when using wihout skill
/// </summary>
[DataField]
public float FuckupChance = 0.5f;
[DataField(required: true)]
public List<ProtoId<CP14SkillPrototype>> RequiredSkills = new();
}

View File

@@ -1,14 +0,0 @@
using Content.Shared._CP14.Skills.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skills.Components;
/// <summary>
/// a list of skills learned by this entity
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14SkillSystem))]
public sealed partial class CP14SkillsStorageComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public List<ProtoId<CP14SkillPrototype>> Skills { get; private set; }= new();
}

View File

@@ -1,23 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skills.Prototypes;
/// <summary>
///
/// </summary>
[Prototype("CP14Skill")]
public sealed partial class CP14SkillPrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
[DataField(required: true)]
public LocId? Name{ get; private set; }
[DataField]
public LocId? Desc{ get; private set; }
[DataField]
public ComponentRegistry Components = new();
}

View File

@@ -1,114 +0,0 @@
using Content.Shared._CP14.Skills.Components;
using Content.Shared._CP14.Skills.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared._CP14.Skills;
public partial class SharedCP14SkillSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14SkillsStorageComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CP14AutoAddSkillComponent, MapInitEvent>(AutoAddSkill);
}
private void AutoAddSkill(Entity<CP14AutoAddSkillComponent> ent, ref MapInitEvent args)
{
foreach (var skill in ent.Comp.Skills)
{
TryLearnSkill(ent, skill);
}
RemComp(ent, ent.Comp);
}
private void OnMapInit(Entity<CP14SkillsStorageComponent> ent, ref MapInitEvent args)
{
foreach (var skill in ent.Comp.Skills)
{
TryLearnSkill(ent, skill, force: true);
}
}
public bool HasEnoughSkillToUse(EntityUid user, EntityUid target, out List<ProtoId<CP14SkillPrototype>> missingSkills)
{
missingSkills = new();
if (!TryComp<CP14SkillRequirementComponent>(target, out var requirement) || requirement.RequiredSkills.Count == 0)
return true;
if (!TryComp<CP14SkillsStorageComponent>(user, out var skillStorage))
{
missingSkills = requirement.RequiredSkills;
return false;
}
var success = requirement.NeedAll;
foreach (var skill in requirement.RequiredSkills)
{
var hasSkill = skillStorage.Skills.Contains(skill);
if (requirement.NeedAll && !hasSkill)
{
missingSkills.Add(skill);
success = false;
}
else if (!requirement.NeedAll && hasSkill)
{
success = true;
}
else if (!requirement.NeedAll && !hasSkill)
{
missingSkills.Add(skill);
}
}
return success;
}
public bool TryLearnSkill(EntityUid uid, ProtoId<CP14SkillPrototype> skill, bool force = false)
{
if (!TryComp<CP14SkillsStorageComponent>(uid, out var skillStorage))
return false;
if (!skillStorage.Skills.Contains(skill))
{
skillStorage.Skills.Add(skill);
if (!force)
return false;
}
var proto = _proto.Index(skill);
EntityManager.AddComponents(uid, proto.Components);
return true;
}
public bool TryForgotSkill(EntityUid uid, ProtoId<CP14SkillPrototype> skill)
{
if (!TryComp<CP14SkillsStorageComponent>(uid, out var skillStorage))
return false;
if (!skillStorage.Skills.Contains(skill))
return false;
skillStorage.Skills.Remove(skill);
var proto = _proto.Index(skill);
EntityManager.RemoveComponents(uid, proto.Components);
return true;
}
}
public sealed partial class CP14TrySkillIssueEvent : EntityEventArgs
{
public readonly EntityUid User;
public CP14TrySkillIssueEvent(EntityUid uid)
{
User = uid;
}
}

View File

@@ -3,6 +3,7 @@
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
*/
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.Stacks;
using Content.Shared.Tag;
using Robust.Shared.Audio;
@@ -39,4 +40,10 @@ public sealed class CP14WorkbenchRecipePrototype : IPrototype
[DataField]
public string Solution = "food";
/// <summary>
/// If the player does not have this knowledge, the recipe will not be displayed in the workbench.
/// </summary>
[DataField]
public ProtoId<CP14KnowledgePrototype>? KnowledgeRequired;
}