Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
09c0961
add The Manawa Rite
IngvarJackal Apr 29, 2026
0369b44
slightly nicer texture
IngvarJackal Apr 29, 2026
7981220
more removals of the random pixels
IngvarJackal Apr 29, 2026
dd63e39
rework the robe side view a bit
IngvarJackal Apr 30, 2026
164bfac
Merge branch 'master' into add-manawa
IngvarJackal May 5, 2026
d4d3cbc
add manawa rite fab and hoodie
IngvarJackal May 7, 2026
dbd58ff
minor localoisations
IngvarJackal May 7, 2026
47f2ec0
some assets
IngvarJackal May 7, 2026
b502c80
fixes
IngvarJackal May 7, 2026
261c028
cleanup and niceties
IngvarJackal May 7, 2026
5193276
Merge branch 'master' into add-manawa
IngvarJackal May 7, 2026
60b684b
fixes and copyrights
IngvarJackal May 7, 2026
dd77219
trees and lights rework
IngvarJackal May 7, 2026
d89c4cd
make mana grass more blue
IngvarJackal May 7, 2026
8e0dcd5
fix hoodie storage
IngvarJackal May 7, 2026
d578498
fix a typo
IngvarJackal May 7, 2026
aa6b8b9
add transmog system
IngvarJackal May 8, 2026
591aaf0
move all files into <base>/_HL/Factions/ManawaRite/<normal structure>
IngvarJackal May 8, 2026
a939444
fix random letters
IngvarJackal May 8, 2026
ac3353e
add some colored sprites
IngvarJackal May 8, 2026
ab29efc
add more trees
IngvarJackal May 8, 2026
48afbde
fix tree shift
IngvarJackal May 9, 2026
9635b10
Merge branch 'master' into add-manawa
IngvarJackal May 10, 2026
c35f72a
more light
IngvarJackal May 10, 2026
e03779b
Merge branch 'master' into add-manawa
IngvarJackal May 12, 2026
bd79d8d
add more decal tiles
IngvarJackal May 18, 2026
51ba3fd
Merge branch 'master' into add-manawa
IngvarJackal May 18, 2026
1c2dec0
fix RSIs
IngvarJackal May 18, 2026
d4e13d2
fix astro decals
IngvarJackal May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using Content.Shared._HL.Factions.ManawaRite.Clothing.Rings;
using Content.Shared.Humanoid;
using Robust.Client.GameObjects;
using Robust.Shared.Utility;

namespace Content.Client._HL.Factions.ManawaRite.Clothing.Rings;

public sealed class RingGlowEffectSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;

private const string LayerKey = "ring_transmogrification_layer";
private const string LayerKey2 = "ring_transmogrification_layer_2";

private static readonly ResPath AnomalyRsi = new("Structures/Specific/Anomalies/inner_anom_layer.rsi");
private static readonly ResPath HaloRsi = new("Clothing/Head/Hats/holyhatmelon.rsi");
private static readonly ResPath RunicBeltRsi = new("_NF/Clothing/Belt/cult_force_field.rsi");
private static readonly ResPath WingsRsi = new("_RMC14/Mobs/Customization/reptilian.rsi");
private static readonly ResPath FireflyRsi = new("_Impstation/Mobs/Customization/animatedmarkings.rsi");
private static readonly ResPath WatchingEyesRsi = new("_HL/Factions/ManawaRite/Mobs/Customization/watching_eyes.rsi");

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RingGlowEffectComponent, AfterAutoHandleStateEvent>(OnStateHandled);
SubscribeLocalEvent<RingGlowEffectComponent, ComponentShutdown>(OnShutdown);
}

private void OnStateHandled(Entity<RingGlowEffectComponent> ent, ref AfterAutoHandleStateEvent args)
{
ApplyOverlay(ent);
}

private void OnShutdown(Entity<RingGlowEffectComponent> ent, ref ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;

HideLayer(ent.Owner, sprite, LayerKey);
HideLayer(ent.Owner, sprite, LayerKey2);
}

private void ApplyOverlay(Entity<RingGlowEffectComponent> ent)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;

var effect = ent.Comp.Effect;

// Always hide second layer first; re-show only for multi-layer effects.
HideLayer(ent.Owner, sprite, LayerKey2);

var spec = GetSpriteOverlay(effect);
if (spec == null)
{
HideLayer(ent.Owner, sprite, LayerKey);
return;
}

SetLayer(ent.Owner, sprite, LayerKey, spec,
tint: GetLayerTint(ent.Owner, effect, layer: 0),
unshaded: IsUnshaded(effect));

// Wings need a second layer.
if (effect == TransmogrificationEffect.DraconicWings)
{
SetLayer(ent.Owner, sprite, LayerKey2,
new SpriteSpecifier.Rsi(WingsRsi, "body_dragonwings_membrane"),
tint: GetLayerTint(ent.Owner, effect, layer: 1),
unshaded: false);
}
}

private void SetLayer(EntityUid uid, SpriteComponent sprite, string key, SpriteSpecifier spec, Color? tint, bool unshaded)
{
var idx = _sprite.LayerMapReserve((uid, sprite), key);
_sprite.LayerSetSprite((uid, sprite), idx, spec);
_sprite.LayerSetVisible((uid, sprite), idx, true);
if (tint.HasValue)
_sprite.LayerSetColor((uid, sprite), idx, tint.Value);
if (unshaded)
sprite.LayerSetShader(idx, "unshaded");
}

private void HideLayer(EntityUid uid, SpriteComponent sprite, string key)
{
if (_sprite.LayerMapTryGet((uid, sprite), key, out var idx, false))
_sprite.LayerSetVisible((uid, sprite), idx, false);
}

private Color? GetLayerTint(EntityUid uid, TransmogrificationEffect effect, int layer)
{
switch (effect)
{
case TransmogrificationEffect.DraconicWings:
{
var skin = TryComp<HumanoidAppearanceComponent>(uid, out var humanoid)
? humanoid.SkinColor
: Color.White;
if (layer == 0)
return skin;
// Secondary layer: skin tone with -10 saturation, +20 value.
var hsv = Color.ToHsv(skin);
hsv.Y = Math.Clamp(hsv.Y - 0.10f, 0f, 1f);
hsv.Z = Math.Clamp(hsv.Z + 0.20f, 0f, 1f);
return Color.FromHsv(hsv);
}
case TransmogrificationEffect.CyanFireflies:
return Color.FromHex("#00FFFF");
default:
return null;
}
}

private static SpriteSpecifier? GetSpriteOverlay(TransmogrificationEffect effect) => effect switch
{
TransmogrificationEffect.AnomalyFire => new SpriteSpecifier.Rsi(AnomalyRsi, "fire"),
TransmogrificationEffect.AnomalyShadow => new SpriteSpecifier.Rsi(AnomalyRsi, "shadow"),
TransmogrificationEffect.AnomalyFlora => new SpriteSpecifier.Rsi(AnomalyRsi, "flora"),
TransmogrificationEffect.AnomalyFrost => new SpriteSpecifier.Rsi(AnomalyRsi, "frost"),
TransmogrificationEffect.AnomalyBluespace => new SpriteSpecifier.Rsi(AnomalyRsi, "bluespace"),
TransmogrificationEffect.AnomalyElectricity => new SpriteSpecifier.Rsi(AnomalyRsi, "shock"),
TransmogrificationEffect.AnomalyGravity => new SpriteSpecifier.Rsi(AnomalyRsi, "grav"),
TransmogrificationEffect.AnomalyRock => new SpriteSpecifier.Rsi(AnomalyRsi, "rock"),
TransmogrificationEffect.AnomalyFlesh => new SpriteSpecifier.Rsi(AnomalyRsi, "flesh"),
TransmogrificationEffect.AnomalyTech => new SpriteSpecifier.Rsi(AnomalyRsi, "tech"),
TransmogrificationEffect.Halo => new SpriteSpecifier.Rsi(HaloRsi, "equipped-HELMET"),
TransmogrificationEffect.RunicBelt => new SpriteSpecifier.Rsi(RunicBeltRsi, "equipped-BELT"),
TransmogrificationEffect.DraconicWings => new SpriteSpecifier.Rsi(WingsRsi, "body_dragonwings"),
TransmogrificationEffect.CyanFireflies => new SpriteSpecifier.Rsi(FireflyRsi, "dionafirefly"),
TransmogrificationEffect.WatchingEyes => new SpriteSpecifier.Rsi(WatchingEyesRsi, "watching-eyes"),
_ => null,
};

private static bool IsUnshaded(TransmogrificationEffect effect) => effect switch
{
TransmogrificationEffect.AnomalyFire => true,
TransmogrificationEffect.AnomalyShadow => true,
TransmogrificationEffect.AnomalyFlora => true,
TransmogrificationEffect.AnomalyFrost => true,
TransmogrificationEffect.AnomalyBluespace => true,
TransmogrificationEffect.AnomalyElectricity => true,
TransmogrificationEffect.AnomalyGravity => true,
TransmogrificationEffect.AnomalyRock => true,
TransmogrificationEffect.AnomalyFlesh => true,
TransmogrificationEffect.AnomalyTech => true,
TransmogrificationEffect.Halo => true,
TransmogrificationEffect.RunicBelt => true,
TransmogrificationEffect.CyanFireflies => true,
TransmogrificationEffect.WatchingEyes => true,
_ => false,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Content.Shared.Actions.Events;
using Content.Shared.Hands.EntitySystems;

namespace Content.Server._HL.Factions.ManawaRite.Actions;

public sealed class ManawaRiteOnboardingSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ManawaRiteOnboardingActionEvent>(OnManawaRiteOnboarding);
}

private void OnManawaRiteOnboarding(ManawaRiteOnboardingActionEvent args)
{
if (args.Handled)
return;

var coords = Transform(args.Performer).Coordinates;
var package = Spawn("ManawaRiteOnboardingPackage", coords);
_hands.TryPickupAnyHand(args.Performer, package);
args.Handled = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using Content.Server.Jittering;
using Content.Shared._HL.Factions.ManawaRite.Clothing.Rings;
using Content.Shared.Clothing;
using Content.Shared.Jittering;
using Content.Shared.Verbs;
using Robust.Shared.Utility;

namespace Content.Server._HL.Factions.ManawaRite.Clothing.Rings;

public sealed class RingOfTransmogrificationSystem : EntitySystem
{
private static readonly VerbCategory AppearanceCategory =
new("Appearance", "/Textures/Interface/VerbIcons/group.svg.192dpi.png");
[Dependency] private readonly JitteringSystem _jitter = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RingOfTransmogrificationComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<RingOfTransmogrificationComponent, ClothingGotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<RingOfTransmogrificationComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
}

private void OnEquipped(EntityUid uid, RingOfTransmogrificationComponent comp, ClothingGotEquippedEvent args)
{
comp.Wearer = args.Wearer;
ApplyEffect(uid, comp, comp.SelectedEffect);
}

private void OnUnequipped(EntityUid uid, RingOfTransmogrificationComponent comp, ClothingGotUnequippedEvent args)
{
if (comp.Wearer is { } wearer)
RemoveEffect(wearer, comp);
comp.Wearer = null;
}

private void OnGetVerbs(EntityUid uid, RingOfTransmogrificationComponent comp, GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || args.User != comp.Wearer)
return;

foreach (var effect in Enum.GetValues<TransmogrificationEffect>())
{
var captured = effect;
var isCurrent = comp.SelectedEffect == effect;
args.Verbs.Add(new Verb
{
Text = EffectLabel(effect),
Category = AppearanceCategory,
Act = () => SetEffect(uid, comp, captured),
Disabled = isCurrent,
Priority = isCurrent ? 1 : 0,
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png")),
});
}
}

private void SetEffect(EntityUid uid, RingOfTransmogrificationComponent comp, TransmogrificationEffect effect)
{
if (comp.Wearer is not { } wearer)
return;

RemoveEffect(wearer, comp);
comp.SelectedEffect = effect;
ApplyEffect(uid, comp, effect);
}

private void ApplyEffect(EntityUid ring, RingOfTransmogrificationComponent comp, TransmogrificationEffect effect)
{
if (comp.Wearer is not { } wearer)
return;

var glowComp = EnsureComp<RingGlowEffectComponent>(wearer);
glowComp.Effect = effect;
Dirty(wearer, glowComp);

if (effect == TransmogrificationEffect.Jitter)
{
_jitter.AddJitter(wearer, amplitude: 10f, frequency: 4f);
comp.AddedJitter = true;
}
}

private void RemoveEffect(EntityUid wearer, RingOfTransmogrificationComponent comp)
{
if (HasComp<RingGlowEffectComponent>(wearer))
RemCompDeferred<RingGlowEffectComponent>(wearer);

if (comp.AddedJitter)
{
RemComp<JitteringComponent>(wearer);
comp.AddedJitter = false;
}
}

private static string EffectLabel(TransmogrificationEffect effect) => effect switch
{
TransmogrificationEffect.None => "No effect",
TransmogrificationEffect.AnomalyFire => "Anomaly: fire",
TransmogrificationEffect.AnomalyShadow => "Anomaly: shadow",
TransmogrificationEffect.AnomalyFlora => "Anomaly: flora",
TransmogrificationEffect.AnomalyFrost => "Anomaly: frost",
TransmogrificationEffect.AnomalyBluespace => "Anomaly: bluespace",
TransmogrificationEffect.AnomalyElectricity => "Anomaly: electricity",
TransmogrificationEffect.AnomalyGravity => "Anomaly: gravity",
TransmogrificationEffect.AnomalyRock => "Anomaly: rock",
TransmogrificationEffect.AnomalyFlesh => "Anomaly: flesh",
TransmogrificationEffect.AnomalyTech => "Anomaly: tech",
TransmogrificationEffect.Jitter => "Jitter",
TransmogrificationEffect.Halo => "Halo",
TransmogrificationEffect.RunicBelt => "Runic belt",
TransmogrificationEffect.DraconicWings => "Draconic wings",
TransmogrificationEffect.CyanFireflies => "Cyan fireflies",
TransmogrificationEffect.WatchingEyes => "Watching eyes",
_ => effect.ToString(),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Numerics;

namespace Content.Shared.Humanoid.Markings;

/// <summary>
/// Colors a marking layer using the character's skin color, shifted by the given HSV offsets.
/// Saturation and value are on a 0–100 scale; hue is in degrees.
/// </summary>
public sealed partial class SkinHsvAdjustColoring : LayerColoringType
{
[DataField]
public float HueAdjust { get; private set; } = 0f;

[DataField]
public float SaturationAdjust { get; private set; } = 0f;

[DataField]
public float ValueAdjust { get; private set; } = 0f;

public override Color? GetCleanColor(Color? skin, Color? eyes, MarkingSet markingSet)
{
if (skin is not { } s)
return null;

var hsv = Color.ToHsv(s);
hsv.X = (hsv.X + HueAdjust / 360f) % 1f;
if (hsv.X < 0f) hsv.X += 1f;
hsv.Y = Math.Clamp(hsv.Y + SaturationAdjust / 100f, 0f, 1f);
hsv.Z = Math.Clamp(hsv.Z + ValueAdjust / 100f, 0f, 1f);
return Color.FromHsv(hsv);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Robust.Shared.GameStates;

namespace Content.Shared._HL.Factions.ManawaRite.Clothing.Rings;

public enum TransmogrificationEffect : byte
{
None,
AnomalyFire,
AnomalyShadow,
AnomalyFlora,
AnomalyFrost,
AnomalyBluespace,
AnomalyElectricity,
AnomalyGravity,
AnomalyRock,
AnomalyFlesh,
AnomalyTech,
Jitter,
Halo,
RunicBelt,
DraconicWings,
CyanFireflies,
WatchingEyes,
}

[RegisterComponent]
public sealed partial class RingOfTransmogrificationComponent : Component
{
[DataField]
public TransmogrificationEffect SelectedEffect = TransmogrificationEffect.None;

public EntityUid? Wearer;
public bool AddedJitter;
}

/// <summary>
/// Marker placed on the wearer so the client can render the cosmetic overlay.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class RingGlowEffectComponent : Component
{
[DataField, AutoNetworkedField]
public TransmogrificationEffect Effect = TransmogrificationEffect.None;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ namespace Content.Shared.Actions.Events;
public sealed partial class DuskEnclaveOnboardingActionEvent : InstantActionEvent
{
}

public sealed partial class ManawaRiteOnboardingActionEvent : InstantActionEvent
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
construction-category-decorations = Decorations
construction-step-insert-bluespace = bluespace crystal
1 change: 1 addition & 0 deletions Resources/Locale/en-US/_HL/Factions/ManawaRite/headset.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chat-radio-manawa-rite = Manawa Rite
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
manawa-rite-onboarding-name = Manawa Rite Onboarding
manawa-rite-onboarding-desc = You can make Manawa Rite onboarding packages.

manawa-rite-onboarding-action-name = Produce Onboarding Package
manawa-rite-onboarding-action-desc = Produce one Manawa Rite onboarding package.
Loading
Loading