Skip to content

Commit

Permalink
The Blood Cult (#1001)
Browse files Browse the repository at this point in the history
<!--
This is a semi-strict format, you can add/remove sections as needed but
the order/format should be kept the same
Remove these comments before submitting
-->

# Description
Adds a new game mode - The Blood Cult. 
Hail to Nar'Sie!

---

# TODO

- [x] Gamemode
- [x] Roles
- [x] Runes
- [x] Constructs
- [x] Structures
  - [x] Forge
  - [x] Archives
  - [x] Altar
  - [x] Pylon
    - [x] Structure
    - [x] Placement System
  - [x] Airlocks
    - [x] Repulsor system
  - [x] Construction system
- [x] Items
  - [x] Eldritch Whetstone
  - [x] Construct Shell
  - [x] Mirror Shield
  - [x] True Nar'sian Hardened Armor
  - [x] Flagellant's Robe
  - [x] Eldritch Longsword
  - [x] Zealot's Blindfold
    - [ ] Night vision system
  - [x] Shuttle Curse
  - [x] Veil Shifter
  - [x] Void Torch
 - [x] Reagents
   - [x] Holy Water
- [ ] Actions
  - [x] Cult Magic
    - [x] Stun
    - [x] Teleport
    - [x] Electromagnetic Pulse
    - [x] Shadow Shackles
    - [x] Twisted Construction
    - [x] Summon Combat Equipment
    - [x] Summon Ritual Dagger
    - [x] Blood Rites
  - [ ] Cult Leader Magic
    - [ ] Final Reckoning
    - [ ] Mark Target
    - [ ] Eldritch Pulse
  - [ ] Construct's Magic 
    - [ ] Artificer 
      - [ ] Summon Cult Floor 
      - [ ] Summon Cult Wall
      - [ ] Greater Conjuration
      - [ ] Summon Soulstone
    - [ ] Wraith 
      - [ ] Phase Shift
    - [ ] Juggernaut
      - [ ] Shield
      - [ ] Gauntlet Echo
- [x] Cult leader selection system
- [ ] Rending/Apocalypse rune placement markers
EXTRA:
- [ ] Spirit Realm rune
- [x] Eldritch language
- [ ] Conceal magic
- [ ] Ru locale
---

<!--
This is default collapsed, readers click to expand it and see all your
media
The PR media section can get very large at times, so this is a good way
to keep it clean
The title is written using HTML tags
The title must be within the <summary> tags or you won't see it
-->

<details><summary><h1>Media</h1></summary>
<p>

![Example Media Embed](https://example.com/thisimageisntreal.png)

</p>
</details>

---

# Changelog

:cl:
- add: Added Blood Cult Gamemode.

---------

Signed-off-by: VMSolidus <[email protected]>
Signed-off-by: Remuchi <[email protected]>
Co-authored-by: VMSolidus <[email protected]>
  • Loading branch information
Remuchi and VMSolidus authored Nov 22, 2024
1 parent 09d5900 commit 1500208
Show file tree
Hide file tree
Showing 408 changed files with 10,442 additions and 303 deletions.
15 changes: 14 additions & 1 deletion Content.Client/Antag/AntagStatusIconSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
using Content.Shared.Revolutionary.Components;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.WhiteDream.BloodCult.BloodCultist;
using Content.Shared.WhiteDream.BloodCult.Components;
using Content.Shared.WhiteDream.BloodCult.Constructs;
using Content.Shared.Zombies;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
Expand All @@ -23,6 +26,10 @@ public override void Initialize()
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<InitialInfectedComponent, GetStatusIconsEvent>(GetIcon);

SubscribeLocalEvent<ConstructComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<BloodCultistComponent, GetStatusIconsEvent>(GetBloodCultIcon);
SubscribeLocalEvent<BloodCultLeaderComponent, GetStatusIconsEvent>(GetIcon);
}

/// <summary>
Expand All @@ -39,7 +46,6 @@ private void GetIcon<T>(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where
ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon));
}


/// <summary>
/// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal
/// with a special case where if someone is a head rev we only want to display the headrev icon.
Expand All @@ -50,6 +56,13 @@ private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatu
return;

GetIcon(uid, comp, ref ev);
}

private void GetBloodCultIcon(EntityUid uid, BloodCultistComponent comp, ref GetStatusIconsEvent ev)
{
if (HasComp<BloodCultLeaderComponent>(uid))
return;

GetIcon(uid, comp, ref ev);
}
}
111 changes: 111 additions & 0 deletions Content.Client/ListViewSelector/ListViewSelectorBUI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Content.Client.Lathe.UI;
using Content.Client.UserInterface.Controls;
using Content.Shared.ListViewSelector;
using JetBrains.Annotations;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;

// ReSharper disable InconsistentNaming

namespace Content.Client.ListViewSelector;

[UsedImplicitly]
public sealed class ListViewSelectorBUI(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;

private FancyWindow _window = new();
private BoxContainer? _itemsContainer;
private Dictionary<string, object> _metaData = new();

protected override void Open()
{
_window = FormWindow();
_window.OnClose += Close;
_window.OpenCentered();
}

protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not ListViewSelectorState listViewSelectorState)
return;

PopulateWindow(listViewSelectorState.Items);
_metaData = listViewSelectorState.MetaData;
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (disposing)
_window.Close();
}

private FancyWindow FormWindow()
{
var window = new FancyWindow
{
HorizontalExpand = true,
VerticalExpand = true,
MinWidth = 350,
MinHeight = 400,
Title = Loc.GetString("list-view-window-default-title")
};

var scrollContainer = new ScrollContainer
{
HorizontalExpand = true,
VerticalExpand = true
};

var itemsContainer = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical
};

scrollContainer.AddChild(itemsContainer);
window.AddChild(scrollContainer);

_itemsContainer = itemsContainer;

return window;
}

private void PopulateWindow(List<ListViewSelectorEntry> items)
{
if (_itemsContainer is null)
return;

_itemsContainer.Children.Clear();

foreach (var item in items)
{
var itemName = item.Name;
var itemDesc = item.Description;
if (_prototypeManager.TryIndex(item.Id, out var itemPrototype))
{
itemName = itemPrototype.Name;
itemDesc = itemPrototype.Description;
}

var button = new Button
{
Text = itemName,
};

if (!string.IsNullOrEmpty(itemDesc))
button.TooltipSupplier = _ => new RecipeTooltip(itemDesc);

button.OnButtonUp += _ =>
{
var msg = new ListViewItemSelectedMessage(item, items.IndexOf(item), _metaData);
SendMessage(msg);
Close();
};

_itemsContainer.AddChild(button);
}
}
}
5 changes: 4 additions & 1 deletion Content.Client/ShortConstruction/ShortConstructionSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Client.Construction;
// using Content.Client.WhiteDream.BloodCult.UI;
using Content.Shared.Construction.Prototypes;
using Content.Shared.RadialSelector;
using Content.Shared.ShortConstruction;
Expand Down Expand Up @@ -36,11 +37,13 @@ private void OnItemRecieved(Entity<ShortConstructionComponent> ent, ref RadialSe
return;
}

var hijack = new ConstructionPlacementHijack(_construction, prototype);

_placement.BeginPlacing(new PlacementInformation
{
IsTile = false,
PlacementOption = prototype.PlacementMode
},
new ConstructionPlacementHijack(_construction, prototype));
hijack);
}
}
73 changes: 73 additions & 0 deletions Content.Client/WhiteDream/BloodCult/BloodCultistSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Numerics;
using Content.Shared.Antag;
using Content.Shared.Ghost;
using Content.Shared.StatusIcon.Components;
using Content.Shared.WhiteDream.BloodCult;
using Content.Shared.WhiteDream.BloodCult.BloodCultist;
using Content.Shared.WhiteDream.BloodCult.Components;
using Content.Shared.WhiteDream.BloodCult.Constructs;
using Robust.Client.GameObjects;
using Robust.Shared.Random;
using Robust.Shared.Utility;

namespace Content.Client.WhiteDream.BloodCult;

public sealed class BloodCultistSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;

public override void Initialize()
{
SubscribeLocalEvent<PentagramComponent, ComponentStartup>(OnPentagramAdded);
SubscribeLocalEvent<PentagramComponent, ComponentShutdown>(OnPentagramRemoved);

SubscribeLocalEvent<ConstructComponent, CanDisplayStatusIconsEvent>(OnCanShowCultIcon);
SubscribeLocalEvent<BloodCultistComponent, CanDisplayStatusIconsEvent>(OnCanShowCultIcon);
SubscribeLocalEvent<BloodCultLeaderComponent, CanDisplayStatusIconsEvent>(OnCanShowCultIcon);
}

private void OnPentagramAdded(EntityUid uid, PentagramComponent component, ComponentStartup args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite) || sprite.LayerMapTryGet(PentagramKey.Key, out _))
return;

var adj = sprite.Bounds.Height / 2 + 1.0f / 32 * 10.0f;

var randomState = _random.Pick(component.States);

var layer = sprite.AddLayer(new SpriteSpecifier.Rsi(component.RsiPath, randomState));

sprite.LayerMapSet(PentagramKey.Key, layer);
sprite.LayerSetOffset(layer, new Vector2(0.0f, adj));
}

private void OnPentagramRemoved(EntityUid uid, PentagramComponent component, ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite) || !sprite.LayerMapTryGet(PentagramKey.Key, out var layer))
return;

sprite.RemoveLayer(layer);
}

/// <summary>
/// Determine whether a client should display the cult icon.
/// </summary>
private void OnCanShowCultIcon<T>(EntityUid uid, T comp, ref CanDisplayStatusIconsEvent args)
where T : IAntagStatusIconComponent
{
if (!CanDisplayIcon(args.User, comp.IconVisibleToGhost))
args.Cancelled = true;
}

/// <summary>
/// The criteria that determine whether a client should see Cult/Cult leader icons.
/// </summary>
private bool CanDisplayIcon(EntityUid? uid, bool visibleToGhost)
{
if (HasComp<BloodCultistComponent>(uid) || HasComp<BloodCultLeaderComponent>(uid) ||
HasComp<ConstructComponent>(uid))
return true;

return visibleToGhost && HasComp<GhostComponent>(uid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Content.Client.Light.Components;
using Content.Shared.WhiteDream.BloodCult;
using Content.Shared.WhiteDream.BloodCult.Items.VoidTorch;
using Robust.Client.GameObjects;

namespace Content.Client.WhiteDream.BloodCult.Items.VoidTorch;

public sealed class VoidTorchSystem : VisualizerSystem<VoidTorchComponent>
{
protected override void OnAppearanceChange(EntityUid uid,
VoidTorchComponent component,
ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!AppearanceSystem.TryGetData<bool>(uid, GenericCultVisuals.State, out var state)
|| !TryComp<LightBehaviourComponent>(uid, out var lightBehaviour))
return;

lightBehaviour.StopLightBehaviour();
lightBehaviour.StartLightBehaviour(state ? component.TurnOnLightBehaviour : component.TurnOffLightBehaviour);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.WhiteDream.BloodCult.UI;
using JetBrains.Annotations;
using Robust.Client.UserInterface.Controls;

// ReSharper disable InconsistentNaming

namespace Content.Client.WhiteDream.BloodCult.NameSelector;

[UsedImplicitly]
public sealed class NameSelectorBUI(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
private readonly FancyWindow _window = new();

protected override void Open()
{
base.Open();

FormWindow();
_window.OpenCentered();
_window.OnClose += Close;
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (disposing)
_window.Close();
}

private void FormWindow()
{
var container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical
};

var label = new Label
{
Text = Loc.GetString("name-selector-title")
};

var lineEdit = new LineEdit
{
HorizontalExpand = true
};

var button = new Button
{
Text = Loc.GetString("name-selector-accept-button")
};

button.OnButtonUp += _ =>
{
var msg = new NameSelectedMessage(lineEdit.Text);
SendMessage(msg);
Close();
};

container.AddChild(label);
container.AddChild(lineEdit);
container.AddChild(button);

_window.AddChild(container);
}
}
Loading

0 comments on commit 1500208

Please sign in to comment.