Gods and religions (#1405)

* basic religion vision

* block god interactions

* skill tree mob specify

* silvania setup??

* clustering shader optimization

* gods department & job

* random gods jobs

* Luxian god

* Update Nature.png

* Update sphere_of_light.yml

* god chat

* Update ChatSystem.cs

* OBSERVATION

* public observation and basic follower api

* shader tweaks

* improve shaders

* spawning and praying on altars

* altars ppvs override

* move pvs overridiation from altars to observers

* shader coloration

* spectral z mover

* god magic radius restricted

* guide how to believe in god

* sends messages to god when smoeone wanna become follower

* follower doAfter

* goodbye luxian, welcome lumera

* goodbye silvania, welcome merkas

* som polish and renamings

* gods fast travel

* Update altar.ftl

* some lumera sfx

* renouncing patrons!

* renounce followers

* followewr percentage calculation

* remove from player-facing

* fix

* Update sphere_of_light.yml

* Update base.yml
This commit is contained in:
Red
2025-06-13 14:15:48 +03:00
committed by GitHub
parent 32be823619
commit 422a0c5e10
101 changed files with 2217 additions and 103 deletions

View File

@@ -0,0 +1,83 @@
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Religion;
public sealed partial class CP14ClientReligionGodSystem : CP14SharedReligionGodSystem
{
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
[Dependency] private readonly IPlayerManager _player = default!;
private CP14ReligionVisionOverlay? _overlay;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14ReligionVisionComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<CP14ReligionVisionComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<CP14ReligionVisionComponent, ComponentInit>(OnOverlayInit);
SubscribeLocalEvent<CP14ReligionVisionComponent, ComponentRemove>(OnOverlayRemove);
}
protected override void SendMessageToGods(ProtoId<CP14ReligionPrototype> religion, string msg, EntityUid source) { }
public override void Shutdown()
{
base.Shutdown();
_overlayMgr.RemoveOverlay<CP14ReligionVisionOverlay>();
}
private void OnPlayerAttached(Entity<CP14ReligionVisionComponent> ent, ref LocalPlayerAttachedEvent args)
{
AddOverlay();
}
private void OnPlayerDetached(Entity<CP14ReligionVisionComponent> ent, ref LocalPlayerDetachedEvent args)
{
RemoveOverlay();
}
private void OnOverlayInit(Entity<CP14ReligionVisionComponent> ent, ref ComponentInit args)
{
var attachedEnt = _player.LocalEntity;
if (attachedEnt != ent.Owner)
return;
AddOverlay();
}
private void OnOverlayRemove(Entity<CP14ReligionVisionComponent> ent, ref ComponentRemove args)
{
var attachedEnt = _player.LocalEntity;
if (attachedEnt != ent.Owner)
return;
RemoveOverlay();
}
private void AddOverlay()
{
if (_overlay != null)
return;
_overlay = new CP14ReligionVisionOverlay();
_overlayMgr.AddOverlay(_overlay);
}
private void RemoveOverlay()
{
if (_overlay == null)
return;
_overlayMgr.RemoveOverlay(_overlay);
_overlay = null;
}
}

View File

@@ -0,0 +1,35 @@
using Content.Client._CP14.DemiplaneTraveling;
using Content.Shared._CP14.DemiplaneTraveling;
using Content.Shared._CP14.Religion.Systems;
using Robust.Client.UserInterface;
namespace Content.Client._CP14.Religion;
public sealed class CP14ReligionEntityBoundUserInterface : BoundUserInterface
{
private CP14ReligionEntityWindow? _window;
public CP14ReligionEntityBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<CP14ReligionEntityWindow>();
_window.OnTeleportAttempt += netId => SendMessage(new CP14ReligionEntityTeleportAttempt(netId));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_window == null || state is not CP14ReligionEntityUiState mapState)
return;
_window?.UpdateState(mapState);
}
}

View File

@@ -0,0 +1,28 @@
<religion:CP14ReligionEntityWindow
xmlns="https://spacestation14.io"
xmlns:religion="clr-namespace:Content.Client._CP14.Religion"
Title="{Loc 'cp14-god-ui-title'}"
SetSize="600 500"
MinSize="600 100">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<Label Name="Altars" Text="{Loc 'cp14-god-ui-altars'}" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center" HorizontalExpand="True" HorizontalAlignment="Center"/>
<BoxContainer
Name="AltarsContainer"
Orientation="Vertical"
HorizontalExpand="True" />
<Label Name="Followers" Text="{Loc 'cp14-god-ui-follower'}" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center" HorizontalExpand="True" HorizontalAlignment="Center" Margin="0, 30, 0, 0"/>
<BoxContainer
Name="FollowersContainer"
Orientation="Vertical"
HorizontalExpand="True" />
</BoxContainer>
</ScrollContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<RichTextLabel Name="Status"/>
</BoxContainer>
</BoxContainer>
</religion:CP14ReligionEntityWindow>

View File

@@ -0,0 +1,69 @@
using Content.Shared._CP14.Religion.Systems;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._CP14.Religion;
[GenerateTypedNameReferences]
public sealed partial class CP14ReligionEntityWindow : DefaultWindow
{
[Dependency] private readonly ILogManager _log = default!;
private ISawmill Sawmill { get; init; }
public event Action<NetEntity>? OnTeleportAttempt;
public CP14ReligionEntityWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Sawmill = _log.GetSawmill("cp14_religion_entity_window");
}
public void UpdateState(CP14ReligionEntityUiState state)
{
AltarsContainer.RemoveAllChildren();
FollowersContainer.RemoveAllChildren();
Altars.Visible = state.Altars.Count > 0;
Followers.Visible = state.Followers.Count > 0;
foreach (var (netId, name) in state.Altars)
{
var btn = new Button
{
Text = name,
HorizontalAlignment = HAlignment.Center
};
btn.OnPressed += _ =>
{
OnTeleportAttempt?.Invoke(netId);
};
AltarsContainer.AddChild(btn);
}
foreach (var (netId, name) in state.Followers)
{
var btn = new Button
{
Text = name,
HorizontalAlignment = HAlignment.Center
};
btn.OnPressed += _ =>
{
OnTeleportAttempt?.Invoke(netId);
};
FollowersContainer.AddChild(btn);
}
Status.Text = GetStatusText(state);
}
private string GetStatusText(CP14ReligionEntityUiState state)
{
return Loc.GetString("cp14-god-ui-follower-percentage", ("count", state.FollowerPercentage * 100));
}
}

View File

@@ -0,0 +1,148 @@
using System.Numerics;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Religion;
public sealed class CP14ReligionVisionOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly SharedTransformSystem _transform;
/// <summary>
/// Maximum number of observers zones that can be shown on screen at a time.
/// If this value is changed, the shader itself also needs to be updated.
/// </summary>
public const int MaxCount = 64;
public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ProtoId<CP14ReligionPrototype>? _religion = null;
private readonly ShaderInstance _religionShader;
private readonly Vector2[] _positions = new Vector2[MaxCount];
private readonly float[] _radii = new float[MaxCount];
private int _count = 0;
public CP14ReligionVisionOverlay()
{
IoCManager.InjectDependencies(this);
_religionShader = _proto.Index<ShaderPrototype>("CP14ReligionVision").InstanceUnique();
_transform = _entManager.System<SharedTransformSystem>();
if (_entManager.TryGetComponent<CP14ReligionEntityComponent>(_player.LocalEntity, out var vision))
{
_religion = vision.Religion;
}
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (args.Viewport.Eye == null)
return false;
_count = 0;
var clusters = new List<Cluster>();
var religionQuery = _entManager.AllEntityQueryEnumerator<CP14ReligionObserverComponent, TransformComponent>();
while (religionQuery.MoveNext(out var uid, out var rel, out var xform))
{
if (_religion is null)
continue;
var observation = rel.Observation;
if (!observation.ContainsKey(_religion.Value))
continue;
if (!rel.Active || xform.MapID != args.MapId)
continue;
var mapPos = _transform.GetWorldPosition(uid);
// To be clear, this needs to use "inside-viewport" pixels.
// In other words, specifically NOT IViewportControl.WorldToScreen (which uses outer coordinates).
var tempCoords = args.Viewport.WorldToLocal(mapPos);
tempCoords.Y = args.Viewport.Size.Y - tempCoords.Y; // Local space to fragment space.
// try find cluster to merge with
bool merged = false;
foreach (var cluster in clusters)
{
if ((cluster.Position - tempCoords).Length() < 150f)
{
cluster.Add(tempCoords, rel.Observation[_religion.Value]);
merged = true;
break;
}
}
if (!merged)
clusters.Add(new Cluster(tempCoords, rel.Observation[_religion.Value]));
if (clusters.Count >= MaxCount)
break;
}
_count = 0;
foreach (var cluster in clusters)
{
_positions[_count] = cluster.Position;
_radii[_count] = cluster.Radius;
_count++;
}
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null || args.Viewport.Eye == null)
return;
if (!_entManager.TryGetComponent<CP14ReligionVisionComponent>(_player.LocalEntity, out var visionComponent))
return;
_religionShader?.SetParameter("shaderColor", visionComponent.ShaderColor);
_religionShader?.SetParameter("renderScale", args.Viewport.RenderScale * args.Viewport.Eye.Scale);
_religionShader?.SetParameter("count", _count);
_religionShader?.SetParameter("position", _positions);
_religionShader?.SetParameter("radius", _radii);
_religionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
var worldHandle = args.WorldHandle;
worldHandle.UseShader(_religionShader);
worldHandle.DrawRect(args.WorldAABB, Color.White);
worldHandle.UseShader(null);
}
private sealed class Cluster
{
public Vector2 Position;
public float Radius;
public int Count;
public Cluster(Vector2 pos, float radius)
{
Position = pos;
Radius = radius;
Count = 1;
}
public void Add(Vector2 pos, float radius)
{
Position = (Position * Count + pos) / (Count + 1);
Radius = Math.Max(Radius, radius);
Count++;
}
}
}

View File

@@ -37,7 +37,6 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
private EntityUid? _targetPlayer;
private IEnumerable<CP14SkillPrototype> _allSkills = [];
private IEnumerable<CP14SkillTreePrototype> _allTrees = [];
private CP14SkillPrototype? _selectedSkill;
private CP14SkillTreePrototype? _selectedSkillTree;
@@ -72,7 +71,6 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
private void CacheSkillProto()
{
_allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>();
_allTrees = _proto.EnumeratePrototypes<CP14SkillTreePrototype>().OrderBy(tree => Loc.GetString(tree.Name));
}
public void OnStateExited(GameplayState state)
@@ -293,9 +291,9 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
//If tree not selected, select the first one
if (_selectedSkillTree == null)
{
var firstTree = _allTrees.First();
var firstTree = storage.AvailableSkillTrees.First();
SelectTree(firstTree, storage); // Set the first tree from the player's progress
SelectTree(firstTree); // Set the first tree from the player's progress
}
if (_selectedSkillTree == null)
@@ -308,8 +306,11 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
_window.LevelLabel.Text = $"{storage.SkillsSumExperience}/{storage.ExperienceMaxCap}";
_window.TreeTabsContainer.RemoveAllChildren();
foreach (var tree in _allTrees)
foreach (var tree in storage.AvailableSkillTrees)
{
if (!_proto.TryIndex(tree, out var indexedTree))
return;
float learnedPoints = 0;
foreach (var skillId in storage.LearnedSkills)
{
@@ -322,25 +323,28 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
}
}
var treeButton2 = new CP14SkillTreeButtonControl(tree.Color, Loc.GetString(tree.Name), learnedPoints);
treeButton2.ToolTip = Loc.GetString(tree.Desc ?? string.Empty);
var treeButton2 = new CP14SkillTreeButtonControl(indexedTree.Color, Loc.GetString(indexedTree.Name), learnedPoints);
treeButton2.ToolTip = Loc.GetString(indexedTree.Desc ?? string.Empty);
treeButton2.OnPressed += () =>
{
SelectTree(tree, storage);
SelectTree(indexedTree);
};
_window.TreeTabsContainer.AddChild(treeButton2);
}
}
private void SelectTree(CP14SkillTreePrototype tree, CP14SkillStorageComponent storage)
private void SelectTree(ProtoId<CP14SkillTreePrototype> tree)
{
if (_window == null)
return;
_selectedSkillTree = tree;
_window.ParallaxBackground.ParallaxPrototype = tree.Parallax;
_window.TreeName.Text = Loc.GetString(tree.Name);
if (!_proto.TryIndex(tree, out var indexedTree))
return;
_selectedSkillTree = indexedTree;
_window.ParallaxBackground.ParallaxPrototype = indexedTree.Parallax;
_window.TreeName.Text = Loc.GetString(indexedTree.Name);
UpdateGraphControl();
}

View File

@@ -6,10 +6,12 @@ using Content.Server.Administration.Managers;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.Players.RateLimiting;
using Content.Server.Radio.EntitySystems;
using Content.Server.Speech.Prototypes;
using Content.Server.Speech.EntitySystems;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared._CP14.Religion.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration;
using Content.Shared.CCVar;
@@ -60,6 +62,9 @@ public sealed partial class ChatSystem : SharedChatSystem
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
//CP14
[Dependency] private readonly RadioSystem _radio = default!;
//CP14 end
public const int VoiceRange = 10; // how far voice goes in world units
public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
@@ -174,6 +179,15 @@ public sealed partial class ChatSystem : SharedChatSystem
bool ignoreActionBlocker = false
)
{
//CP14 Zone
if (HasComp<CP14ReligionEntityComponent>(source))
{
TryProccessRadioMessage(source, message, out var modMessage, out var channel);
_radio.SendRadioMessage(source, modMessage, "CP14Gods", source);
return;
}
//CP14 Zone end
if (HasComp<GhostComponent>(source))
{
// Ghosts can only send dead chat messages, so we'll forward it to InGame OOC.

View File

@@ -27,4 +27,7 @@ public enum SpawnPointType
LateJoin,
Job,
Observer,
//CP14
Always, //Always use only these spawn point, and latejoin, and roundstart
//CP14 end
}

View File

@@ -32,6 +32,15 @@ public sealed class SpawnPointSystem : EntitySystem
if (args.Station != null && _stationSystem.GetOwningStation(uid, xform) != args.Station)
continue;
//CP14 always spawn gods on gods spawnpoints
if (spawnPoint.SpawnType == SpawnPointType.Always && (args.Job == null || spawnPoint.Job == args.Job))
{
possiblePositions.Clear();
possiblePositions.Add(xform.Coordinates);
break;
}
//CP14end
if (_gameTicker.RunLevel == GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.LateJoin)
{
possiblePositions.Add(xform.Coordinates);

View File

@@ -6,6 +6,7 @@ using Content.Server.Popups;
using Content.Shared._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared._CP14.DemiplaneTraveling;
using Content.Shared._CP14.Religion.Components;
using Content.Shared.Ghost;
using Content.Shared.Item;
using Content.Shared.Movement.Pulling.Components;
@@ -58,6 +59,8 @@ public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
{
if (HasComp<GhostComponent>(ent))
continue;
if (HasComp<CP14ReligionEntityComponent>(ent)) //TODO: make some generic way to whitelist entities from teleporting
continue;
if (!_mind.TryGetMind(ent, out var mindId, out var mind))
continue;

View File

@@ -0,0 +1,26 @@
using Content.Shared.Destructible.Thresholds;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.RandomJobs;
[RegisterComponent, Access(typeof(CP14StationRandomJobsSystem))]
public sealed partial class CP14StationRandomJobsComponent : Component
{
[DataField]
public List<CP14RandomJobEntry> Entries = new();
}
[Serializable, DataDefinition]
public sealed partial class CP14RandomJobEntry
{
[DataField(required: true)]
public List<ProtoId<JobPrototype>> Jobs = default!;
[DataField(required: true)]
public MinMax Count = new(1, 1);
[DataField]
public float Prob = 1f;
}

View File

@@ -0,0 +1,52 @@
using Content.Server.Station.Events;
using Content.Server.Station.Systems;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.RandomJobs;
public sealed partial class CP14StationRandomJobsSystem : EntitySystem
{
[Dependency] private readonly StationJobsSystem _jobs = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StationInitializedEvent>(OnInit, after: new[] { typeof(StationJobsSystem) });
}
private void OnInit(StationInitializedEvent args)
{
if (!TryComp<CP14StationRandomJobsComponent>(args.Station, out var randomJobs))
return;
foreach (var entry in randomJobs.Entries)
{
if (!_random.Prob(entry.Prob))
continue;
var count = entry.Count.Next(_random);
var tempList = new List<ProtoId<JobPrototype>>(entry.Jobs);
for (var i = 0; i < count; i++)
{
if (tempList.Count == 0)
break;
var job = _random.Pick(tempList);
tempList.Remove(job);
if (!_proto.TryIndex(job, out var jobProto))
continue;
_jobs.TryAdjustJobSlot(args.Station, jobProto, 1, createSlot: true);
}
}
}
}

View File

@@ -0,0 +1,75 @@
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Systems;
using Content.Shared.Follower;
using Robust.Server.GameObjects;
namespace Content.Server._CP14.Religion;
public sealed partial class CP14ReligionGodSystem
{
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly FollowerSystem _follower = default!;
private void InitializeUI()
{
SubscribeLocalEvent<CP14ReligionEntityComponent, OpenBoundInterfaceMessage>(OnOpenInterface);
SubscribeLocalEvent<CP14ReligionEntityComponent, CP14ReligionEntityTeleportAttempt>(OnTeleportAttempt);
}
private void OnTeleportAttempt(Entity<CP14ReligionEntityComponent> ent, ref CP14ReligionEntityTeleportAttempt args)
{
var target = GetEntity(args.Entity);
var canTeleport = false;
if (TryComp<CP14ReligionAltarComponent>(target, out var altar))
{
if (altar.Religion == ent.Comp.Religion)
{
canTeleport = true;
}
}
else if (TryComp<CP14ReligionFollowerComponent>(target, out var follower))
{
if (follower.Religion == ent.Comp.Religion)
{
canTeleport = true;
}
}
if (!canTeleport)
return;
_follower.StartFollowingEntity(ent, target);
}
private void OnOpenInterface(Entity<CP14ReligionEntityComponent> ent, ref OpenBoundInterfaceMessage args)
{
if (ent.Comp.Religion is null)
return;
var altars = new Dictionary<NetEntity, string>();
var queryAltars = EntityQueryEnumerator<CP14ReligionAltarComponent, MetaDataComponent>();
while (queryAltars.MoveNext(out var uid, out var altar, out var meta))
{
if (altar.Religion != ent.Comp.Religion)
continue;
altars.TryAdd(GetNetEntity(uid), meta.EntityName);
}
var followers = new Dictionary<NetEntity, string>();
var queryFollowers = EntityQueryEnumerator<CP14ReligionFollowerComponent, MetaDataComponent>();
while (queryFollowers.MoveNext(out var uid, out var follower, out var meta))
{
if (follower.Religion != ent.Comp.Religion)
continue;
followers.TryAdd(GetNetEntity(uid), meta.EntityName);
}
var followerPercentage = GetFollowerPercentage(ent);
ent.Comp.FollowerPercentage = followerPercentage;
Dirty(ent);
_userInterface.SetUiState(ent.Owner, CP14ReligionEntityUiKey.Key, new CP14ReligionEntityUiState(altars, followers, followerPercentage));
}
}

View File

@@ -0,0 +1,177 @@
using Content.Server.Chat.Managers;
using Content.Server.Speech;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Content.Shared.Chat;
using Robust.Server.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Religion;
public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
public override void Initialize()
{
base.Initialize();
InitializeUI();
SubscribeLocalEvent<CP14ReligionObserverComponent, ComponentInit>(OnObserverInit);
SubscribeLocalEvent<CP14ReligionObserverComponent, AfterAutoHandleStateEvent>(OnObserverHandleState);
SubscribeLocalEvent<CP14ReligionEntityComponent, ComponentInit>(OnGodInit);
SubscribeLocalEvent<CP14ReligionEntityComponent, ComponentShutdown>(OnGodShutdown);
SubscribeLocalEvent<CP14ReligionEntityComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<CP14ReligionEntityComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<CP14ReligionAltarComponent, ListenEvent>(OnListen);
}
private void OnObserverHandleState(Entity<CP14ReligionObserverComponent> ent, ref AfterAutoHandleStateEvent args)
{
var query = EntityQueryEnumerator<CP14ReligionEntityComponent>();
while (query.MoveNext(out var uid, out var god))
{
UpdatePvsOverrides(new Entity<CP14ReligionEntityComponent>(uid, god));
}
}
private void OnObserverInit(Entity<CP14ReligionObserverComponent> ent, ref ComponentInit args)
{
foreach (var (religion, _) in ent.Comp.Observation)
{
var gods = GetGods(religion);
foreach (var god in gods)
{
UpdatePvsOverrides(god);
}
}
}
private void OnGodInit(Entity<CP14ReligionEntityComponent> ent, ref ComponentInit args)
{
AddPvsOverrides(ent);
}
private void OnGodShutdown(Entity<CP14ReligionEntityComponent> ent, ref ComponentShutdown args)
{
RemovePvsOverrides(ent);
}
private void OnPlayerAttached(Entity<CP14ReligionEntityComponent> ent, ref PlayerAttachedEvent args)
{
AddPvsOverrides(ent);
}
private void OnPlayerDetached(Entity<CP14ReligionEntityComponent> ent, ref PlayerDetachedEvent args)
{
RemovePvsOverrides(ent);
}
private void OnListen(Entity<CP14ReligionAltarComponent> ent, ref ListenEvent args)
{
if (ent.Comp.Religion is null)
return;
var wrappedMessage =
Loc.GetString("cp14-altar-wrapped-message", ("name", MetaData(args.Source).EntityName), ("msg", args.Message));
SendMessageToGods(ent.Comp.Religion.Value, wrappedMessage, args.Source);
}
protected override void SendMessageToGods(ProtoId<CP14ReligionPrototype> religion, string msg, EntityUid source)
{
var gods = GetGods(religion);
HashSet<INetChannel> channels = new();
foreach (var god in gods)
{
if (!TryComp<ActorComponent>(god, out var godActor))
continue;
channels.Add(godActor.PlayerSession.Channel);
}
_chat.ChatMessageToMany(ChatChannel.Notifications, msg, msg, source, false, true, channels, colorOverride: Color.Aqua);
}
public float GetFollowerPercentage(Entity<CP14ReligionEntityComponent> god)
{
var total = 0;
var followers = 0;
var allHumans = Mind.GetAliveHumans();
foreach (var human in allHumans)
{
total++;
if (!TryComp<CP14ReligionFollowerComponent>(human.Comp.CurrentEntity, out var relFollower))
continue;
if (relFollower.Religion != god.Comp.Religion)
continue;
followers++;
}
if (total == 0)
return 0f;
return (float)followers / total;
}
private void AddPvsOverrides(Entity<CP14ReligionEntityComponent> ent)
{
if (ent.Comp.Religion is null)
return;
if (!TryComp<ActorComponent>(ent, out var actor))
return;
ent.Comp.Session = actor.PlayerSession;
var query = EntityQueryEnumerator<CP14ReligionObserverComponent>();
while (query.MoveNext(out var uid, out var observer))
{
if (!observer.Observation.ContainsKey(ent.Comp.Religion.Value))
continue;
if (observer.Observation[ent.Comp.Religion.Value] <= 6.5f) //Maybe there is a variable for the distance outside the screen in PVS, I don't know. This number works best
continue;
ent.Comp.PvsOverridedObservers.Add(uid);
_pvs.AddSessionOverride(uid, actor.PlayerSession);
}
}
private void RemovePvsOverrides(Entity<CP14ReligionEntityComponent> ent)
{
if (ent.Comp.Religion is null)
return;
if (ent.Comp.Session is null)
return;
foreach (var altar in ent.Comp.PvsOverridedObservers)
{
_pvs.RemoveSessionOverride(altar, ent.Comp.Session);
}
ent.Comp.Session = null;
ent.Comp.PvsOverridedObservers.Clear();
}
private void UpdatePvsOverrides(Entity<CP14ReligionEntityComponent> ent)
{
if (ent.Comp.Session is null)
return;
RemovePvsOverrides(ent);
AddPvsOverrides(ent);
}
}

View File

@@ -1,4 +1,3 @@
using System.Numerics;
using Content.Shared._CP14.ZLevel;
using Content.Shared.Ghost;
using Robust.Shared.Map;
@@ -9,11 +8,13 @@ public sealed partial class CP14StationZLevelsSystem
{
private void InitActions()
{
SubscribeLocalEvent<GhostComponent, CP14ZLevelActionUp>(OnZLevelUp);
SubscribeLocalEvent<GhostComponent, CP14ZLevelActionDown>(OnZLevelDown);
SubscribeLocalEvent<GhostComponent, CP14ZLevelActionUp>(OnZLevelUpGhost);
SubscribeLocalEvent<GhostComponent, CP14ZLevelActionDown>(OnZLevelDownGhost);
SubscribeLocalEvent<SpectralComponent, CP14ZLevelActionUp>(OnZLevelUp);
SubscribeLocalEvent<SpectralComponent, CP14ZLevelActionDown>(OnZLevelDown);
}
private void OnZLevelDown(Entity<GhostComponent> ent, ref CP14ZLevelActionDown args)
private void OnZLevelDownGhost(Entity<GhostComponent> ent, ref CP14ZLevelActionDown args)
{
if (args.Handled)
return;
@@ -23,7 +24,27 @@ public sealed partial class CP14StationZLevelsSystem
args.Handled = true;
}
private void OnZLevelUp(Entity<GhostComponent> ent, ref CP14ZLevelActionUp args)
private void OnZLevelUpGhost(Entity<GhostComponent> ent, ref CP14ZLevelActionUp args)
{
if (args.Handled)
return;
ZLevelMove(ent, 1);
args.Handled = true;
}
private void OnZLevelDown(Entity<SpectralComponent> ent, ref CP14ZLevelActionDown args)
{
if (args.Handled)
return;
ZLevelMove(ent, -1);
args.Handled = true;
}
private void OnZLevelUp(Entity<SpectralComponent> ent, ref CP14ZLevelActionUp args)
{
if (args.Handled)
return;

View File

@@ -49,12 +49,6 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory CP14RitualBook = new("cp14-verb-categories-ritual-book", null);
public static readonly VerbCategory CP14CurrencyConvert = new("cp14-verb-categories-currency-converter", null); //CP14
public static readonly VerbCategory CP14AdminSkillAdd =
new ("cp14-verb-categories-admin-skill-add", null, iconsOnly: true) { Columns = 6 };
public static readonly VerbCategory CP14AdminSkillRemove =
new ("cp14-verb-categories-admin-skill-remove", null, iconsOnly: true) { Columns = 6 };
//CP14 verbs
public static readonly VerbCategory Admin =

View File

@@ -1,5 +1,8 @@
using Content.Shared._CP14.MagicSpell.Components;
using Content.Shared._CP14.MagicSpell.Events;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Damage.Components;
using Content.Shared.Hands.Components;
@@ -7,12 +10,14 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Speech.Muting;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell;
public abstract partial class CP14SharedMagicSystem
{
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly CP14SharedReligionGodSystem _god = default!;
private void InitializeChecks()
{
@@ -22,12 +27,14 @@ public abstract partial class CP14SharedMagicSystem
SubscribeLocalEvent<CP14MagicEffectStaminaCostComponent, CP14CastMagicEffectAttemptEvent>(OnStaminaCheck);
SubscribeLocalEvent<CP14MagicEffectPacifiedBlockComponent, CP14CastMagicEffectAttemptEvent>(OnPacifiedCheck);
SubscribeLocalEvent<CP14MagicEffectAliveTargetRequiredComponent, CP14CastMagicEffectAttemptEvent>(OnMobStateCheck);
SubscribeLocalEvent<CP14MagicEffectReligionRestrictedComponent, CP14CastMagicEffectAttemptEvent>(OnReligionRestrictedCheck);
//Verbal speaking
SubscribeLocalEvent<CP14MagicEffectVerbalAspectComponent, CP14StartCastMagicEffectEvent>(OnVerbalAspectStartCast);
SubscribeLocalEvent<CP14MagicEffectVerbalAspectComponent, CP14MagicEffectConsumeResourceEvent>(OnVerbalAspectAfterCast);
SubscribeLocalEvent<CP14MagicEffectEmotingComponent, CP14StartCastMagicEffectEvent>(OnEmoteStartCast);
SubscribeLocalEvent<CP14MagicEffectEmotingComponent, CP14MagicEffectConsumeResourceEvent>(OnEmoteEndCast);
}
/// <summary>
@@ -148,6 +155,18 @@ public abstract partial class CP14SharedMagicSystem
}
}
private void OnReligionRestrictedCheck(Entity<CP14MagicEffectReligionRestrictedComponent> ent, ref CP14CastMagicEffectAttemptEvent args)
{
if (!TryComp<CP14ReligionEntityComponent>(args.Performer, out var religionComp))
return;
if (args.Position is not null && _god.InVision(args.Position.Value, (args.Performer, religionComp)))
return;
args.Cancel();
}
private void OnVerbalAspectStartCast(Entity<CP14MagicEffectVerbalAspectComponent> ent,
ref CP14StartCastMagicEffectEvent args)
{

View File

@@ -0,0 +1,12 @@
using Content.Shared._CP14.MagicSpellStorage;
using Content.Shared.FixedPoint;
namespace Content.Shared._CP14.MagicSpell.Components;
/// <summary>
/// If the user belongs to a religion, this spell can only be used within the area of influence of that religion
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicSystem), typeof(CP14SpellStorageSystem))]
public sealed partial class CP14MagicEffectReligionRestrictedComponent : Component
{
}

View File

@@ -0,0 +1,23 @@
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Systems;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellGodRenounce : CP14SpellEffect
{
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
if (!entManager.TryGetComponent<CP14ReligionEntityComponent>(args.User, out var god) || god.Religion is null)
return;
if (!entManager.TryGetComponent<CP14ReligionFollowerComponent>(args.Target.Value, out var follower) || follower.Religion != god.Religion)
return;
var religionSys = entManager.System<CP14SharedReligionGodSystem>();
religionSys.ToDisbelieve(args.Target.Value);
}
}

View File

@@ -0,0 +1,24 @@
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellGodTouch : CP14SpellEffect
{
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
if (!entManager.TryGetComponent<CP14ReligionEntityComponent>(args.User, out var god) || god.Religion is null)
return;
var ev = new CP14GodTouchEvent(god.Religion.Value);
entManager.EventBus.RaiseLocalEvent(args.Target.Value, ev);
}
}
public sealed class CP14GodTouchEvent(ProtoId<CP14ReligionPrototype> religion) : EntityEventArgs
{
public ProtoId<CP14ReligionPrototype> Religion = religion;
}

View File

@@ -0,0 +1,16 @@
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(CP14SharedReligionGodSystem))]
public sealed partial class CP14ReligionAltarComponent : Component
{
[DataField, AutoNetworkedField]
public ProtoId<CP14ReligionPrototype>? Religion;
[DataField, AutoNetworkedField]
public bool CanBeConverted = true;
}

View File

@@ -0,0 +1,27 @@
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Components;
/// <summary>
///
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(CP14SharedReligionGodSystem))]
public sealed partial class CP14ReligionEntityComponent : Component
{
[DataField(required: true)]
public ProtoId<CP14ReligionPrototype>? Religion;
public HashSet<EntityUid> PvsOverridedObservers = new();
public ICommonSession? Session;
/// <summary>
/// Number of followers as a percentage. Automatically calculated on the server and sent to the client for data synchronization.
/// </summary>
[DataField, AutoNetworkedField]
public FixedPoint2 FollowerPercentage = 0;
}

View File

@@ -0,0 +1,25 @@
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Components;
/// <summary>
/// Determines whether the entity is a follower of God, or may never be able to become one
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(CP14SharedReligionGodSystem))]
public sealed partial class CP14ReligionFollowerComponent : Component
{
[DataField, AutoNetworkedField]
public ProtoId<CP14ReligionPrototype>? Religion;
[DataField, AutoNetworkedField]
public HashSet<ProtoId<CP14ReligionPrototype>> RejectedReligions = new();
[DataField]
public EntProtoId RenounceActionProto = "CP14ActionRenounceFromGod";
[DataField]
public EntityUid? RenounceAction;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Components;
/// <summary>
/// Allows the god of a particular religion to see within a radius around the observer.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(CP14SharedReligionGodSystem))]
public sealed partial class CP14ReligionObserverComponent : Component
{
[DataField, AutoNetworkedField]
public Dictionary<ProtoId<CP14ReligionPrototype>, float> Observation = new(); //DAMNATION
[DataField, AutoNetworkedField]
public bool Active = true;
}

View File

@@ -0,0 +1,17 @@
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Content.Shared.Alert;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Components;
/// <summary>
/// This entity has not yet become a follower of God, but wants to become one. Confirmation from god is expected
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(CP14SharedReligionGodSystem))]
public sealed partial class CP14ReligionPendingFollowerComponent : Component
{
[DataField, AutoNetworkedField]
public ProtoId<CP14ReligionPrototype>? Religion;
}

View File

@@ -0,0 +1,14 @@
using Content.Shared._CP14.Religion.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Religion.Components;
/// <summary>
/// Limits the vision of entities, allowing them to see only areas within a radius around observers of their religion.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(CP14SharedReligionGodSystem))]
public sealed partial class CP14ReligionVisionComponent : Component
{
[DataField]
public Vector3 ShaderColor = new (1f, 1f, 1f);
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Prototypes;
/// <summary>
///
/// </summary>
[Prototype("cp14Religion")]
public sealed partial class CP14ReligionPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
[DataField]
public float FollowerObservationRadius = 8f;
[DataField]
public float AltarObservationRadius = 25f;
}

View File

@@ -0,0 +1,106 @@
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared.DoAfter;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Religion.Systems;
public abstract partial class CP14SharedReligionGodSystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
private void InitializeAltars()
{
SubscribeLocalEvent<CP14ReligionAltarComponent, GetVerbsEvent<ActivationVerb>>(GetBaseVerb);
SubscribeLocalEvent<CP14ReligionAltarComponent, GetVerbsEvent<AlternativeVerb>>(GetAltVerb);
}
private void GetBaseVerb(Entity<CP14ReligionAltarComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
{
}
private void GetAltVerb(Entity<CP14ReligionAltarComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (ent.Comp.Religion is null)
return;
var disabled = !CanBecomeFollower(args.User, ent.Comp.Religion.Value);
if (!disabled && TryComp<CP14ReligionPendingFollowerComponent>(args.User, out var pendingFollower))
{
if (pendingFollower.Religion is not null)
disabled = true;
}
if (disabled)
return;
var user = args.User;
args.Verbs.Add(new AlternativeVerb()
{
Text = Loc.GetString("cp14-altar-become-follower"),
Message = Loc.GetString("cp14-altar-become-follower-desc"),
Act = () =>
{
var doAfterArgs = new DoAfterArgs(EntityManager, user, 5f, new CP14AltarOfferDoAfter(), ent, used: ent)
{
BreakOnDamage = true,
BreakOnMove = true,
};
_doAfter.TryStartDoAfter(doAfterArgs);
},
});
}
public bool TryConvertAltar(EntityUid target, ProtoId<CP14ReligionPrototype> religion)
{
if (!_proto.TryIndex(religion, out var indexedReligion))
return false;
EnsureComp<CP14ReligionAltarComponent>(target, out var altar);
if (!altar.CanBeConverted)
return false;
var oldReligion = altar.Religion;
altar.Religion = religion;
Dirty(target, altar);
EditObservation(target, religion, indexedReligion.AltarObservationRadius);
var ev = new CP14ReligionChangedEvent(oldReligion, religion);
RaiseLocalEvent(target, ev);
return true;
}
public void DeconvertAltar(EntityUid target)
{
if (!TryComp<CP14ReligionAltarComponent>(target, out var altar))
return;
if (altar.Religion is null)
return;
if (!_proto.TryIndex(altar.Religion, out var indexedReligion))
return;
EditObservation(target, altar.Religion.Value, -indexedReligion.AltarObservationRadius);
var oldReligion = altar.Religion;
altar.Religion = null;
var ev = new CP14ReligionChangedEvent(oldReligion, null);
RaiseLocalEvent(target, ev);
Dirty(target, altar);
}
}
[Serializable, NetSerializable]
public sealed partial class CP14AltarOfferDoAfter : SimpleDoAfterEvent
{
}

View File

@@ -0,0 +1,154 @@
using Content.Shared._CP14.MagicSpell.Spells;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared.Actions;
using Content.Shared.Alert;
using Content.Shared.Mind;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Systems;
public abstract partial class CP14SharedReligionGodSystem
{
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] protected readonly SharedMindSystem Mind = default!;
private void InitializeFollowers()
{
SubscribeLocalEvent<CP14ReligionPendingFollowerComponent, MapInitEvent>(OnPendingFollowerInit);
SubscribeLocalEvent<CP14ReligionPendingFollowerComponent, ComponentShutdown>(OnPendingFollowerShutdown);
SubscribeLocalEvent<CP14ReligionPendingFollowerComponent, CP14BreakDivineOfferEvent>(OnBreakDivineOffer);
SubscribeLocalEvent<CP14ReligionPendingFollowerComponent, CP14GodTouchEvent>(OnGodTouch);
SubscribeLocalEvent<CP14ReligionAltarComponent, CP14AltarOfferDoAfter>(OnOfferDoAfter);
SubscribeLocalEvent<CP14ReligionFollowerComponent, CP14RenounceFromGodEvent>(OnRenounceFromGod);
}
private void OnRenounceFromGod(Entity<CP14ReligionFollowerComponent> ent, ref CP14RenounceFromGodEvent args)
{
ToDisbelieve(ent);
}
private void OnOfferDoAfter(Entity<CP14ReligionAltarComponent> ent, ref CP14AltarOfferDoAfter args)
{
if (args.Handled || args.Cancelled)
return;
if (ent.Comp.Religion is null)
return;
TryAddPendingFollower(args.User, ent.Comp.Religion.Value);
args.Handled = true;
}
private void OnGodTouch(Entity<CP14ReligionPendingFollowerComponent> ent, ref CP14GodTouchEvent args)
{
if (args.Religion != ent.Comp.Religion)
return;
TryToBelieve(ent);
}
private void OnBreakDivineOffer(Entity<CP14ReligionPendingFollowerComponent> ent, ref CP14BreakDivineOfferEvent args)
{
RemCompDeferred<CP14ReligionPendingFollowerComponent>(ent);
if (ent.Comp.Religion is null)
return;
SendMessageToGods(ent.Comp.Religion.Value, Loc.GetString("cp14-unoffer-soul-god-message", ("name", MetaData(ent).EntityName)), ent);
}
private void OnPendingFollowerInit(Entity<CP14ReligionPendingFollowerComponent> ent, ref MapInitEvent args)
{
_alerts.ShowAlert(ent, "CP14DivineOffer");
}
private void OnPendingFollowerShutdown(Entity<CP14ReligionPendingFollowerComponent> ent, ref ComponentShutdown args)
{
_alerts.ClearAlert(ent, "CP14DivineOffer");
}
private bool CanBecomeFollower(EntityUid target, ProtoId<CP14ReligionPrototype> religion)
{
if (HasComp<CP14ReligionEntityComponent>(target))
return false;
EnsureComp<CP14ReligionFollowerComponent>(target, out var follower);
return !follower.RejectedReligions.Contains(religion);
}
private void TryAddPendingFollower(EntityUid target, ProtoId<CP14ReligionPrototype> religion)
{
if (!CanBecomeFollower(target, religion))
return;
EnsureComp<CP14ReligionPendingFollowerComponent>(target, out var pendingFollower);
pendingFollower.Religion = religion;
SendMessageToGods(religion, Loc.GetString("cp14-offer-soul-god-message", ("name", MetaData(target).EntityName)), target);
}
private bool TryToBelieve(Entity<CP14ReligionPendingFollowerComponent> pending)
{
if (pending.Comp.Religion is null)
return false;
if (!_proto.TryIndex(pending.Comp.Religion, out var indexedReligion))
return false;
if (!CanBecomeFollower(pending, pending.Comp.Religion.Value))
return false;
EnsureComp<CP14ReligionFollowerComponent>(pending, out var follower);
var oldReligion = follower.Religion;
follower.Religion = pending.Comp.Religion;
Dirty(pending, follower);
EditObservation(pending, pending.Comp.Religion.Value, indexedReligion.FollowerObservationRadius);
var ev = new CP14ReligionChangedEvent(oldReligion, pending.Comp.Religion);
RaiseLocalEvent(pending, ev);
RemCompDeferred<CP14ReligionPendingFollowerComponent>(pending);
SendMessageToGods(pending.Comp.Religion.Value, Loc.GetString("cp14-become-follower-message", ("name", MetaData(pending).EntityName)), pending);
_actions.AddAction(pending, ref follower.RenounceAction, follower.RenounceActionProto);
return true;
}
public void ToDisbelieve(EntityUid target)
{
if (!TryComp<CP14ReligionFollowerComponent>(target, out var follower))
return;
if (follower.Religion is null)
return;
if (!_proto.TryIndex(follower.Religion, out var indexedReligion))
return;
SendMessageToGods(follower.Religion.Value, Loc.GetString("cp14-remove-follower-message", ("name", MetaData(target).EntityName)), target);
EditObservation(target, follower.Religion.Value, -indexedReligion.FollowerObservationRadius);
var oldReligion = follower.Religion;
follower.Religion = null;
if (oldReligion is not null)
follower.RejectedReligions.Add(oldReligion.Value);
var ev = new CP14ReligionChangedEvent(oldReligion, null);
RaiseLocalEvent(target, ev);
Dirty(target, follower);
_actions.RemoveAction(target, follower.RenounceAction);
}
}
public sealed partial class CP14BreakDivineOfferEvent : BaseAlertEvent;
public sealed partial class CP14RenounceFromGodEvent : InstantActionEvent;

View File

@@ -0,0 +1,96 @@
using System.Numerics;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Systems;
public abstract partial class CP14SharedReligionGodSystem
{
private void InitializeObservation()
{
SubscribeLocalEvent<CP14ReligionEntityComponent, InRangeOverrideEvent>(OnGodInRange);
SubscribeLocalEvent<CP14ReligionEntityComponent, MenuVisibilityEvent>(OnGodMenu);
}
private void OnGodInRange(Entity<CP14ReligionEntityComponent> ent, ref InRangeOverrideEvent args)
{
args.Handled = true;
args.InRange = InVision(args.Target, ent);
}
private void OnGodMenu(Entity<CP14ReligionEntityComponent> ent, ref MenuVisibilityEvent args)
{
args.Visibility &= ~MenuVisibility.NoFov;
}
public void EditObservation(EntityUid target, ProtoId<CP14ReligionPrototype> religion, float range)
{
EnsureComp<CP14ReligionObserverComponent>(target, out var observer);
if (observer.Observation.ContainsKey(religion))
{
var newRange = Math.Clamp(observer.Observation[religion] + range, 0, float.MaxValue);
if (newRange <= 0)
{
observer.Observation.Remove(religion);
}
else
{
observer.Observation[religion] = newRange;
}
}
else
{
// Otherwise, add a new observation for the religion.
observer.Observation.Add(religion, range);
}
Dirty(target, observer);
}
public bool InVision(EntityUid target, Entity<CP14ReligionEntityComponent> user)
{
var position = Transform(target).Coordinates;
return InVision(position, user);
}
public bool InVision(EntityCoordinates coords, Entity<CP14ReligionEntityComponent> user)
{
if (!HasComp<CP14ReligionVisionComponent>(user))
return true;
var userXform = Transform(user);
var query = EntityQueryEnumerator<CP14ReligionObserverComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var observer, out var xform))
{
if (!observer.Active)
continue;
if (xform.MapID != userXform.MapID)
continue;
if (user.Comp.Religion is null)
continue;
if (!observer.Observation.ContainsKey(user.Comp.Religion.Value))
continue;
var obsPos = _transform.GetWorldPosition(uid);
var targetPos = coords.Position;
if (Vector2.Distance(obsPos, targetPos) <= observer.Observation[user.Comp.Religion.Value])
{
// If the observer is within range of the target, they can see it.
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,23 @@
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Religion.Systems;
[Serializable, NetSerializable]
public enum CP14ReligionEntityUiKey
{
Key,
}
[Serializable, NetSerializable]
public sealed class CP14ReligionEntityUiState(Dictionary<NetEntity, string> altars, Dictionary<NetEntity, string> followers, float followerPercentage) : BoundUserInterfaceState
{
public Dictionary<NetEntity, string> Altars = altars;
public Dictionary<NetEntity, string> Followers = followers;
public float FollowerPercentage = followerPercentage;
}
[Serializable, NetSerializable]
public sealed class CP14ReligionEntityTeleportAttempt(NetEntity entity) : BoundUserInterfaceMessage
{
public readonly NetEntity Entity = entity;
}

View File

@@ -0,0 +1,46 @@
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Religion.Systems;
public abstract partial class CP14SharedReligionGodSystem : EntitySystem
{
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
InitializeObservation();
InitializeFollowers();
InitializeAltars();
}
public HashSet<Entity<CP14ReligionEntityComponent>> GetGods(ProtoId<CP14ReligionPrototype> religion)
{
HashSet<Entity<CP14ReligionEntityComponent>> gods = new();
var query = EntityQueryEnumerator<CP14ReligionEntityComponent>();
while (query.MoveNext(out var uid, out var god))
{
if (god.Religion != religion)
continue;
gods.Add(new Entity<CP14ReligionEntityComponent>(uid, god));
}
return gods;
}
protected abstract void SendMessageToGods(ProtoId<CP14ReligionPrototype> religion, string msg, EntityUid source);
}
/// <summary>
/// It is invoked on altars and followers when they change their religion.
/// </summary>
public sealed class CP14ReligionChangedEvent(ProtoId<CP14ReligionPrototype>? oldRel, ProtoId<CP14ReligionPrototype>? newRel) : EntityEventArgs
{
public ProtoId<CP14ReligionPrototype>? OldReligion = oldRel;
public ProtoId<CP14ReligionPrototype>? NewReligion = newRel;
}

View File

@@ -174,6 +174,10 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
if (HaveSkill(target, skill, component))
return false;
//Check if the skill is in the available skill trees
if (!component.AvailableSkillTrees.Contains(skill.Tree))
return false;
//Check max cap
if (component.SkillsSumExperience + skill.LearnCost > component.ExperienceMaxCap)
return false;

View File

@@ -51,58 +51,16 @@ public abstract partial class CP14SharedSkillSystem
var target = args.Target;
//Add Skill
foreach (var skill in _allSkills)
{
if (ent.Comp.LearnedSkills.Contains(skill))
continue;
var name = Loc.GetString(GetSkillName(skill));
args.Verbs.Add(new Verb
{
Text = name,
Message = name + ": " + Loc.GetString(GetSkillDescription(skill)),
Category = VerbCategory.CP14AdminSkillAdd,
Icon = skill.Icon,
Act = () =>
{
TryAddSkill(target, skill);
},
});
}
//Remove Skill
foreach (var skill in ent.Comp.LearnedSkills)
{
if (!_proto.TryIndex(skill, out var indexedSkill))
continue;
var name = Loc.GetString(GetSkillName(skill));
args.Verbs.Add(new Verb
{
Text = name,
Message = name + ": " + Loc.GetString(GetSkillDescription(skill)),
Category = VerbCategory.CP14AdminSkillRemove,
Icon = indexedSkill.Icon,
Act = () =>
{
TryRemoveSkill(target, skill);
},
});
}
//Reset/Remove All Skills
args.Verbs.Add(new Verb
{
Text = "Reset skills",
Message = "Remove all learned skills",
Category = VerbCategory.CP14AdminSkillRemove,
Icon = new SpriteSpecifier.Rsi(new("/Textures/_CP14/Interface/Misc/reroll.rsi"), "reroll"),
Act = () =>
{
TryResetSkills(target);
},
});
}
}

View File

@@ -14,9 +14,15 @@ namespace Content.Shared._CP14.Skill.Components;
[Access(typeof(CP14SharedSkillSystem), typeof(CP14SharedResearchSystem))]
public sealed partial class CP14SkillStorageComponent : Component
{
/// <summary>
/// Skill trees displayed in the skill tree interface. Only skills from these trees can be learned by this player.
/// </summary>
[DataField]
public HashSet<ProtoId<CP14SkillTreePrototype>> AvailableSkillTrees = new();
/// <summary>
/// Tracks skills that are learned without spending memory points.
/// the skills that are here are DUBLED in the LearnedSkills,
/// the skills that are here are DOUBLED in the LearnedSkills,
/// </summary>
[DataField, AutoNetworkedField]
public List<ProtoId<CP14SkillPrototype>> FreeLearnedSkills = new();

View File

@@ -41,7 +41,4 @@ public sealed partial class CP14SkillTreePrototype : IPrototype
[DataField]
public SoundSpecifier LearnSound = new SoundCollectionSpecifier("CP14LearnSkill");
[DataField]
public SpriteSpecifier? Icon = null;
}

View File

@@ -0,0 +1,27 @@
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill.Restrictions;
public sealed partial class GodFollowerPercentage : CP14SkillRestriction
{
[DataField]
public FixedPoint2 Percentage = 0.5f;
public override bool Check(IEntityManager entManager, EntityUid target, CP14SkillPrototype skill)
{
if (!entManager.TryGetComponent<CP14ReligionEntityComponent>(target, out var god))
return false;
if (god.Religion is null)
return false;
return god.FollowerPercentage >= Percentage;
}
public override string GetDescription(IEntityManager entManager, IPrototypeManager protoManager)
{
return Loc.GetString("cp14-skill-req-god-follower-percentage", ("count", Percentage * 100));
}
}

View File

@@ -0,0 +1 @@
cp14-chat-radio-gods = Divine

View File

@@ -11,4 +11,7 @@ department-CP14Guard = Guards
department-CP14Guard-desc = Protectors and warriors who oversee security and law and order in all corners of the Empire.
department-CP14Artisan = Artisans
department-CP14Artisan-desc = People who have learnt peaceful professions, people who help the settlement with their knowledge and skills.
department-CP14Artisan-desc = People who have learnt peaceful professions, people who help the settlement with their knowledge and skills.
department-CP14Demigods = Patrons
department-CP14Demigods-desc = Higher beings playing their own games, where ordinary creatures are merely pawns in their plans.

View File

@@ -34,4 +34,12 @@ cp14-job-name-blacksmith = Blacksmith
cp14-job-desc-blacksmith = Create and improve equipment for everyone in need! You have the power of metal and fire in your hands, and only you know how to use them carefully to create masterpieces.
cp14-job-name-apprentice = Apprentice
cp14-job-desc-apprentice = A peaceful citizen of the empire, just beginning to learn the subtleties of various sciences. Choose a specialisation in equipment, and try to help others in their work, in exchange for a salary and invaluable experience.
cp14-job-desc-apprentice = A peaceful citizen of the empire, just beginning to learn the subtleties of various sciences. Try to help others in their work, in exchange for a salary and invaluable experience.
# Demigods
cp14-job-name-god-merkas = Merkas
cp14-job-desc-god-merkas = God of purity and healing. TODO
cp14-job-name-god-lumera = Lumera
cp14-job-desc-god-lumera = Patroness of the night and the starry sky. TODO

View File

@@ -0,0 +1,19 @@
cp14-altar-wrapped-message = [bold]{$name}[/bold] prays, {$msg}
cp14-offer-soul-god-message = [bold]{$name}[/bold] [color=green]wants to become your follower[/color]. Touch him to establish a connection.
cp14-unoffer-soul-god-message = [bold]{$name}[/bold] [color=red]has changed his mind about becoming your follower.[/color]
cp14-become-follower-message = [bold]{$name}[/bold] [color=green]becomes your follower[/color]!
cp14-remove-follower-message = [bold]{$name}[/bold] [color=red]rejects you and will never be able to return to you![/color]
cp14-renounce-action-popup = YOU ARE RENOUNCING YOUR PATRON! To confirm, perform the action again.
cp-renounce-action-god-popup = YOU ARE REJECTING YOUR FOLLOWER! To confirm, perform the action again.
cp14-god-ui-title = Fast Travel
cp14-god-ui-follower = Followers
cp14-god-ui-altars = Altars
cp14-god-ui-follower-percentage = Follower percentage: {$count}%
cp14-altar-become-follower = Become a follower
cp14-altar-become-follower-desc = You offer yourself into the service of your patron. If he agrees, your bond will be strengthened.
cp14-alert-offer = Offer of patronage
cp14-alert-offer-desc = You want to become a follower of the patron, but there is no response from him yet. Click to cancel the offer.

View File

@@ -1,4 +1,5 @@
cp14-skill-req-prerequisite = Skill "{$name}" must be learned
cp14-skill-req-species = You must be the race of “{$name}”
cp14-skill-req-researched = A study needs to be done on the research table
cp14-skill-req-impossible = Unable to explore during a round at the current moment
cp14-skill-req-impossible = Unable to explore during a round at the current moment
cp14-skill-req-god-follower-percentage = The number of your followers should be more than {$count}%

View File

@@ -0,0 +1,3 @@
cp14-skill-lumera-t1-name = The origins of the secret night
cp14-skill-lumera-t2-name = The waxing moon
cp14-skill-lumera-t3-name = The full moon of Lumera

View File

@@ -0,0 +1 @@
cp14-chat-radio-gods = Божественный

View File

@@ -11,4 +11,7 @@ department-CP14Guard = Стража
department-CP14Guard-desc = Защитники и войны, следящие за безопасностью и правопорядком во всех уголках империи.
department-CP14Artisan = Ремесленники
department-CP14Artisan-desc = Освоившие мирные профессии, люди, которые помогают поселению своими знаниями и умениями.
department-CP14Artisan-desc = Освоившие мирные профессии, люди, которые помогают поселению своими знаниями и умениями.
department-CP14Demigods = Покровители
department-CP14Demigods-desc = Высшие сущности, играющие в свои собственные игры, где обычные существа лишь пешки в их планах.

View File

@@ -34,4 +34,12 @@ cp14-job-name-blacksmith = Кузнец
cp14-job-desc-blacksmith = Создавайте и улучшайте экипировку для всех нуждающихся! В ваших руках мощь металла и огня, и только вы знаете как аккуратно использовать их, чтобы создавать шедевры.
cp14-job-name-apprentice = Подмастерье
cp14-job-desc-apprentice = Мирный житель империи, только начинающий постигать тонкости различных наук. Выберите специализацию в экипировке, и постарайтесь помочь другим в их работе, в обмен на зарплату и бесценный опыт.
cp14-job-desc-apprentice = Мирный житель империи, только начинающий постигать тонкости различных наук. Постарайтесь помочь другим в их работе, в обмен на зарплату и бесценный опыт.
# Demigods
cp14-job-name-god-merkas = Меркас
cp14-job-desc-god-merkas = Бог света и исцеления. TODO
cp14-job-name-god-lumera = Лумера
cp14-job-desc-god-lumera = Покровительница ночи и звёздного неба. TODO

View File

@@ -0,0 +1,19 @@
cp14-altar-wrapped-message = [bold]{$name}[/bold] молится, {$msg}
cp14-offer-soul-god-message = [bold]{$name}[/bold] [color=green]хочет стать вашим последователем[/color]. Прикоснитесь к нему, чтобы установить связь.
cp14-unoffer-soul-god-message = [bold]{$name}[/bold] [color=red]передумал становиться вашим последователем.[/color]
cp14-become-follower-message = [bold]{$name}[/bold] [color=green]становится вашим последователем[/color]!
cp14-remove-follower-message = [bold]{$name}[/bold] [color=red]отвергает вас, и больше никогда не сможет вернться к вам![/color]
cp14-renounce-action-popup = ВЫ ОТРЕКАЕТЕСЬ ОТ ПОКРОВИТЕЛЯ! Для подтверждения выполните действие еще раз.
cp-renounce-action-god-popup = ВЫ ОТВЕРГАЕТЕ СВОЕГО ПОСЛЕДОВАТЕЛЯ! Для подтверждения выполните действие еще раз.
cp14-god-ui-title = Быстрое перемещение
cp14-god-ui-follower = Последователи
cp14-god-ui-altars = Алтари
cp14-god-ui-follower-percentage = Процент последователей: {$count}%
cp14-altar-become-follower = Стать последователем
cp14-altar-become-follower-desc = Вы предлагаете себя в службу покровителю. Если он согласится, ваша связь укрепится.
cp14-alert-offer = Предложение о покровительстве
cp14-alert-offer-desc = Вы хотите стать последователем покровителя, но пока от него нет ответа. Нажмите, чтобы отменить предложение.

View File

@@ -1,4 +1,5 @@
cp14-skill-req-prerequisite = Навык "{$name}" должен быть изучен
cp14-skill-req-species = Вы должны быть расы "{$name}"
cp14-skill-req-researched = Необходимо провести исследование на исследовательском столе
cp14-skill-req-impossible = Невозможно изучить во время раунда на текущий момент
cp14-skill-req-impossible = Невозможно изучить во время раунда на текущий момент
cp14-skill-req-god-follower-percentage = Количество ваших последователей должно быть больше {$count}%

View File

@@ -0,0 +1,3 @@
cp14-skill-lumera-t1-name = Истоки тайной ночи
cp14-skill-lumera-t2-name = Растущая луна
cp14-skill-lumera-t3-name = Полнолуние Лумеры

View File

@@ -48,3 +48,11 @@
name: alerts-hunger-name
description: alerts-hunger-desc
- type: alert
id: CP14DivineOffer
icons:
- sprite: /Textures/_CP14/Interface/Alerts/divine_offer.rsi
state: offer
name: cp14-alert-offer
description: cp14-alert-offer-desc
clickEvent: !type:CP14BreakDivineOfferEvent

View File

@@ -0,0 +1,45 @@
- type: entity
id: CP14ActionSpellGodLumeraRenounce
name: Renunciation of a follower
description: You are rejecting the chosen follower. They lose the opportunity to become your follower at any time.
components:
- type: CP14MagicEffect
effects:
- !type:CP14SpellSpawnEntityOnTarget
spawns:
- CP14LumeraRenounceImpact
- !type:CP14SpellGodRenounce
- type: ConfirmableAction
popup: cp-renounce-action-god-popup
- type: EntityWorldTargetAction
repeat: true
checkCanAccess: false
itemIconStyle: BigAction
range: 100
sound: !type:SoundPathSpecifier
path: /Audio/Magic/rumble.ogg
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: renounce
event: !type:CP14EntityWorldTargetActionEvent
cooldown: 0.5
- type: entity
id: CP14LumeraRenounceImpact
categories: [ ForkFiltered ]
parent: CP14BaseMagicImpact
save: false
components:
- type: PointLight
color: "#94154e"
enabled: true
radius: 5
energy: 4
netsync: false
- type: Sprite
layers:
- state: stars
color: "#94154e"
shader: unshaded
- type: LightFade
duration: 1

View File

@@ -0,0 +1,49 @@
- type: entity
id: CP14ActionSpellGodLumeraTouch
name: Touch of Lumera
description: "Multitasking effects on the world: depending on what you click on, the effect may vary. Using it on an empty space will create a glowing sign that attracts the attention of mortals."
components:
- type: CP14MagicEffectReligionRestricted
- type: CP14MagicEffectManaCost
manaCost: 5
- type: CP14MagicEffect
effects:
- !type:CP14SpellSpawnEntityOnTarget
spawns:
- CP14LumeraTouchImpact
- !type:CP14SpellGodTouch
- !type:CP14SpellApplyEntityEffect
effects:
- !type:Jitter
- type: EntityWorldTargetAction
repeat: true
checkCanAccess: false
itemIconStyle: BigAction
range: 100
sound: !type:SoundPathSpecifier
path: /Audio/Magic/rumble.ogg
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: touch
event: !type:CP14EntityWorldTargetActionEvent
cooldown: 0.5
- type: entity
id: CP14LumeraTouchImpact
categories: [ ForkFiltered ]
parent: CP14BaseMagicImpact
save: false
components:
- type: PointLight
color: "#3843a8"
enabled: true
radius: 5
energy: 4
netsync: false
- type: Sprite
layers:
- state: stars
color: "#3843a8"
shader: unshaded
- type: LightFade
duration: 1

View File

@@ -0,0 +1,12 @@
- type: entity
id: CP14ActionSpellGodLumeraWarp
name: Fast travel
description: Allows you to quickly teleport to your altars and followers.
components:
- type: InstantAction
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: warp
priority: -8
event: !type:ToggleIntrinsicUIEvent
key: enum.CP14ReligionEntityUiKey.Key

View File

@@ -0,0 +1,13 @@
- type: entity
id: CP14ActionRenounceFromGod
name: Renounce patron
description: You renounce your patron by severing your connection with him. After that, you can never become his follower again, but you can become a follower of another patron.
components:
- type: InstantAction
icon:
sprite: _CP14/Interface/Alerts/divine_offer.rsi
state: unoffer
priority: -8
event: !type:CP14RenounceFromGodEvent
- type: ConfirmableAction
popup: cp14-renounce-action-popup

View File

@@ -0,0 +1,113 @@
- type: entity
id: CP14MobGodBase
parent:
- Incorporeal
- BaseMob
- CP14MobMagical
name: god
description: The supreme entity formed from the beliefs, desires, and fears of the human race.
abstract: true
components:
- type: Sprite
sprite: _CP14/Mobs/Demigods/temp_icons.rsi
color: "#ffffff99"
- type: PointLight
radius: 10
softness: 1
castShadows: false
- type: Input
context: "ghost"
- type: Spectral
- type: MovementSpeedModifier
baseWalkSpeed: 3
baseSprintSpeed: 6
friction: 0.5
acceleration: 3
- type: NoSlip
- type: Eye
drawFov: false
visMask:
- Normal
- Ghost
- type: Visibility
layer: 2 #ghost vis layer
- type: ContentEye
maxZoom: 1.2, 1.2
- type: Speech
speechVerb: Ghost
- type: CP14ReligionVision
- type: CP14MagicEnergyContainer
magicAlert: CP14MagicEnergy
maxEnergy: 1000
energy: 1000
unsafeSupport: false
- type: CP14MagicEnergyDraw
energy: 5
delay: 6 # 20m to full restore
- type: IntrinsicRadioTransmitter
channels:
- CP14Gods
- type: IntrinsicRadioReceiver
- type: ActiveRadio
receiveAllChannels: true
globalReceive: true
channels:
- CP14Gods
- type: CP14SpellStorage
grantAccessToSelf: true
spells:
- CP14ActionZLevelUp
- CP14ActionZLevelDown
- ActionToggleLighting
- ActionToggleFov
- type: CP14DemiplaneStabilizer # teleports gods outside when the demiplane closes
enabled: false
- type: UserInterface
interfaces:
enum.CP14ReligionEntityUiKey.Key:
type: CP14ReligionEntityBoundUserInterface
- type: entity
parent: CP14MobGodBase
id: CP14MobGodMerkas
name: Merkas
categories: [ HideSpawnMenu ]
description: Merkas is the god of purity and healing. In his presence, decay is destroyed. He does not save — he purifies. Those who remain can start anew.
components:
- type: Sprite
layers:
- state: merkas
shader: unshaded
- type: PointLight
color: "#9bf2b5"
- type: CP14SkillStorage
availableSkillTrees:
- GodMerkas
- type: CP14ReligionEntity
religion: Merkas
- type: CP14ReligionVision
shaderColor: 0.02, 0.36, 0.05
- type: entity
parent: CP14MobGodBase
id: CP14MobGodLumera
name: Lumera
description: The goddess of the night and the night sky. Her stars hold so many secrets, which she protects with her wings.
components:
- type: Sprite
layers:
- state: lumera
shader: unshaded
- type: PointLight
color: "#4367ba"
- type: CP14SkillStorage
availableSkillTrees:
- GodLumera
- type: CP14ReligionEntity
religion: Lumera
- type: CP14ReligionVision
shaderColor: 0.29, 0.66, 0.87
- type: IntrinsicUI
uis:
enum.CP14ReligionEntityUiKey.Key:
toggleAction: CP14ActionSpellGodLumeraWarp

View File

@@ -231,7 +231,6 @@
types:
Cold: 0.25
Bloodloss: 0.25
- type: CP14SkillStorage
- type: CP14TradingReputation

View File

@@ -57,6 +57,16 @@
delay: 3 # 5m to full restore
- type: CP14MagicUnsafeDamage
- type: CP14MagicUnsafeSleep
- type: CP14SkillStorage
availableSkillTrees:
- Pyrokinetic
- Hydrosophistry
- Illusion
- Metamagic
- Healing
- Atlethic
- MartialArts
- Craftsmanship
- type: entity

View File

@@ -10,6 +10,7 @@
- CP14BaseStationCommonObjectives
- CP14BaseStationDemiplaneMap
- CP14BaseStationEconomy
#- CP14BaseStationGods
- type: entity
id: CP14BaseStationCommonObjectives
@@ -35,3 +36,16 @@
abstract: true
components:
- type: CP14StationEconomy
#- type: entity
# id: CP14BaseStationGods
# abstract: true
# components:
# - type: CP14StationRandomJobs
# entries:
# - jobs:
# - CP14GodMerkas
# - CP14GodLumera
# count:
# min: 2
# max: 2

View File

@@ -470,4 +470,3 @@
CP14BirchWoodLog:
min: 3
max: 6

View File

@@ -0,0 +1,35 @@
- type: entity
id: CP14BaseAltar
parent: BaseStructure
abstract: true
name: altar
description: TODO
categories: [ ForkFiltered ]
components:
- type: Sprite
noRot: true
drawdepth: Mobs
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 60
mask:
- MachineMask
layer:
- MachineLayer
- type: CP14ReligionAltar
- type: ActiveListener
range: 1
- type: entity
parent: CP14BaseAltar
id: CP14BaseAltarPrimordial
abstract: true
components:
- type: CP14ReligionAltar
canBeConverted: false
- type: SpawnPoint
spawn_type: Always

View File

@@ -0,0 +1,35 @@
- type: entity
parent: CP14BaseAltarPrimordial
id: CP14AltarPrimordialGodLumera
name: primordial statue of Lumera
description: "The enchanting statue of Lumera, patroness of the starry sky, night, and mysteries. She does not demand worship—she simply observes. And waits for you to ask."
components:
- type: Sprite
sprite: Structures/Furniture/Altars/Gods/convertaltar.rsi
layers:
- state: white
- type: CP14ReligionAltar
religion: Lumera
- type: CP14ReligionObserver
observation:
Lumera: 25
- type: SpawnPoint
job_id: CP14GodLumera
- type: entity
parent: CP14BaseAltarPrimordial
id: CP14AltarPrimordialGodMerkas
name: primordial statue of Merkas
description: A beautiful, overgrown statue of Sylvania, revered as a goddess of nature.
components:
- type: Sprite
sprite: Structures/Furniture/Altars/Gods/nanotrasen.rsi
layers:
- state: druid
- type: CP14ReligionAltar
religion: Merkas
- type: CP14ReligionObserver
observation:
Merkas: 25
- type: SpawnPoint
job_id: CP14GodMerkas

View File

@@ -0,0 +1,5 @@
- type: cp14Religion
id: Merkas
- type: cp14Religion
id: Lumera

View File

@@ -0,0 +1,16 @@
- type: job
id: CP14GodLumera
name: cp14-job-name-god-lumera
description: cp14-job-desc-god-lumera
setPreference: false
playTimeTracker: CP14GodLumera
requirements:
- !type:OverallPlaytimeRequirement
time: 36000 # 10 hrs
canBeAntag: false
icon: CP14JobIconGodLumera
jobEntity: CP14MobGodLumera
joinNotifyCrew: false
requireAdminNotify: true
jobPreviewEntity: CP14MobGodLumera
applyTraits: false

View File

@@ -0,0 +1,16 @@
- type: job
id: CP14GodMerkas
name: cp14-job-name-god-merkas
description: cp14-job-desc-god-merkas
setPreference: false
playTimeTracker: CP14GodMerkas
requirements:
- !type:OverallPlaytimeRequirement
time: 36000 # 10 hrs
canBeAntag: false
icon: CP14JobIconGodMerkas
jobEntity: CP14MobGodMerkas
joinNotifyCrew: false
requireAdminNotify: true
jobPreviewEntity: CP14MobGodMerkas
applyTraits: false

View File

@@ -1,3 +1,14 @@
- type: department
id: CP14Demigods
name: department-CP14Demigods
description: department-CP14Demigods-desc
weight: 666 # >:)
color: "#ffffff"
editorHidden: true #temp
roles:
- CP14GodLumera
- CP14GodMerkas
- type: department
id: CP14Command
name: department-CP14Command
@@ -5,6 +16,7 @@
primary: false
weight: 10
color: "#3ec8fa"
editorHidden: true #temp
roles:
- CP14GuardCommander
- CP14Guildmaster
@@ -15,6 +27,7 @@
description: department-CP14Guard-desc
weight: 9
color: "#576384"
editorHidden: true #temp
roles:
- CP14GuardCommander
- CP14Guard
@@ -40,5 +53,4 @@
color: "#588151"
roles:
- CP14Guildmaster
- CP14Adventurer
- CP14Adventurer

View File

@@ -37,3 +37,9 @@
- type: playTimeTracker
id: CP14JobMerchant
# Gods
- type: playTimeTracker
id: CP14GodMerkas
- type: playTimeTracker
id: CP14GodLumera

View File

@@ -19,3 +19,8 @@
FalloffStrength: 0.75
FalloffPow: 3.0
HDR: 1.5
- type: shader
id: CP14ReligionVision
kind: source
path: "/Textures/_CP14/Shaders/religion.swsl"

View File

@@ -3,72 +3,48 @@
name: cp14-skill-tree-pyrokinetic-name
desc: cp14-skill-tree-pyrokinetic-desc
color: "#d6933c"
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: pyro
- type: cp14SkillTree
id: Hydrosophistry
name: cp14-skill-tree-hydrosophistry-name
desc: cp14-skill-tree-hydrosophistry-desc
color: "#1554a1"
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: water
- type: cp14SkillTree
id: Illusion
name: cp14-skill-tree-illusion-name
desc: cp14-skill-tree-illusion-desc
color: "#f55faf"
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: light
- type: cp14SkillTree
id: Metamagic
name: cp14-skill-tree-metamagic-name
desc: cp14-skill-tree-metamagic-desc
color: "#56e5f5"
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: meta
- type: cp14SkillTree
id: Healing
name: cp14-skill-tree-healing-name
desc: cp14-skill-tree-healing-desc
color: "#51cf72"
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: heal
- type: cp14SkillTree
id: Atlethic
name: cp14-skill-tree-atlethic-name
desc: cp14-skill-tree-atlethic-desc
color: "#b32e37"
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: atlethic
#- type: cp14SkillTree
# id: Dimension
# name: cp14-skill-tree-dimension-name
# desc: cp14-skill-tree-dimension-desc
# color: "#ac66be"
# icon:
# sprite: _CP14/Actions/skill_tree.rsi
# state: dimension
- type: cp14SkillTree
id: MartialArts
name: cp14-skill-tree-martial-name
desc: cp14-skill-tree-martial-desc
color: "#f54242"
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: martial
#

View File

@@ -0,0 +1,75 @@
# T1
- type: cp14Skill
id: LumeraT1
skillUiPosition: 1, 0
tree: GodLumera
name: cp14-skill-lumera-t1-name
learnCost: 0.5
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: t1
- type: cp14Skill
id: LumeraTouch
skillUiPosition: 0, 3
tree: GodLumera
learnCost: 0.0
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: touch
effects:
- !type:AddAction
action: CP14ActionSpellGodLumeraTouch
restrictions:
- !type:NeedPrerequisite
prerequisite: LumeraT1
- type: cp14Skill
id: LumeraRenounce
skillUiPosition: 2, 3
tree: GodLumera
learnCost: 0.0
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: renounce
effects:
- !type:AddAction
action: CP14ActionSpellGodLumeraRenounce
restrictions:
- !type:NeedPrerequisite
prerequisite: LumeraT1
# T2
- type: cp14Skill
id: LumeraT2
skillUiPosition: 7, 0
tree: GodLumera
name: cp14-skill-lumera-t2-name
learnCost: 0.5
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: t2
restrictions:
- !type:NeedPrerequisite
prerequisite: LumeraT1
- !type:GodFollowerPercentage
percentage: 0.3
# T3
- type: cp14Skill
id: LumeraT3
skillUiPosition: 13, 0
tree: GodLumera
name: cp14-skill-lumera-t3-name
learnCost: 0.5
icon:
sprite: _CP14/Actions/DemigodSpells/lumera.rsi
state: t3
restrictions:
- !type:NeedPrerequisite
prerequisite: LumeraT2
- !type:GodFollowerPercentage
percentage: 0.6

View File

@@ -0,0 +1,12 @@
- type: cp14Skill
id: NatureGodT1
skillUiPosition: 1, 0
tree: GodMerkas
name: cp14-skill-life-t1-name
learnCost: 0.5
icon:
sprite: _CP14/Actions/skill_tree.rsi
state: heal
effects:
- !type:AddAction
action: CP14ActionSpellBloodPurification

View File

@@ -0,0 +1,12 @@
- type: cp14SkillTree
id: GodMerkas
name: cp14-job-name-god-merkas
desc: cp14-job-desc-god-merkas
color: "#51cf72"
- type: cp14SkillTree
id: GodLumera
name: cp14-job-name-god-lumera
desc: cp14-job-desc-god-lumera
color: "#5632a8"
parallax: Default

View File

@@ -0,0 +1,15 @@
- type: jobIcon
parent: CP14JobIcon
id: CP14JobIconGodMerkas
icon:
sprite: /Textures/_CP14/Interface/Misc/job_god_icons.rsi
state: Merkas
jobName: cp14-job-name-god-merkas
- type: jobIcon
parent: CP14JobIcon
id: CP14JobIconGodLumera
icon:
sprite: /Textures/_CP14/Interface/Misc/job_god_icons.rsi
state: Lumera
jobName: cp14-job-name-god-lumera

View File

@@ -0,0 +1,7 @@
- type: radioChannel
id: CP14Gods
name: cp14-chat-radio-gods
keycode: 'g'
frequency: 666
color: "#fad634"
longRange: true

View File

@@ -0,0 +1,29 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "All right reserved",
"copyright": "Created by TheShuEd",
"states": [
{
"name": "touch"
},
{
"name": "warp"
},
{
"name": "renounce"
},
{
"name": "t1"
},
{
"name": "t2"
},
{
"name": "t3"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

View File

@@ -81,6 +81,21 @@
0.2
]
]
},
{
"name": "stars",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,19 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "offer",
"directions": 1
},
{
"name": "unoffer",
"directions": 1
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

View File

@@ -0,0 +1,17 @@
{
"version": 1,
"size": {
"x": 13,
"y": 13
},
"license": "CC-BY-SA-4.0",
"copyright": "Created by TheShuEd (Github)",
"states": [
{
"name": "Merkas"
},
{
"name": "Lumera"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,19 @@
{
"version": 1,
"license": "All right reserved",
"copyright": "Created by Jaraten (discord/Github)",
"size": {
"x": 48,
"y": 48
},
"states": [
{
"name": "lumera",
"directions": 4
},
{
"name": "merkas",
"directions": 4
}
]
}

Some files were not shown because too many files have changed in this diff Show More