Ritualizm (#474)

* ritual cucumber setup

* entities requirements

* try graph???

* Revert "try graph???"

This reverts commit c90c6353cb.

* pipyau

* fixes

* yay, it works

* spawn effect

* regex lower message restrictions

* unique speakers support

* apply entity effect ritual

* ritual chalk

* Update SpawnEntity.cs

* ritual stability

* stability event

* add guidebook description to all ritual actions

* Readability added

* Update RequiredResource.cs

* finish describer

* clean up describer

* Update triggers.ftl

* cave ambient loop

* parry sound update

* rituals end start

* magic ambience add

* global sharedization

* Update phases.yml

* daytime requirement

* Update phases.yml

* start ritual

* fixes

* more ambient work

* rritual visualizer

* end ritual

* magic orbs!

* required orbs

* orbs design

* consume orbs

* setup neutral cluster triggers and edges

* listener proxy

* restucture graph

* fix time triggers

* healing cluster

* fixes

* Create CP14RitualTest.cs

* test errors for check test

* YEEEE

* Fuck triggers, its broken now, YAY

* triggers redo

* fix

* fix test

* Update CP14RitualTest.cs

* Update neutral_cluster.yml

* Update CP14RitualSystem.Triggers.cs

* clean up, documentation

* redo triggers again

* and another one

* species sacrifice trigger

* whitelist trigger

* fix

* describer refactor

* fix memory leaking  + hyperlinks

* dd
This commit is contained in:
Ed
2024-10-06 18:04:18 +03:00
committed by GitHub
parent a6532a2801
commit d2c5aa74b4
95 changed files with 2380 additions and 59 deletions

View File

@@ -0,0 +1,35 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Client.GameObjects;
namespace Content.Server._CP14.MagicRituals;
public partial class CP14ClientRitualSystem : CP14SharedRitualSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14MagicRitualComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnAppearanceChange(Entity<CP14MagicRitualComponent> ent, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!args.Sprite.LayerMapTryGet(ent.Comp.RitualLayerMap, out var ritualLayer))
return;
if (_appearance.TryGetData<Color>(ent, RitualVisuals.Color, out var ritualColor, args.Component))
{
args.Sprite.LayerSetColor(ritualLayer, ritualColor);
}
if (_appearance.TryGetData<bool>(ent, RitualVisuals.Enabled, out var enabled, args.Component))
{
args.Sprite.LayerSetVisible(ritualLayer, enabled);
}
}
}

View File

@@ -0,0 +1,53 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests._CP14;
#nullable enable
[TestFixture]
public sealed class CP14RitualTest
{
/// <summary>
/// States that all edges of the ritual phase have triggers.
/// </summary>
[Test]
public async Task RitualHasAllTriggersTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var compFactory = server.ResolveDependency<IComponentFactory>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
await server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
{
if (!proto.TryGetComponent(out CP14MagicRitualPhaseComponent? phase, compFactory))
continue;
if (phase.DeadEnd)
{
Assert.That(phase.Edges.Count == 0, $"{proto} is a ritual node, but has no paths to other nodes. Either add deadEnd = true, or add paths to other nodes.");
}
else
{
Assert.That(phase.Edges.Count > 0, $"{proto} is a deadEnd ritual node, but has {phase.Edges.Count} edges! Remove all edges, or make it a non dead-end node");
}
foreach (var edge in phase.Edges)
{
Assert.That(edge.Triggers.Count > 0, $"{{proto}} is ritual node, but edge to {edge.Target} has no triggers and cannot be activated.");
}
}
});
});
await pair.CleanReturnAsync();
}
}

View File

@@ -1,3 +1,5 @@
using Content.Shared.Speech;
namespace Content.Server.Speech;
public sealed class ListenEvent : EntityEventArgs

View File

@@ -0,0 +1,86 @@
using Content.Shared._CP14.MagicRitual;
using Content.Shared._CP14.MagicRitualTrigger.Triggers;
using Content.Shared.Humanoid;
using Content.Shared.Mobs;
using Content.Shared.Whitelist;
namespace Content.Server._CP14.MagicRitualTrigger;
public partial class CP14RitualTriggerSystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
private void InitializeSacrifice()
{
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
}
private void OnMobStateChanged(MobStateChangedEvent ev)
{
if (ev.NewMobState != MobState.Dead)
return;
var deathXform = Transform(ev.Target);
SacrificeSpecies(ev, deathXform);
SacrificeWhitelist(ev, deathXform);
}
private void SacrificeSpecies(MobStateChangedEvent ev, TransformComponent deathXform)
{
if (!TryComp<HumanoidAppearanceComponent>(ev.Target, out var humanoid))
return;
var query = EntityQueryEnumerator<CP14RitualSacrificeSpeciesTriggerComponent, CP14MagicRitualPhaseComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var sacrifice, out var phase, out var xform))
{
if (!deathXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
continue;
foreach (var trigger in sacrifice.Triggers)
{
if (distance > trigger.Range)
continue;
if (trigger.Edge is null)
continue;
if (trigger.Species != humanoid.Species)
continue;
TriggerRitualPhase((uid, phase), trigger.Edge.Value.Target);
}
}
}
private void SacrificeWhitelist(MobStateChangedEvent ev, TransformComponent deathXform)
{
var query = EntityQueryEnumerator<CP14RitualSacrificeWhitelistTriggerComponent, CP14MagicRitualPhaseComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var sacrifice, out var phase, out var xform))
{
if (!deathXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
continue;
foreach (var trigger in sacrifice.Triggers)
{
if (distance > trigger.Range)
continue;
if (trigger.Edge is null)
continue;
var entProto = MetaData(ev.Target).EntityPrototype;
if (entProto is null)
continue;
if (!_whitelist.IsValid(trigger.Whitelist, ev.Target))
continue;
TriggerRitualPhase((uid, phase), trigger.Edge.Value.Target);
}
}
}
}

View File

@@ -0,0 +1,43 @@
using Content.Shared._CP14.MagicRitual;
using Content.Shared._CP14.MagicRitualTrigger.Triggers;
using Robust.Shared.Timing;
namespace Content.Server._CP14.MagicRitualTrigger;
public partial class CP14RitualTriggerSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
private void InitializeTimer()
{
SubscribeLocalEvent<CP14RitualTimerTriggerComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(Entity<CP14RitualTimerTriggerComponent> ent, ref MapInitEvent args)
{
foreach (var trigger in ent.Comp.Triggers)
{
trigger.TriggerTime = _timing.CurTime + TimeSpan.FromSeconds(trigger.Delay);
}
}
private void UpdateTimer(float frameTime)
{
var query = EntityQueryEnumerator<CP14RitualTimerTriggerComponent, CP14MagicRitualPhaseComponent>();
while (query.MoveNext(out var uid, out var timer, out var phase))
{
foreach (var trigger in timer.Triggers)
{
if (_timing.CurTime < trigger.TriggerTime || trigger.TriggerTime == TimeSpan.Zero)
continue;
if (trigger.Edge is null)
continue;
TriggerRitualPhase((uid, phase), trigger.Edge.Value.Target);
trigger.TriggerTime = TimeSpan.Zero;
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Text.RegularExpressions;
using Content.Server.Speech;
using Content.Shared._CP14.MagicRitual;
using Content.Shared._CP14.MagicRitualTrigger.Triggers;
namespace Content.Server._CP14.MagicRitualTrigger;
public partial class CP14RitualTriggerSystem
{
private void InitializeVoice()
{
SubscribeLocalEvent<CP14RitualVoiceTriggerComponent, ListenEvent>(OnListenEvent);
}
private void OnListenEvent(Entity<CP14RitualVoiceTriggerComponent> ent, ref ListenEvent args)
{
if (!TryComp<CP14MagicRitualPhaseComponent>(ent, out var phase))
return;
// Lowercase the phrase and remove all punctuation marks
var message = Regex.Replace(args.Message.Trim().ToLower(), @"[^\w\s]", "");
foreach (var trigger in ent.Comp.Triggers)
{
var triggerMessage = Regex.Replace(trigger.Message.ToLower(), @"[^\w\s]", "");
if (triggerMessage != message)
continue;
if (trigger.Edge is null)
continue;
TriggerRitualPhase((ent.Owner, phase), trigger.Edge.Value.Target);
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Shared._CP14.MagicRitual;
using Content.Shared._CP14.MagicRitualTrigger;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.MagicRitualTrigger;
public partial class CP14RitualTriggerSystem : CP14SharedRitualTriggerSystem
{
public override void Initialize()
{
InitializeTimer();
InitializeVoice();
InitializeSacrifice();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateTimer(frameTime);
}
private void TriggerRitualPhase(Entity<CP14MagicRitualPhaseComponent> ent, EntProtoId nextPhase)
{
var evConfirmed = new CP14RitualTriggerEvent(nextPhase);
RaiseLocalEvent(ent, evConfirmed);
}
}

View File

@@ -0,0 +1,167 @@
using System.Text;
using Content.Server._CP14.MagicRituals.Components;
using Content.Shared._CP14.MagicRitual;
using Content.Shared.Paper;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.MagicRituals;
public sealed partial class CP14RitualSystem
{
[Dependency] private readonly PaperSystem _paper = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
private void InitializeDescriber()
{
SubscribeLocalEvent<CP14PaperPhaseDescriberComponent, GetVerbsEvent<Verb>>(OnDescriberVerbs);
SubscribeLocalEvent<CP14PaperPhaseDescriberComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CP14PaperPhaseDescriberComponent, ComponentShutdown>(OnShutdown);
}
private void OnShutdown(Entity<CP14PaperPhaseDescriberComponent> ent, ref ComponentShutdown args)
{
QueueDel(ent.Comp.CurrentPhase);
}
private void OnMapInit(Entity<CP14PaperPhaseDescriberComponent> ent, ref MapInitEvent args)
{
SetPhase(ent, ent.Comp.StartPhase);
}
private void SetPhase(Entity<CP14PaperPhaseDescriberComponent> ent, EntProtoId newProto, bool saveHistory = true)
{
var oldPhase = ent.Comp.CurrentPhase;
if (oldPhase is not null && saveHistory)
{
var oldProto = MetaData(oldPhase.Value).EntityPrototype;
if (oldProto is not null && oldProto != newProto)
{
ent.Comp.SearchHistory.Add(oldProto);
if (ent.Comp.SearchHistory.Count > 50)
ent.Comp.SearchHistory.RemoveAt(0);
}
}
QueueDel(oldPhase);
var newPhase = Spawn(newProto, MapCoordinates.Nullspace);
ent.Comp.CurrentPhase = newPhase;
if (!TryComp<PaperComponent>(ent, out var paper))
return;
_paper.SetContent((ent, paper), GetPhaseDescription(newPhase));
_audio.PlayPvs(ent.Comp.UseSound, ent);
}
private void BackPhase(Entity<CP14PaperPhaseDescriberComponent> ent)
{
if (ent.Comp.SearchHistory.Count > 0)
SetPhase(ent, ent.Comp.SearchHistory[^1], false);
}
private void OnDescriberVerbs(Entity<CP14PaperPhaseDescriberComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!TryComp<CP14MagicRitualPhaseComponent>(ent.Comp.CurrentPhase, out var phase))
return;
if (!TryComp<CP14MagicRitualPhaseComponent>(ent.Comp.CurrentPhase.Value, out var phaseComp))
return;
foreach (var edge in phaseComp.Edges)
{
if (!_proto.TryIndex(edge.Target, out var indexedTarget))
continue;
Verb verb = new()
{
Text = Loc.GetString("cp14-ritual-describer-verb-item", ("name", indexedTarget.Name)),
Category = VerbCategory.CP14RitualBook,
Priority = 1,
Act = () => SetPhase(ent, edge.Target),
};
args.Verbs.Add(verb);
}
foreach (var hyperlink in ent.Comp.Hyperlinks)
{
if (!_proto.TryIndex(hyperlink, out var indexedTarget))
continue;
Verb verb = new()
{
Text = Loc.GetString("cp14-ritual-describer-verb-hyperlink", ("name", indexedTarget.Name)),
Category = VerbCategory.CP14RitualBook,
Priority = 0,
Act = () => SetPhase(ent, hyperlink),
};
args.Verbs.Add(verb);
}
if (ent.Comp.SearchHistory.Count > 0)
{
Verb verb = new()
{
Text = Loc.GetString("cp14-ritual-describer-verb-back"),
Category = VerbCategory.CP14RitualBook,
Priority = -1,
Act = () => BackPhase(ent),
};
args.Verbs.Add(verb);
}
}
private string GetPhaseDescription(EntityUid uid)
{
if (!TryComp<CP14MagicRitualPhaseComponent>(uid, out var phase))
return string.Empty;
return GetPhaseDescription((uid, phase));
}
private string GetPhaseDescription(Entity<CP14MagicRitualPhaseComponent> ent)
{
var sb = new StringBuilder();
sb.Append($"[color=#e6a132][head=1]{MetaData(ent).EntityName}[/head][/color] \n \n");
sb.Append($"[italic]{MetaData(ent).EntityDescription}[/italic] \n \n");
sb.Append(Loc.GetString("cp14-ritual-intro") + "\n \n \n");
foreach (var edge in ent.Comp.Edges)
{
if (!_proto.TryIndex(edge.Target, out var targetIndexed))
continue;
sb.Append($"[color=#b5783c][head=3]{targetIndexed.Name}[/head][/color]" + "\n");
//TRIGGERS
if (edge.Triggers.Count > 0)
{
sb.Append($"[bold]{Loc.GetString("cp14-ritual-trigger-header")}[/bold] \n");
foreach (var trigger in edge.Triggers)
sb.Append(trigger.GetGuidebookTriggerDescription(_proto, _entitySystem) + "\n");
}
//REQUIREMENTS
if (edge.Requirements.Count > 0)
{
sb.Append($"[bold]{Loc.GetString("cp14-ritual-req-header")}[/bold] \n");
foreach (var req in edge.Requirements)
sb.Append(req.GetGuidebookRequirementDescription(_proto, _entitySystem) + "\n");
}
//ACTIONS
if (edge.Actions.Count > 0)
{
sb.Append($"[bold]{Loc.GetString("cp14-ritual-effect-header")}[/bold] \n");
foreach (var act in edge.Actions)
sb.Append(act.GetGuidebookEffectDescription(_proto, _entitySystem) + "\n");
}
}
return sb.ToString();
}
}

View File

@@ -0,0 +1,22 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Server.GameObjects;
namespace Content.Server._CP14.MagicRituals;
public sealed partial class CP14RitualSystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
private void InitializeVisuals()
{
SubscribeLocalEvent<CP14MagicRitualPhaseComponent, CP14RitualPhaseBoundEvent>(OnPhaseBound);
}
private void OnPhaseBound(Entity<CP14MagicRitualPhaseComponent> ent, ref CP14RitualPhaseBoundEvent args)
{
if (!TryComp<CP14MagicRitualComponent>(args.Ritual, out var ritual))
return;
_pointLight.SetColor(ent, ent.Comp.PhaseColor);
_appearance.SetData(args.Ritual, RitualVisuals.Color, ent.Comp.PhaseColor);
}
}

View File

@@ -0,0 +1,190 @@
using System.Text;
using Content.Server.Speech.Components;
using Content.Shared._CP14.MagicRitual;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._CP14.MagicRituals;
public partial class CP14RitualSystem : CP14SharedRitualSystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly PointLightSystem _pointLight = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly TransformSystem _transform = default!;
public override void Initialize()
{
base.Initialize();
InitializeDescriber();
InitializeVisuals();
SubscribeLocalEvent<CP14MagicRitualComponent, CP14ActivateRitualDoAfter>(OnActivateRitual);
SubscribeLocalEvent<CP14MagicRitualComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
SubscribeLocalEvent<CP14MagicRitualPhaseComponent, CP14RitualTriggerEvent>(OnPhaseTrigger);
SubscribeLocalEvent<CP14MagicRitualOrbComponent, ExaminedEvent>(OnOrbExamine);
}
private void OnActivateRitual(Entity<CP14MagicRitualComponent> ent, ref CP14ActivateRitualDoAfter args)
{
if (args.Cancelled || args.Handled)
return;
args.Handled = true;
StartRitual(ent);
}
private void OnAlternativeVerb(Entity<CP14MagicRitualComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || ent.Comp.CurrentPhase is not null)
return;
var user = args.User;
AlternativeVerb verb = new()
{
Act = () =>
{
var doAfterArgs =
new DoAfterArgs(EntityManager, user, ent.Comp.ActivationTime, new CP14ActivateRitualDoAfter(), ent, ent)
{
BreakOnDamage = true,
BreakOnMove = true,
};
_doAfter.TryStartDoAfter(doAfterArgs);
},
Text = Loc.GetString("cp14-ritual-verb-text"),
Priority = 1,
};
args.Verbs.Add(verb);
}
private void OnOrbExamine(Entity<CP14MagicRitualOrbComponent> ent, ref ExaminedEvent args)
{
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-ritual-orb-examine", ("name", MetaData(ent).EntityName)) + "\n");
foreach (var orbType in ent.Comp.Powers)
{
if (!_proto.TryIndex(orbType.Key, out var indexedType))
continue;
sb.Append($"[color={indexedType.Color.ToHex()}]");
sb.Append(Loc.GetString("cp14-ritual-entry-item",
("name", Loc.GetString(indexedType.Name)),
("count", orbType.Value)));
sb.Append($"[/color] \n");
}
args.PushMarkup(sb.ToString());
}
public void StartRitual(Entity<CP14MagicRitualComponent> ritual)
{
EndRitual(ritual);
var ev = new CP14RitualStartEvent(ritual);
RaiseLocalEvent(ritual, ev);
ChangePhase(ritual, ritual.Comp.StartPhase);
_appearance.SetData(ritual, RitualVisuals.Enabled, true);
}
private void ChangePhase(Entity<CP14MagicRitualComponent> ritual, EntProtoId newPhase)
{
QueueDel(ritual.Comp.CurrentPhase);
var newPhaseEnt = Spawn(newPhase, Transform(ritual).Coordinates);
_transform.SetParent(newPhaseEnt, ritual);
var newPhaseComp = EnsureComp<CP14MagicRitualPhaseComponent>(newPhaseEnt);
ritual.Comp.CurrentPhase = (newPhaseEnt, newPhaseComp);
newPhaseComp.Ritual = ritual;
foreach (var edge in newPhaseComp.Edges)
{
foreach (var trigger in edge.Triggers)
{
trigger.Initialize(EntityManager, ritual.Comp.CurrentPhase.Value, edge);
}
}
var ev = new CP14RitualPhaseBoundEvent(ritual, newPhaseEnt);
RaiseLocalEvent(ritual, ev);
RaiseLocalEvent(newPhaseEnt, ev);
if (newPhaseComp.DeadEnd)
EndRitual(ritual);
}
public void EndRitual(Entity<CP14MagicRitualComponent> ritual)
{
if (ritual.Comp.CurrentPhase is null)
return;
QueueDel(ritual.Comp.CurrentPhase);
ritual.Comp.CurrentPhase = null;
var ev = new CP14RitualEndEvent(ritual);
RaiseLocalEvent(ritual, ev);
_appearance.SetData(ritual, RitualVisuals.Enabled, false);
foreach (var orb in ritual.Comp.Orbs)
{
QueueDel(orb);
}
}
private void OnPhaseTrigger(Entity<CP14MagicRitualPhaseComponent> phase, ref CP14RitualTriggerEvent args)
{
if (phase.Comp.Ritual is null)
return;
RitualPhaseEdge? selectedEdge = null;
foreach (var edge in phase.Comp.Edges)
{
if (edge.Target == args.NextPhase)
{
selectedEdge = edge;
break;
}
}
if (selectedEdge is null)
return;
var passed = true;
foreach (var req in selectedEdge.Value.Requirements)
{
if (!req.Check(EntityManager, phase, phase.Comp.Ritual.Value.Comp.Stability)) //lol
{
ChangeRitualStability(phase.Comp.Ritual.Value, -req.FailStabilityCost);
passed = false;
break;
}
}
if (!passed)
return;
foreach (var action in selectedEdge.Value.Actions)
{
action.Effect(EntityManager, _transform, phase);
}
ChangePhase(phase.Comp.Ritual.Value, args.NextPhase);
}
}

View File

@@ -0,0 +1,31 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.MagicRituals.Components;
/// <summary>
///
/// </summary>
[RegisterComponent, Access(typeof(CP14RitualSystem))]
public sealed partial class CP14PaperPhaseDescriberComponent : Component
{
[DataField(required: true)]
public EntProtoId StartPhase = default!;
[DataField]
public EntityUid? CurrentPhase = null;
public List<EntProtoId> SearchHistory = new();
[DataField]
public List<EntProtoId> Hyperlinks = new();
public SoundSpecifier UseSound = new SoundCollectionSpecifier("CP14Book")
{
Params = AudioParams.Default
.WithVariation(0.05f)
.WithVolume(0.5f),
};
}

View File

@@ -89,6 +89,6 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory PowerLevel = new("verb-categories-power-level", null);
public static readonly VerbCategory CP14Craft = new("cp14-verb-categories-craft", null); //CP14
public static readonly VerbCategory CP14RitualBook = new("cp14-verb-categories-ritual-book", null); //CP14
}
}

View File

@@ -7,4 +7,7 @@ public sealed class CP14DayCyclePeriodPrototype : IPrototype
{
[IdDataField]
public string ID { get; } = string.Empty;
[DataField(required: true)]
public LocId Name = default!;
}

View File

@@ -0,0 +1,44 @@
using System.Text;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Actions;
/// <summary>
/// Adds a key-orb to the ritual.
/// </summary>
public sealed partial class AddOrb : CP14RitualAction
{
[DataField(required: true)]
public Dictionary<EntProtoId, int> Orbs = new();
public override string? GetGuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-ritual-effect-add-orb")+ "\n");
foreach (var orb in Orbs)
{
if (!prototype.TryIndex(orb.Key, out var indexedOrb))
continue;
sb.Append(Loc.GetString("cp14-ritual-entry-item", ("name", indexedOrb.Name), ("count", orb.Value)) + "\n");
}
return sb.ToString();
}
public override void Effect(EntityManager entManager, SharedTransformSystem transform, Entity<CP14MagicRitualPhaseComponent> phase)
{
if (phase.Comp.Ritual is null)
return;
var ritual = entManager.System<CP14SharedRitualSystem>();
foreach (var orb in Orbs)
{
for (var i = 0; i < orb.Value; i++)
{
ritual.AddOrbToRitual(phase.Comp.Ritual.Value, orb.Key);
}
}
}
}

View File

@@ -0,0 +1,75 @@
using System.Text;
using Content.Shared.EntityEffects;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Actions;
/// <summary>
/// Filters the nearest X entities by whitelist and applies the specified EntityEffects on them
/// </summary>
public sealed partial class ApplyEntityEffect : CP14RitualAction
{
[DataField]
public float CheckRange = 1f;
[DataField]
public EntityWhitelist? Whitelist;
[DataField]
public LocId? WhitelistDesc;
[DataField(required: true, serverOnly: true)]
public List<EntityEffect> Effects = new();
[DataField]
public int MaxEntities = 1;
public override string? GetGuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-ritual-range", ("range", CheckRange)) + "\n");
sb.Append(Loc.GetString("cp14-ritual-effect-apply-effect", ("count", MaxEntities), ("range", CheckRange)) + "\n");
if (WhitelistDesc is not null)
{
sb.Append(Loc.GetString(WhitelistDesc));
sb.Append("\n");
}
foreach (var effect in Effects)
{
sb.Append("- " + effect.GuidebookEffectDescription(prototype, entSys) + "\n");
}
sb.Append("\n");
return sb.ToString();
}
public override void Effect(EntityManager entManager, SharedTransformSystem transform, Entity<CP14MagicRitualPhaseComponent> phase)
{
var lookup = entManager.System<EntityLookupSystem>();
var whitelist = entManager.System<EntityWhitelistSystem>();
var entitiesAround = lookup.GetEntitiesInRange(phase, CheckRange, LookupFlags.Uncontained);
var count = 0;
foreach (var entity in entitiesAround)
{
if (Whitelist is not null && !whitelist.IsValid(Whitelist, entity))
continue;
foreach (var effect in Effects)
{
effect.Effect(new EntityEffectBaseArgs(entity, entManager));
}
entManager.Spawn(VisualEffect, transform.GetMapCoordinates(entity));
count++;
if (count >= MaxEntities)
break;
}
}
}

View File

@@ -0,0 +1,19 @@
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Actions;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract partial class CP14RitualAction
{
/// <summary>
/// Effect appearing in place of interacted entities
/// </summary>
[DataField("vfx")]
public EntProtoId? VisualEffect = "CP14DustEffect";
public abstract void Effect(EntityManager entManager, SharedTransformSystem transform, Entity<CP14MagicRitualPhaseComponent> phase);
public abstract string? GetGuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys);
}

View File

@@ -0,0 +1,41 @@
using System.Text;
using Content.Shared._CP14.MagicRitual.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Actions;
/// <summary>
/// Removes the orb key from the ritual.
/// </summary>
public sealed partial class ConsumeOrb : CP14RitualAction
{
[DataField(required: true)]
public ProtoId<CP14MagicTypePrototype> MagicType = new();
[DataField(required: true)]
public int Count = 0;
public override string? GetGuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (!prototype.TryIndex(MagicType, out var indexedType))
return null;
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-ritual-effect-consume-orb", ("name", Loc.GetString(indexedType.Name)), ("count", Count))+ "\n");
return sb.ToString();
}
public override void Effect(EntityManager entManager, SharedTransformSystem transform, Entity<CP14MagicRitualPhaseComponent> phase)
{
if (phase.Comp.Ritual is null)
return;
var ritual = entManager.System<CP14SharedRitualSystem>();
for (var i = 0; i < Count; i++)
{
ritual.ConsumeOrbType(phase.Comp.Ritual.Value, MagicType);
}
}
}

View File

@@ -0,0 +1,104 @@
using System.Text;
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Actions;
public sealed partial class ConsumeResource : CP14RitualAction
{
[DataField]
public float CheckRange = 1f;
[DataField]
public Dictionary<EntProtoId, int> RequiredEntities = new ();
[DataField]
public Dictionary<ProtoId<StackPrototype>, int> RequiredStacks = new();
public override string? GetGuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-ritual-effect-consume-resource", ("range", CheckRange)) + "\n");
foreach (var entity in RequiredEntities)
{
if (!prototype.TryIndex(entity.Key, out var indexed))
continue;
sb.Append(Loc.GetString("cp14-ritual-entry-item", ("name", indexed.Name), ("count", entity.Value)) + "\n");
}
foreach (var stack in RequiredStacks)
{
if (!prototype.TryIndex(stack.Key, out var indexed))
continue;
sb.Append(Loc.GetString("cp14-ritual-entry-item", ("name", Loc.GetString(indexed.Name)), ("count", stack.Value)) + "\n");
}
return sb.ToString();
}
public override void Effect(EntityManager entManager, SharedTransformSystem transform, Entity<CP14MagicRitualPhaseComponent> phase)
{
var lookup = entManager.System<EntityLookupSystem>();
var stack = entManager.System<SharedStackSystem>();
var entitiesAround = lookup.GetEntitiesInRange(phase, CheckRange, LookupFlags.Uncontained);
foreach (var reqEnt in RequiredEntities)
{
var requiredCount = reqEnt.Value;
foreach (var entity in entitiesAround)
{
if (!entManager.TryGetComponent<MetaDataComponent>(entity, out var metaData))
continue;
if (!entManager.HasComponent<TransformComponent>(entity))
continue;
var entProto = metaData.EntityPrototype;
if (entProto is null)
continue;
if (entProto.ID == reqEnt.Key && requiredCount > 0)
{
if (VisualEffect is not null)
entManager.Spawn(VisualEffect.Value, transform.GetMapCoordinates(entity));
entManager.DeleteEntity(entity);
requiredCount--;
}
}
}
foreach (var reqStack in RequiredStacks)
{
var requiredCount = reqStack.Value;
foreach (var entity in entitiesAround)
{
if (!entManager.TryGetComponent<StackComponent>(entity, out var stackComp))
continue;
if (stackComp.StackTypeId != reqStack.Key)
continue;
var count = (int)MathF.Min(requiredCount, stackComp.Count);
if (stackComp.Count - count <= 0)
entManager.DeleteEntity(entity);
else
stack.SetCount(entity, stackComp.Count - count, stackComp);
requiredCount -= count;
if (VisualEffect is not null)
entManager.Spawn(VisualEffect.Value, transform.GetMapCoordinates(entity));
}
}
}
}

View File

@@ -0,0 +1,27 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Actions;
public sealed partial class EditStability : CP14RitualAction
{
[DataField(required: true)]
public float Mod;
public override void Effect(EntityManager entManager, SharedTransformSystem transform, Entity<CP14MagicRitualPhaseComponent> phase)
{
var ritual = entManager.System<CP14SharedRitualSystem>();
if (phase.Comp.Ritual is not null)
ritual.ChangeRitualStability(phase.Comp.Ritual.Value, Mod);
}
public override string? GetGuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Mod switch
{
> 0 => Loc.GetString("cp14-ritual-effect-stability-add", ("count", Mod * 100)) + "\n",
< 0 => Loc.GetString("cp14-ritual-effect-stability-minus", ("count", -Mod * 100)) + "\n",
_ => null,
};
}
}

View File

@@ -0,0 +1,44 @@
using System.Text;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Actions;
/// <summary>
/// Creates an entity in the coordinates of the ritual.
/// </summary> TODO: EntityTable support?
public sealed partial class SpawnEntity : CP14RitualAction
{
[DataField(required: true)]
public Dictionary<EntProtoId, int> Spawns = new();
[DataField]
public LocId? Name;
public override string? GetGuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-ritual-effect-spawn-entity")+ "\n");
foreach (var spawn in Spawns)
{
if (!prototype.TryIndex(spawn.Key, out var indexed))
return null;
sb.Append(Loc.GetString("cp14-ritual-entry-item",
("name", Name is null ? indexed.Name : Loc.GetString(Name)),
("count", spawn.Value)) + "\n");
}
return sb.ToString();
}
public override void Effect(EntityManager entManager, SharedTransformSystem transform, Entity<CP14MagicRitualPhaseComponent> phase)
{
foreach (var spawn in Spawns)
{
for (var i = 0; i < spawn.Value; i++)
{
entManager.Spawn(spawn.Key, transform.GetMapCoordinates(phase));
}
}
}
}

View File

@@ -0,0 +1,37 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual;
/// <summary>
/// Ritual Behavior Controller. Creates and removes entities of magical phases
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedRitualSystem))]
public sealed partial class CP14MagicRitualComponent : Component
{
[DataField(required: true)]
public EntProtoId StartPhase;
[DataField]
public Entity<CP14MagicRitualPhaseComponent>? CurrentPhase;
[DataField]
public float Stability = 1f;
[DataField]
public float ActivationTime = 5f;
[DataField]
public string RitualLayerMap = "ritual";
[DataField]
public int MaxOrbCapacity = 3;
[DataField]
public float RitualRadius = 5;
[DataField]
public TimeSpan TriggerTime = TimeSpan.Zero;
[DataField]
public List<Entity<CP14MagicRitualOrbComponent>> Orbs = new();
}

View File

@@ -0,0 +1,86 @@
using Content.Shared.DoAfter;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.MagicRitual;
/// <summary>
/// Called out a ritual when any of its phase triggers are activated
/// </summary>
public sealed class CP14RitualTriggerEvent : EntityEventArgs
{
public EntProtoId NextPhase;
public CP14RitualTriggerEvent(EntProtoId phase)
{
NextPhase = phase;
}
}
/// <summary>
/// Called out at a ritual when his stability is altered
/// </summary>
public sealed class CP14RitualStabilityChangedEvent : EntityEventArgs
{
public float OldStability;
public float NewStability;
public CP14RitualStabilityChangedEvent(float oldS, float newS)
{
OldStability = oldS;
NewStability = newS;
}
}
/// <summary>
/// Called on both the ritual and the phase when they link together
/// </summary>
public sealed class CP14RitualPhaseBoundEvent : EntityEventArgs
{
public EntityUid Ritual;
public EntityUid Phase;
public CP14RitualPhaseBoundEvent(EntityUid r, EntityUid p)
{
Ritual = r;
Phase = p;
}
}
/// <summary>
/// Invoked at the ritual holder entity when the ritual is complete and the phase entities have been removed
/// </summary>
public sealed class CP14RitualEndEvent : EntityEventArgs
{
public EntityUid Ritual;
public CP14RitualEndEvent(EntityUid r)
{
Ritual = r;
}
}
/// <summary>
/// Invoked at the ritual holder entity when the ritual begins, and invokes the starting phase
/// </summary>
public sealed class CP14RitualStartEvent : EntityEventArgs
{
public EntityUid Ritual;
public CP14RitualStartEvent(EntityUid r)
{
Ritual = r;
}
}
[Serializable, NetSerializable]
public sealed partial class CP14ActivateRitualDoAfter : SimpleDoAfterEvent
{
}
[Serializable, NetSerializable]
public enum RitualVisuals
{
Color,
Enabled,
}

View File

@@ -0,0 +1,14 @@
using Content.Shared._CP14.MagicRitual.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual;
/// <summary>
/// “Key” in the concept of rituals. An entity that can be a key to a ritual, and holds certain characteristics that can be spent, or by which a phase transition requirement check can be made.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedRitualSystem))]
public sealed partial class CP14MagicRitualOrbComponent : Component
{
[DataField]
public Dictionary<ProtoId<CP14MagicTypePrototype>, int> Powers = new();
}

View File

@@ -0,0 +1,41 @@
using Content.Shared._CP14.MagicRitual.Actions;
using Content.Shared._CP14.MagicRitual.Requirements;
using Content.Shared._CP14.MagicRitualTrigger;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual;
/// <summary>
/// Magical entity that reacts to world events
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedRitualSystem))]
public sealed partial class CP14MagicRitualPhaseComponent : Component
{
/// <summary>
/// A link to the ritual itself in which this phase is found
/// </summary>
[DataField]
public Entity<CP14MagicRitualComponent>? Ritual;
[DataField]
public Color PhaseColor = Color.White;
[DataField]
public List<RitualPhaseEdge> Edges = new();
/// <summary>
/// by moving to this node, the ritual will end instantly.
/// </summary>
[DataField]
public bool DeadEnd = false;
}
[DataRecord]
public partial record struct RitualPhaseEdge()
{
public EntProtoId Target { get; set; }
public List<CP14RitualTrigger> Triggers { get; set; } = new();
public List<CP14RitualRequirement> Requirements { get; set; } = new();
public List<CP14RitualAction> Actions { get; set; } = new();
}

View File

@@ -0,0 +1,61 @@
using Content.Shared._CP14.MagicRitual.Prototypes;
using Content.Shared.Follower;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual;
public partial class CP14SharedRitualSystem : EntitySystem
{
[Dependency] private readonly FollowerSystem _followerSystem = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly INetManager _net = default!;
public void ChangeRitualStability(Entity<CP14MagicRitualComponent> ritual, float dStab)
{
var newS = MathHelper.Clamp01(ritual.Comp.Stability + dStab);
var ev = new CP14RitualStabilityChangedEvent(ritual.Comp.Stability, newS);
RaiseLocalEvent(ritual, ev);
ritual.Comp.Stability = newS;
}
public void AddOrbToRitual(Entity<CP14MagicRitualComponent> ritual, EntProtoId orb)
{
if (_net.IsClient)
return;
if (!_proto.TryIndex(orb, out var indexedOrb))
return;
if (ritual.Comp.Orbs.Count >= ritual.Comp.MaxOrbCapacity)
return;
var spawnedOrb = Spawn(orb, _transform.GetMapCoordinates(ritual));
if (!TryComp<CP14MagicRitualOrbComponent>(spawnedOrb, out var orbComp))
{
QueueDel(spawnedOrb);
return;
}
_followerSystem.StartFollowingEntity(spawnedOrb, ritual);
ritual.Comp.Orbs.Add((spawnedOrb, orbComp));
}
public void ConsumeOrbType(Entity<CP14MagicRitualComponent> ritual, ProtoId<CP14MagicTypePrototype> magicType)
{
foreach (var orb in ritual.Comp.Orbs)
{
var powers = orb.Comp.Powers;
if (!powers.ContainsKey(magicType))
continue;
ritual.Comp.Orbs.Remove(orb);
QueueDel(orb);
return;
}
}
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Prototypes;
/// <summary>
/// A round-start setup preset, such as which antagonists to spawn.
/// </summary>
[Prototype("magicType")]
public sealed partial class CP14MagicTypePrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
[DataField(required: true)]
public string Name = string.Empty;
[DataField(required: true)]
public Color Color = Color.White;
}

View File

@@ -0,0 +1,19 @@
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Requirements;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract partial class CP14RitualRequirement
{
/// <summary>
/// If this checks fails, the ritual will lose some of its stability.
/// </summary>
[DataField]
public float FailStabilityCost;
public abstract bool Check(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> phaseEnt, float stability);
public abstract string? GetGuidebookRequirementDescription(IPrototypeManager prototype, IEntitySystemManager entSys);
}

View File

@@ -0,0 +1,63 @@
using System.Text;
using Content.Shared._CP14.MagicRitual.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Requirements;
/// <summary>
/// Requires specific daytime period
/// </summary>
public sealed partial class RequiredOrbs : CP14RitualRequirement
{
[DataField]
public ProtoId<CP14MagicTypePrototype> MagicType = new();
[DataField]
public int? Min;
[DataField]
public int? Max;
public override string? GetGuidebookRequirementDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var sb = new StringBuilder();
if (!prototype.TryIndex(MagicType, out var indexedType))
return null;
sb.Append(Loc.GetString("cp14-ritual-required-orbs", ("name", Loc.GetString(indexedType.Name))) + " ");
if (Min is not null && Max is not null)
sb.Append(Loc.GetString("cp14-ritual-required-orbs-item-minmax", ("min", Min), ("max", Max))+ "\n");
else if (Min is not null)
sb.Append(Loc.GetString("cp14-ritual-required-orbs-item-min", ("min", Min))+ "\n");
else if (Max is not null)
sb.Append(Loc.GetString("cp14-ritual-required-orbs-item-min", ("max", Max))+ "\n");
return sb.ToString();
}
public override bool Check(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> phaseEnt, float stability)
{
if (phaseEnt.Comp.Ritual is null)
return false;
var count = 0;
foreach (var orb in phaseEnt.Comp.Ritual.Value.Comp.Orbs)
{
foreach (var power in orb.Comp.Powers)
{
if (power.Key == MagicType)
count += power.Value;
}
}
if (Min is not null && Max is not null)
return count >= Min && count <= Max;
if (Min is not null)
return count >= Min;
if (Max is not null)
return count <= Max;
return false;
}
}

View File

@@ -0,0 +1,113 @@
using System.Text;
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Requirements;
/// <summary>
/// Requires certain specific entities to be near the ritual. TODO: Replace with Whitelist
/// </summary>
public sealed partial class RequiredResource : CP14RitualRequirement
{
[DataField]
public float CheckRange = 3f;
[DataField]
public Dictionary<EntProtoId, int> RequiredEntities = new ();
[DataField]
public Dictionary<ProtoId<StackPrototype>, int> RequiredStacks = new();
/// <summary>
/// Effect appearing in place of used entities
/// </summary>
[DataField("vfx")]
public EntProtoId? Effect = "CP14DustEffect";
public override string? GetGuidebookRequirementDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-ritual-required-resource", ("range", CheckRange)) + "\n");
foreach (var entity in RequiredEntities)
{
if (!prototype.TryIndex(entity.Key, out var indexed))
continue;
sb.Append(Loc.GetString("cp14-ritual-entry-item", ("name", indexed.Name), ("count", entity.Value)) + "\n");
}
foreach (var stack in RequiredStacks)
{
if (!prototype.TryIndex(stack.Key, out var indexed))
continue;
sb.Append(Loc.GetString("cp14-ritual-entry-item", ("name", Loc.GetString(indexed.Name)), ("count", stack.Value)) + "\n");
}
return sb.ToString();
}
public override bool Check(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> phaseEnt, float stability)
{
var _lookup = entManager.System<EntityLookupSystem>();
var _transform = entManager.System<SharedTransformSystem>();
var entitiesAround = _lookup.GetEntitiesInRange(phaseEnt, CheckRange, LookupFlags.Uncontained);
var passed = true;
foreach (var reqEnt in RequiredEntities)
{
var requiredCount = reqEnt.Value;
foreach (var entity in entitiesAround)
{
if (!entManager.TryGetComponent<MetaDataComponent>(entity, out var metaData))
continue;
if (!entManager.TryGetComponent<TransformComponent>(entity, out var xform))
continue;
var entProto = metaData.EntityPrototype;
if (entProto is null)
continue;
if (entProto.ID == reqEnt.Key && requiredCount > 0)
{
if (Effect is not null)
entManager.Spawn(Effect.Value, _transform.GetMapCoordinates(entity));
requiredCount--;
}
}
if (requiredCount > 0)
passed = false;
}
foreach (var reqStack in RequiredStacks)
{
var requiredCount = reqStack.Value;
foreach (var entity in entitiesAround)
{
if (!entManager.TryGetComponent<StackComponent>(entity, out var stack))
continue;
if (stack.StackTypeId != reqStack.Key)
continue;
var count = (int)MathF.Min(requiredCount, stack.Count);
requiredCount -= count;
if (Effect is not null)
entManager.Spawn(Effect.Value, _transform.GetMapCoordinates(entity));
}
if (requiredCount > 0)
passed = false;
}
return passed;
}
}

View File

@@ -0,0 +1,36 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Requirements;
/// <summary>
/// Requires that the stability of the ritual be within specified limits. If the stability is above or below the specified values, the check will fail
/// </summary>
public sealed partial class RequiredStability : CP14RitualRequirement
{
[DataField]
public float Min = 0;
[DataField]
public float Max = 1;
public override string? GetGuidebookRequirementDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Min switch
{
> 0 when Max < 1 =>
Loc.GetString("cp14-ritual-required-stability-minmax", ("min", Min*100), ("max", Max*100)),
> 0 => Loc.GetString("cp14-ritual-required-stability-min", ("min", Min*100)),
< 0 => Loc.GetString("cp14-ritual-required-stability-max", ("min", Max*100)),
_ => null,
};
}
public override bool Check(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> phaseEnt, float stability)
{
if (phaseEnt.Comp.Ritual is null)
return false;
var s = phaseEnt.Comp.Ritual.Value.Comp.Stability;
return !(s < Min) && !(s > Max);
}
}

View File

@@ -0,0 +1,33 @@
using Content.Shared._CP14.DayCycle;
using Content.Shared._CP14.DayCycle.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitual.Requirements;
/// <summary>
/// Requires specific daytime
/// </summary>
public sealed partial class RequiredTime : CP14RitualRequirement
{
[DataField]
public ProtoId<CP14DayCyclePeriodPrototype> TimePeriod;
public override string? GetGuidebookRequirementDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (!prototype.TryIndex(TimePeriod, out var indexed))
return null;
return Loc.GetString("cp14-ritual-required-time", ("period", Loc.GetString(indexed.Name)));
}
public override bool Check(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> phaseEnt, float stability)
{
var transform = entManager.System<SharedTransformSystem>();
var map = transform.GetMap(phaseEnt.Owner);
if (!entManager.TryGetComponent<CP14DayCycleComponent>(map, out var dayCycle))
return false;
return TimePeriod == dayCycle.CurrentPeriod;
}
}

View File

@@ -0,0 +1,17 @@
using Content.Shared._CP14.MagicRitual;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitualTrigger;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract partial class CP14RitualTrigger
{
[DataField]
public RitualPhaseEdge? Edge = null;
public abstract void Initialize(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> ritual, RitualPhaseEdge edge);
public abstract string? GetGuidebookTriggerDescription(IPrototypeManager prototype, IEntitySystemManager entSys);
}

View File

@@ -0,0 +1,5 @@
namespace Content.Shared._CP14.MagicRitualTrigger;
public partial class CP14SharedRitualTriggerSystem : EntitySystem
{
}

View File

@@ -0,0 +1,34 @@
using Content.Shared._CP14.MagicRitual;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
/// <summary>
/// triggers when a creature of a certain race dies within range of the ritual.
/// </summary>
public sealed partial class CP14SacrificeSpeciesTrigger : CP14RitualTrigger
{
[DataField]
public float Range = 3f;
[DataField(required: true)]
public ProtoId<SpeciesPrototype> Species = default!;
public override void Initialize(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> ritual, RitualPhaseEdge edge)
{
entManager.EnsureComponent<CP14RitualSacrificeSpeciesTriggerComponent>(ritual, out var trigger);
trigger.Triggers.Add(this);
Edge = edge;
}
public override string? GetGuidebookTriggerDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (!prototype.TryIndex(Species, out var indexedSpecies))
return null;
return Loc.GetString("cp14-ritual-trigger-sacrifice",
("name", Loc.GetString(indexedSpecies.Name)),
("range", Range));
}
}

View File

@@ -0,0 +1,8 @@
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
[RegisterComponent]
public sealed partial class CP14RitualSacrificeSpeciesTriggerComponent : Component
{
[DataField]
public HashSet<CP14SacrificeSpeciesTrigger> Triggers = new();
}

View File

@@ -0,0 +1,34 @@
using Content.Shared._CP14.MagicRitual;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
/// <summary>
/// Triggers when a creature passing the whitelist dies within range of the ritual.
/// </summary>
public sealed partial class CP14SacrificeWhitelistTrigger : CP14RitualTrigger
{
[DataField]
public float Range = 3f;
[DataField(required: true)]
public EntityWhitelist Whitelist = default!;
[DataField(required: true)]
public LocId WhitelistDesc = default!;
public override void Initialize(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> ritual, RitualPhaseEdge edge)
{
entManager.EnsureComponent<CP14RitualSacrificeWhitelistTriggerComponent>(ritual, out var trigger);
trigger.Triggers.Add(this);
Edge = edge;
}
public override string? GetGuidebookTriggerDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Loc.GetString("cp14-ritual-trigger-sacrifice",
("name", Loc.GetString(WhitelistDesc)),
("range", Range));
}
}

View File

@@ -0,0 +1,8 @@
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
[RegisterComponent]
public sealed partial class CP14RitualSacrificeWhitelistTriggerComponent : Component
{
[DataField]
public HashSet<CP14SacrificeWhitelistTrigger> Triggers = new();
}

View File

@@ -0,0 +1,28 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
/// <summary>
/// Triggers the phase transition after a certain period of time
/// </summary>
public sealed partial class CP14TimerTrigger : CP14RitualTrigger
{
[DataField]
public float Delay = 10f;
[DataField]
public TimeSpan TriggerTime = TimeSpan.Zero;
public override void Initialize(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> ritual, RitualPhaseEdge edge)
{
entManager.EnsureComponent<CP14RitualTimerTriggerComponent>(ritual, out var trigger);
trigger.Triggers.Add(this);
Edge = edge;
}
public override string? GetGuidebookTriggerDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Loc.GetString("cp14-ritual-trigger-timer-stable", ("time", Delay));
}
}

View File

@@ -0,0 +1,8 @@
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
[RegisterComponent]
public sealed partial class CP14RitualTimerTriggerComponent : Component
{
[DataField]
public HashSet<CP14TimerTrigger> Triggers = new();
}

View File

@@ -0,0 +1,28 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
/// <summary>
/// Triggers a phase transition when the Ritual hears a certain message
/// </summary>
public sealed partial class CP14VoiceTrigger : CP14RitualTrigger
{
[DataField]
public string Message = string.Empty;
[DataField]
public int Speakers = 1;
public override void Initialize(EntityManager entManager, Entity<CP14MagicRitualPhaseComponent> ritual, RitualPhaseEdge edge)
{
entManager.EnsureComponent<CP14RitualVoiceTriggerComponent>(ritual, out var trigger);
trigger.Triggers.Add(this);
Edge = edge;
}
public override string? GetGuidebookTriggerDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Loc.GetString("cp14-ritual-trigger-voice", ("phrase", Message), ("count", Speakers));
}
}

View File

@@ -0,0 +1,8 @@
namespace Content.Shared._CP14.MagicRitualTrigger.Triggers;
[RegisterComponent]
public sealed partial class CP14RitualVoiceTriggerComponent : Component
{
[DataField]
public HashSet<CP14VoiceTrigger> Triggers = new();
}

View File

@@ -21,5 +21,5 @@ public sealed partial class CP14MeleeParriableComponent : Component
public float NeedParryPower = 1f;
[DataField]
public SoundSpecifier ParrySound = new SoundPathSpecifier("/Audio/_CP14/Effects/parry1.ogg", AudioParams.Default.WithVariation(0.2f));
public SoundSpecifier ParrySound = new SoundCollectionSpecifier("CP14Parry", AudioParams.Default.WithVariation(0.05f));
}

View File

@@ -1,20 +1,22 @@
using Content.Shared._CP14.DayCycle;
using Content.Shared._CP14.DayCycle.Components;
using Content.Shared.Random.Rules;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Random.Rules;
/// <summary>
/// Returns true if the attached entity is in space.
/// Checks whether there is a time of day on the current map, and whether the current time of day corresponds to the specified periods.
/// </summary>
public sealed partial class IsDaylight : RulesRule
public sealed partial class CP14TimePeriod : RulesRule
{
[DataField] private List<ProtoId<CP14DayCyclePeriodPrototype>> Periods = new();
public override bool Check(EntityManager entManager, EntityUid uid)
{
var dayCycle = entManager.System<CP14SharedDayCycleSystem>();
var transform = entManager.System<SharedTransformSystem>();
if (Inverted)
return !dayCycle.TryDaylightThere(uid, true);
else
return dayCycle.TryDaylightThere(uid, true);
var map = transform.GetMap(uid);
return entManager.TryGetComponent<CP14DayCycleComponent>(map, out var dayCycle) && Periods.Contains(dayCycle.CurrentPeriod);
}
}

View File

@@ -6,4 +6,9 @@
- files: ["water.ogg"]
license: "CC0-1.0"
copyright: 'by jackthemurray of Freesound.org.'
source: "https://freesound.org/people/jackthemurray/sounds/433589/"
source: "https://freesound.org/people/jackthemurray/sounds/433589/"
- files: ["cave.ogg"]
license: "CC-BY-4.0"
copyright: 'by EminYILDIRIM of Freesound.org.'
source: "https://freesound.org/people/EminYILDIRIM/sounds/711956/"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -18,12 +18,17 @@
copyright: 'by 7by7 of Freesound.org. Mixed from stereo to mono.'
source: "https://freesound.org/people/7by7/sounds/72849/"
- files: ["weatherWindy.ogg"]
license: "CC-BY-4.0"
copyright: 'by Benboncan of Freesound.org. Mixed from stereo to mono.'
source: "https://freesound.org/people/Benboncan/sounds/134699/"
- files: ["ambiAlchemy.ogg"]
license: "CC-BY-4.0"
copyright: 'by be-steele of Freesound.org.'
source: "https://freesound.org/people/be-steele/sounds/130753/"
source: "https://freesound.org/people/be-steele/sounds/130753/"
- files: ["ambimagic1.ogg", "ambimagic2.ogg", "ambimagic3.ogg", "ambimagic4.ogg"]
license: "CC-BY-4.0"
copyright: 'by EminYILDIRIM of Freesound.org.'
source: "https://freesound.org/people/EminYILDIRIM/sounds/581627/"
- files: ["ambimagic5.ogg", "ambimagic6.ogg"]
license: "CC0-1.0"
copyright: 'by xkeril of Freesound.org.'
source: "https://freesound.org/people/xkeril/sounds/706092/"

View File

@@ -23,7 +23,12 @@
copyright: 'Breviceps of Freesound.org'
source: "https://freesound.org/people/Breviceps/sounds/493162/"
- files: ["parry1.ogg"]
license: "CC0-1.0"
copyright: 'JohnBuhr of Freesound.org'
source: "https://freesound.org/people/JohnBuhr/sounds/326803/"
- files: ["parry1.ogg", "parry2.ogg"]
license: "CC-BY-4.0"
copyright: 'by EminYILDIRIM of Freesound.org.'
source: "https://freesound.org/people/EminYILDIRIM/sounds/536104/"
- files: ["parry2.ogg"]
license: "CC-BY-4.0"
copyright: 'by EminYILDIRIM of Freesound.org.'
source: "https://freesound.org/people/EminYILDIRIM/sounds/536105/"

Binary file not shown.

View File

@@ -56,4 +56,14 @@
- files: ["broom1.ogg", "broom2.ogg", "broom3.ogg"]
license: "CC-BY-4.0"
copyright: 'by F.M.Audio of Freesound.org. Cropped by TheShuEd.'
source: "https://freesound.org/people/F.M.Audio/sounds/552056/"
source: "https://freesound.org/people/F.M.Audio/sounds/552056/"
- files: ["book1.ogg"]
license: "CC-BY-4.0"
copyright: 'by flag2 of Freesound.org. edit to Mono by TheShuEd.'
source: "https://freesound.org/people/flag2/sounds/63318/"
- files: ["book2.ogg"]
license: "CC-BY-4.0"
copyright: 'by InspectorJ of Freesound.org. edit to Mono by TheShuEd.'
source: "https://freesound.org/people/InspectorJ/sounds/416179/"

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
cp14-daycycle-sunrise = sunrise
cp14-daycycle-day = day
cp14-daycycle-night = night
cp14-daycycle-evening = evening

View File

@@ -0,0 +1,19 @@
cp14-verb-categories-ritual-book = Choose a page:
cp14-ritual-describer-verb-item = Turn the page: {$name}
cp14-ritual-describer-verb-hyperlink = Quick navigate: {$name}
cp14-ritual-describer-verb-back = Back
cp14-ritual-verb-text = Activate the ritual
cp14-ritual-category-all-living = any living thing
cp14-ritual-intro = The following neighboring nodes are known to be available for transition:
cp14-ritual-range = In a radius {$range},
cp14-ritual-orb-examine = {$name} contains the following energies:
cp14-ritual-entry-item =
- { $count ->
[1] {$name}
*[other] {$name} ({$count} pcs.)
}

View File

@@ -0,0 +1,20 @@
cp14-ritual-effect-header = The moment of transition will cause the following effects:
##
cp14-ritual-effect-apply-effect =
{ $count ->
[1] From the nearest entity
*[other] At the {$count} closest entity
}
cp14-ritual-effect-spawn-entity = Incorporates the following objects into reality:
cp14-ritual-effect-consume-resource = Within a radius of {$range}, absorbs the following entities:
cp14-ritual-effect-stability-add = Stabilizes the ritual by [color=#34eb89]{$count}%[/color]
cp14-ritual-effect-stability-minus = Destabilizes the ritual by [color=#eb4034]{$count}%[/color]
cp14-ritual-effect-add-orb = Ritual receives:
cp14-ritual-effect-consume-orb = The ritual consumes the {$name} spheres: {$count}

View File

@@ -0,0 +1,17 @@
cp14-ritual-req-header = The following conditions must be met for the transition to take place:
#
cp14-ritual-required-stability-min = The stability of the ritual should be higher than {$min}%
cp14-ritual-required-stability-max = The stability of the ritual must be lower than {$max}%
cp14-ritual-required-stability-minmax = The stability of the ritual should be within the range of {$min} to {$max}%
cp14-ritual-required-resource = The following items must be within the {$range} radius:
cp14-ritual-required-time = It must be {$period} outside
cp14-ritual-required-orbs = Energies like “{$name}” should be
cp14-ritual-required-orbs-item-min = at least {$min}
cp14-ritual-required-orbs-item-max = no more than {$max}
cp14-ritual-required-orbs-item-minmax = from {$min} to {$max}

View File

@@ -0,0 +1,15 @@
cp14-ritual-trigger-header = The transition to this node can take place:
#
cp14-ritual-trigger-timer-stable = Automatically in {$time} seconds.
cp14-ritual-trigger-voice = After casting the spell “{$phrase}”
{ $count ->
[1].
*[other], by at least {$count} different entities at the same time
}
cp14-ritual-trigger-failure = As a result of various errors.
cp14-ritual-trigger-sacrifice = When {$name} dies painfully within a {$range} radius of the ritual site.

View File

@@ -0,0 +1,2 @@
cp14-magic-type-creation = Creation
cp14-magic-type-destruction = Destruction

View File

@@ -0,0 +1,4 @@
cp14-daycycle-sunrise = рассвет
cp14-daycycle-day = день
cp14-daycycle-night = ночь
cp14-daycycle-evening = вечер

View File

@@ -0,0 +1,19 @@
cp14-verb-categories-ritual-book = Выбрать страницу
cp14-ritual-describer-verb-item = Перелистнуть: {$name}
cp14-ritual-describer-verb-hyperlink = Быстрый переход: {$name}
cp14-ritual-describer-verb-back = Назад
cp14-ritual-verb-text = Активировать ритуал
cp14-ritual-category-all-living = любое живое существо
cp14-ritual-intro = Известны следующие соседние узлы, доступные для перехода:
cp14-ritual-range = В радиусе {$range},
cp14-ritual-orb-examine = {$name} содержит в себе следующие энергии:
cp14-ritual-entry-item =
- { $count ->
[1] {$name}
*[other] {$name} ({$count} шт.)
}

View File

@@ -0,0 +1,20 @@
cp14-ritual-effect-header = Момент перехода вызовет следующие эффекты:
##
cp14-ritual-effect-apply-effect =
у { $count ->
[1] ближайшей сущности
*[other] {$count} ближайших сущностей
}
cp14-ritual-effect-spawn-entity = Воплощает в реальность следующие объекты:
cp14-ritual-effect-consume-resource = В радиусе {$range} поглощает следующие сущности:
cp14-ritual-effect-stability-add = Стабилизирует ритуал на [color=#34eb89]{$count}%[/color]
cp14-ritual-effect-stability-minus = Дестабилизирует ритуал на [color=#eb4034]{$count}%[/color]
cp14-ritual-effect-add-orb = Ритуал получает:
cp14-ritual-effect-consume-orb = Ритуал поглощает сфер {$name}: {$count}

View File

@@ -0,0 +1,17 @@
cp14-ritual-req-header = Для перехода необходимо соблюсти следующие условия:
#
cp14-ritual-required-stability-min = Стабильность ритуала должна быть выше {$min}%
cp14-ritual-required-stability-max = Стабильность ритуала должна быть ниже {$max}%
cp14-ritual-required-stability-minmax = Стабильность ритуала должна быть в рамках от {$min} до {$max}%
cp14-ritual-required-resource = В радиусе {$range} должны находиться следующие предметы:
cp14-ritual-required-time = На улице должен быть {$period}
cp14-ritual-required-orbs = Энергии типа "{$name}" должно быть
cp14-ritual-required-orbs-item-min = как минимум {$min}
cp14-ritual-required-orbs-item-max = не больше {$max}
cp14-ritual-required-orbs-item-minmax = от {$min} до {$max}

View File

@@ -0,0 +1,15 @@
cp14-ritual-trigger-header = Переход в этот узел может состояться:
#
cp14-ritual-trigger-timer-stable = Автоматически через {$time} секунд.
cp14-ritual-trigger-voice = После произнесения заклинания "{$phrase}"
{ $count ->
[1].
*[other], как минимум {$count} разными сущностями одновременно
}
cp14-ritual-trigger-failure = В результате различных ошибок.
cp14-ritual-trigger-sacrifice = Когда {$name} мучительно погибает в радиусе {$range} от места проведения ритуала.

View File

@@ -0,0 +1,2 @@
cp14-magic-type-creation = Созидание
cp14-magic-type-destruction = Разрушение

View File

@@ -1,11 +1,15 @@
- type: CP14DayCyclePeriod
id: Sunrise # HOLY SHIT!
id: Sunrise
name: cp14-daycycle-sunrise
- type: CP14DayCyclePeriod
id: Day
name: cp14-daycycle-day
- type: CP14DayCyclePeriod
id: Night
name: cp14-daycycle-night
- type: CP14DayCyclePeriod
id: Evening
name: cp14-daycycle-evening

View File

@@ -16,4 +16,16 @@
params:
variation: 0.03
- type: UseDelay
delay: 4
delay: 4
- type: entity
id: CP14RitualChalk
parent: BaseItem
name: ritual chalk
description: Quartz chalk, handy for drawing temporary ritual circles.
components:
- type: Sprite
sprite: _CP14/Objects/Specific/Thaumaturgy/ritual_chalk.rsi
state: icon
- type: Item
sprite: _CP14/Objects/Specific/Thaumaturgy/ritual_chalk.rsi

View File

@@ -0,0 +1,24 @@
- type: entity
parent: BookBase
id: CP14RitualGrimoire
name: ritualist's grimoire
description: a book that holds the knowledge of hundreds of ritualists before you. Use it on an active ritual to get all the information about its current state.
components:
- type: Sprite
sprite: Objects/Misc/books.rsi
layers:
- state: paper
- state: cover_old
color: "#a6161d"
- state: decor_bottom
color: "#6e1022"
- state: decor_wingette
color: "#4a101b"
- state: icon_pentagramm
color: "#911129"
- state: detail_bookmark
color: red
- type: CP14PaperPhaseDescriber
startPhase: CP14_NeutralCluster_Root
hyperlinks:
- CP14_NeutralCluster_Root

View File

@@ -0,0 +1,55 @@
- type: entity
parent: CP14BaseRitualPhase
abstract: true
id: CP14_NeutralCluster_Base
components:
- type: CP14MagicRitualPhase
phaseColor: "#FFFFFF"
- type: ActiveListener
range: 3
- type: entity
parent: CP14_NeutralCluster_Base
id: CP14_NeutralCluster_Root
name: Te-Se-Ra
description: The perfect energetic position to begin any ritual.
categories: [ HideSpawnMenu ]
components:
- type: CP14MagicRitualPhase
edges:
- target: CP14_NeutralCluster_00
triggers:
- !type:CP14SacrificeWhitelistTrigger
whitelist:
components:
- MobState
whitelistDesc: cp14-ritual-category-all-living
- type: entity
parent: CP14_NeutralCluster_Base
id: CP14_NeutralCluster_00
name: Li-Ra
categories: [ HideSpawnMenu ]
components:
- type: CP14MagicRitualPhase
edges:
- target: CP14RitualEnd
triggers:
- !type:CP14VoiceTrigger
message: "Vespere nebula"
actions:
- !type:ApplyEntityEffect
maxEntities: 3
vfx: CP14ImpactEffectCureWounds
whitelist:
components:
- HumanoidAppearance
effects:
- !type:HealthChange
damage:
types:
Asphyxiation: -50
Bloodloss: -10
- !type:ModifyBleedAmount
- !type:ModifyBloodLevel
- !type:Jitter

View File

@@ -0,0 +1,22 @@
- type: entity
id: CP14BaseRitualPhase
abstract: true
components:
- type: CP14MagicRitualPhase
- type: Tag
tags:
- HideContextMenu
- type: PointLight
- type: EmitSoundOnSpawn
sound:
path: /Audio/Effects/tesla_consume.ogg
params:
variation: 0.3
- type: entity
parent: CP14BaseRitualPhase
id: CP14RitualEnd
name: end of ritual
components:
- type: CP14MagicRitualPhase
deadEnd: true

View File

@@ -0,0 +1,39 @@
- type: entity
id: CP14BaseRitualOrb
abstract: true
components:
- type: Transform
- type: Physics
canCollide: false
- type: Sprite
sprite: _CP14/Effects/Magic/ritual_orbs.rsi
drawDepth: Mobs
noRot: true
- type: entity
parent: CP14BaseRitualOrb
id: CP14RitualOrbCreation
name: creation orb
categories: [ HideSpawnMenu]
components:
- type: CP14MagicRitualOrb
powers:
Creation: 1
- type: Sprite
layers:
- state: creation
shader: unshaded
- type: entity
parent: CP14BaseRitualOrb
id: CP14RitualOrbDestruction
name: destruction orb
categories: [ HideSpawnMenu]
components:
- type: CP14MagicRitualOrb
powers:
Destruction: 1
- type: Sprite
layers:
- state: destruction
shader: unshaded

View File

@@ -8,7 +8,12 @@
- type: Sprite
noRot: true
sprite: _CP14/Structures/Decoration/statue_gob.rsi
state: gob
layers:
- state: gob
- state: eyes
map: ["ritual"]
visible: false
shader: unshaded
drawdepth: Mobs
offset: "0.0,0.5"
- type: Fixtures
@@ -43,6 +48,9 @@
CP14StoneBlock1:
min: 4
max: 5
- type: CP14MagicRitual
startPhase: CP14_NeutralCluster_Root
- type: Appearance
- type: entity
id: CP14StatueGobVines
@@ -51,9 +59,12 @@
suffix: Normal. Overgrown.
components:
- type: Sprite
noRot: true
sprite: _CP14/Structures/Decoration/statue_gob.rsi
state: gob_vines
layers:
- state: gob_vines
- state: eyes
map: ["ritual"]
visible: false
shader: unshaded
- type: entity
id: CP14StatueGobRuined
@@ -62,9 +73,12 @@
suffix: Ruined
components:
- type: Sprite
noRot: true
sprite: _CP14/Structures/Decoration/statue_gob.rsi
state: gob_ruined
layers:
- state: gob_ruined
- state: eyes
map: ["ritual"]
visible: false
shader: unshaded
- type: entity
id: CP14StatueGobRuinedVines
@@ -73,6 +87,9 @@
suffix: Ruined. Overgrown.
components:
- type: Sprite
noRot: true
sprite: _CP14/Structures/Decoration/statue_gob.rsi
state: gob_ruined_vines
layers:
- state: gob_ruined_vines
- state: eyes
map: ["ritual"]
visible: false
shader: unshaded

View File

@@ -171,6 +171,71 @@
loot:
All: CP14GatherLumiMushroom
- type: entity
parent: BaseRock
id: CP14QuartzCrystal
name: quartz
description: Quartz is an essential mineral capable of interacting with magical energy. It is highly sought after by alchemists for extracting beneficial properties from liquids
components:
- type: Sprite
drawdepth: Mobs
sprite: _CP14/Structures/crystal.rsi
offset: 0, 0.25
noRot: true
layers:
- state: big
map: ["random"]
- type: InteractionPopup
interactSuccessString: popup-cp14crystal-ding
messagePerceivedByOthers: popup-cp14crystal-ding
interactSuccessSound:
collection: CP14CrystalDings
params:
variation: 0.03
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Glass
- type: MeleeSound
soundGroups:
Brute:
collection: GlassSmash
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 20
behaviors:
- !type:PlaySoundBehavior
sound:
collection: GlassBreak
- !type:DoActsBehavior
acts: [ "Destruction" ]
- !type:SpawnEntitiesBehavior
spawn:
CP14QuartzShard:
min: 1
max: 3
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeCircle
radius: 0.30
density: 60
mask:
- MachineMask
layer:
- MidImpassable
- LowImpassable
- BulletImpassable
- Opaque
- type: RandomSprite
available:
- random:
big: ""
medium: ""
small: ""
# Blue amanita
- type: entityLootTable
@@ -203,4 +268,4 @@
world5: ""
- type: Gatherable
loot:
All: CP14GatherBlueAmanita
All: CP14GatherBlueAmanita

View File

@@ -11,6 +11,8 @@
- type: Sprite
drawdepth: Mobs
- type: CP14Wallmount
- type: Physics
canCollide: false
- type: entity
id: CP14WallmountTorch

View File

@@ -3,12 +3,10 @@
- type: entity
id: CP14WallmountCrystalBase
parent:
- BaseStructure
- CP14BaseWallmount
abstract: true
name: sparkling quartz
description: bioluminescent quartz crystals that can take on any color - a very handy light source in a deep caves. Unfortunately, the luminous properties are very hard to preserve.
placement:
mode: SnapgridCenter
components:
- type: Sprite
drawdepth: Mobs

View File

@@ -0,0 +1,9 @@
- type: magicType
id: Creation
name: cp14-magic-type-creation
color: "#4bcc7d"
- type: magicType
id: Destruction
name: cp14-magic-type-destruction
color: "#c94c1a"

View File

@@ -52,24 +52,7 @@
- FloraRockSolid01
- FloraRockSolid02
- FloraRockSolid03
- CP14CrystalRubiesSmall
- CP14CrystalRubiesMedium
- CP14CrystalRubiesBig
- CP14CrystalTopazesSmall
- CP14CrystalTopazesMedium
- CP14CrystalTopazesBig
- CP14CrystalEmeraldsSmall
- CP14CrystalEmeraldsMedium
- CP14CrystalEmeraldsBig
- CP14CrystalSapphiresSmall
- CP14CrystalSapphiresMedium
- CP14CrystalSapphiresBig
- CP14CrystalAmethystsSmall
- CP14CrystalAmethystsMedium
- CP14CrystalAmethystsBig
- CP14CrystalDiamondsSmall
- CP14CrystalDiamondsMedium
- CP14CrystalDiamondsBig
- CP14QuartzCrystal
- !type:BiomeEntityLayer # lumishroom sage
threshold: 0.6
noise:

View File

@@ -35,4 +35,16 @@
files:
- /Audio/_CP14/Items/broom1.ogg
- /Audio/_CP14/Items/broom2.ogg
- /Audio/_CP14/Items/broom3.ogg
- /Audio/_CP14/Items/broom3.ogg
- type: soundCollection
id: CP14Parry
files:
- /Audio/_CP14/Effects/parry1.ogg
- /Audio/_CP14/Effects/parry2.ogg
- type: soundCollection
id: CP14Book
files:
- /Audio/_CP14/Items/book1.ogg
- /Audio/_CP14/Items/book2.ogg

View File

@@ -3,7 +3,7 @@
id: OpenWinds
sound:
params:
volume: -3
volume: -6
collection: CP14LoopWinds
rules: OpenWinds
@@ -15,6 +15,14 @@
collection: CP14LoopWater
rules: NearWater
- type: ambientLoop
id: Cave
sound:
params:
volume: 2
collection: CP14LoopCave
rules: CaveFloor
# Sound collections
- type: soundCollection
id: CP14LoopWinds
@@ -26,6 +34,11 @@
files:
- /Audio/_CP14/Ambience/Loops/water.ogg
- type: soundCollection
id: CP14LoopCave
files:
- /Audio/_CP14/Ambience/Loops/cave.ogg
# Rules
- type: rules
id: OpenWinds
@@ -41,6 +54,16 @@
- CP14FloorSand
range: 5
- type: rules
id: CaveFloor
rules:
- !type:NearbyTilesPercentRule
ignoreAnchored: true
percent: 0.5
tiles:
- CP14FloorBase
range: 5
- type: rules
id: NearWater
rules:

View File

@@ -1,8 +1,25 @@
# 4 Priority - Special
- type: ambientMusic
id: CP14Ritual
sound:
params:
volume: -12
collection: CP14AmbienceMagic
rules: NearRitual
priority: 4
# 3 Priority - Departments
# 2 Priority - General areas
- type: ambientMusic
id: CP14NightForest
sound:
params:
volume: -6
collection: CP14NightForest
rules: NightForest
priority: 2
## Fallback if nothing else found
- type: ambientMusic
@@ -28,8 +45,61 @@
- /Audio/_CP14/Ambience/ambicreepy2.ogg
- /Audio/_CP14/Ambience/ambicreepy3.ogg
- type: soundCollection
id: CP14AmbienceMagic
files:
- /Audio/_CP14/Ambience/ambimagic1.ogg
- /Audio/_CP14/Ambience/ambimagic2.ogg
- /Audio/_CP14/Ambience/ambimagic3.ogg
- /Audio/_CP14/Ambience/ambimagic4.ogg
- /Audio/_CP14/Ambience/ambimagic5.ogg
- /Audio/_CP14/Ambience/ambimagic6.ogg
- /Audio/_CP14/Ambience/ambiAlchemy.ogg
- type: soundCollection
id: CP14NightForest
files:
- /Audio/_CP14/Animals/owl1.ogg
- /Audio/_CP14/Animals/owl2.ogg
- /Audio/_CP14/Animals/owl3.ogg
- /Audio/_CP14/Animals/owl4.ogg
- /Audio/_CP14/Animals/owl5.ogg
- /Audio/_CP14/Animals/owl6.ogg
- /Audio/_CP14/Animals/owl7.ogg
- /Audio/_CP14/Animals/owl8.ogg
- /Audio/_CP14/Animals/owl9.ogg
# Rules
- type: rules
id: AlwaysTrue
rules:
- !type:AlwaysTrueRule
- !type:AlwaysTrueRule
- type: rules
id: NearRitual
rules:
- !type:NearbyEntitiesRule
count: 1
whitelist:
components:
- CP14MagicRitualPhase
- CP14MagicRitual
range: 6
- type: rules
id: NightForest
rules:
- !type:CP14TimePeriod
periods:
- Night
- Evening
- !type:NearbyTilesPercentRule
ignoreAnchored: true
percent: 0.5
tiles:
- CP14FloorGrass
- CP14FloorGrassLight
- CP14FloorGrassTall
- CP14FloorDirt
- CP14FloorSand
range: 5

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

View File

@@ -0,0 +1,33 @@
{
"version": 1,
"size": {
"x": 16,
"y": 16
},
"license": "CLA",
"copyright": "Created by TheShuEd for CrystallPunk",
"states": [
{
"name": "creation",
"delays": [
[
0.2,
0.2,
0.2,
0.2
]
]
},
{
"name": "destruction",
"delays": [
[
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

View File

@@ -0,0 +1,22 @@
{
"version": 1,
"license": "CLA",
"copyright": "Created by TheShuEd (Github)",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

View File

@@ -1,7 +1,7 @@
{
"version": 1,
"license": "CLA",
"copyright": "Created by omsoyk (Github/Discord)",
"copyright": "Created by TheShuEd (Github)",
"size": {
"x": 32,
"y": 32

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 150 B