Skip to content

Commit

Permalink
Rebase Blood Drinker System, Arachne, Oneirophage (#438)
Browse files Browse the repository at this point in the history
# Description
This is a simple rebase of the Blood Drinker System, and its related
features that have been commented out and/or omitted due to its lack of
rebase. I am NOT substantially updating any of this code at this time,
outside of the barest minimum updates needed to make it run in the first
place. The reason I am doing this is that I require the Blood Drinker
system functional as a prerequisite for future features, and I will
update or refactor it when needed.

Arachne are still pending a Full Rework, but that is beyond the scope of
this PR.

# TODO

- [x] Make the code functional
- [x] Port Arachne
- [x] Uncomment Oneirophages
- [x] Re-add Oneirophage midround event

# Changelog

:cl:
- add: Arachne have been reimplemented!
- add: Oneirophages are back!

---------

Signed-off-by: VMSolidus <[email protected]>
  • Loading branch information
VMSolidus authored Aug 6, 2024
1 parent e352057 commit 981b7dd
Show file tree
Hide file tree
Showing 61 changed files with 1,704 additions and 159 deletions.
231 changes: 231 additions & 0 deletions Content.Server/Arachne/ArachneSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
using Content.Shared.Arachne;
using Content.Shared.Actions;
using Content.Shared.IdentityManagement;
using Content.Shared.Verbs;
using Content.Shared.Buckle.Components;
using Content.Shared.DoAfter;
using Content.Shared.Stunnable;
using Content.Shared.Eye.Blinding.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Damage;
using Content.Shared.Inventory;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Humanoid;
using Content.Shared.Nutrition.EntitySystems;
using Content.Server.Buckle.Systems;
using Content.Server.Popups;
using Content.Server.DoAfter;
using Content.Server.Body.Components;
using Content.Server.Vampiric;
using Content.Server.Speech.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Utility;
using Robust.Server.Console;

namespace Content.Server.Arachne
{
public sealed class ArachneSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly BuckleSystem _buckleSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly BlindableSystem _blindableSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;

[Dependency] private readonly IServerConsoleHost _host = default!;
[Dependency] private readonly BloodSuckerSystem _bloodSuckerSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;

private const string BodySlot = "body_slot";

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ArachneComponent, GetVerbsEvent<InnateVerb>>(AddCocoonVerb);

SubscribeLocalEvent<CocoonComponent, EntInsertedIntoContainerMessage>(OnCocEntInserted);
SubscribeLocalEvent<CocoonComponent, EntRemovedFromContainerMessage>(OnCocEntRemoved);
SubscribeLocalEvent<CocoonComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<CocoonComponent, GetVerbsEvent<AlternativeVerb>>(AddSuccVerb);
SubscribeLocalEvent<ArachneComponent, ArachneCocoonDoAfterEvent>(OnCocoonDoAfter);
}

private void AddCocoonVerb(EntityUid uid, ArachneComponent component, GetVerbsEvent<InnateVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;

if (args.Target == uid)
return;

if (!TryComp<BloodstreamComponent>(args.Target, out var bloodstream))
return;

if (bloodstream.BloodReagent != component.WebBloodReagent)
return;

InnateVerb verb = new()
{
Act = () =>
{
StartCocooning(uid, component, args.Target);
},
Text = Loc.GetString("cocoon"),
Priority = 2
};
args.Verbs.Add(verb);
}

private void OnCocEntInserted(EntityUid uid, CocoonComponent component, EntInsertedIntoContainerMessage args)
{
_blindableSystem.UpdateIsBlind(args.Entity);
EnsureComp<StunnedComponent>(args.Entity);

if (TryComp<ReplacementAccentComponent>(args.Entity, out var currentAccent))
{
component.WasReplacementAccent = true;
component.OldAccent = currentAccent.Accent;
currentAccent.Accent = "mumble";
} else
{
component.WasReplacementAccent = false;
var replacement = EnsureComp<ReplacementAccentComponent>(args.Entity);
replacement.Accent = "mumble";
}
}

private void OnCocEntRemoved(EntityUid uid, CocoonComponent component, EntRemovedFromContainerMessage args)
{
if (component.WasReplacementAccent && TryComp<ReplacementAccentComponent>(args.Entity, out var replacement))
{
replacement.Accent = component.OldAccent;
} else
{
RemComp<ReplacementAccentComponent>(args.Entity);
}

RemComp<StunnedComponent>(args.Entity);
_blindableSystem.UpdateIsBlind(args.Entity);
}

private void OnDamageChanged(EntityUid uid, CocoonComponent component, DamageChangedEvent args)
{
if (!args.DamageIncreased)
return;

if (args.DamageDelta == null)
return;

var body = _itemSlots.GetItemOrNull(uid, BodySlot);

if (body == null)
return;

var damage = args.DamageDelta * component.DamagePassthrough;
_damageableSystem.TryChangeDamage(body, damage);
}

private void AddSuccVerb(EntityUid uid, CocoonComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;

if (!TryComp<BloodSuckerComponent>(args.User, out var sucker))
return;

if (!sucker.WebRequired)
return;

var victim = _itemSlots.GetItemOrNull(uid, BodySlot);

if (victim == null)
return;

if (!TryComp<BloodstreamComponent>(victim, out var stream))
return;

AlternativeVerb verb = new()
{
Act = () =>
{
_bloodSuckerSystem.StartSuccDoAfter(args.User, victim.Value, sucker, stream, false); // start doafter
},
Text = Loc.GetString("action-name-suck-blood"),
Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")),
Priority = 2
};
args.Verbs.Add(verb);
}

private void OnEntRemoved(EntityUid uid, WebComponent web, EntRemovedFromContainerMessage args)
{
if (!TryComp<StrapComponent>(uid, out var strap))
return;

if (HasComp<ArachneComponent>(args.Entity))
_buckleSystem.StrapSetEnabled(uid, false, strap);
}

private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid target)
{
_popupSystem.PopupEntity(Loc.GetString("cocoon-start-third-person", ("target", Identity.Entity(target, EntityManager)), ("spider", Identity.Entity(uid, EntityManager))), uid,
Shared.Popups.PopupType.MediumCaution);

_popupSystem.PopupEntity(Loc.GetString("cocoon-start-second-person", ("target", Identity.Entity(target, EntityManager))), uid, uid, Shared.Popups.PopupType.Medium);

var delay = component.CocoonDelay;

if (HasComp<KnockedDownComponent>(target))
delay *= component.CocoonKnockdownMultiplier;

// Is it good practice to use empty data just to disambiguate doafters
// Who knows, there's no docs!
var ev = new ArachneCocoonDoAfterEvent();

var args = new DoAfterArgs(EntityManager, uid, delay, ev, uid, target: target)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
};

_doAfter.TryStartDoAfter(args);
}

private void OnCocoonDoAfter(EntityUid uid, ArachneComponent component, ArachneCocoonDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;

var spawnProto = HasComp<HumanoidAppearanceComponent>(args.Args.Target) ? "CocoonedHumanoid" : "CocoonSmall";
Transform(args.Args.Target.Value).AttachToGridOrMap();
var cocoon = Spawn(spawnProto, Transform(args.Args.Target.Value).Coordinates);

if (!TryComp<ItemSlotsComponent>(cocoon, out var slots))
return;

// todo: our species should use scale visuals probably...
// TODO: We need a client-accessible notion of scale influence here.
/* if (spawnProto == "CocoonedHumanoid" && TryComp<SpriteComponent>(args.Args.Target.Value, out var sprite)) */
/* { */
/* // why the fuck is this only available as a console command. */
/* _host.ExecuteCommand(null, "scale " + cocoon + " " + sprite.Scale.Y); */
if (TryComp<PhysicsComponent>(args.Args.Target.Value, out var physics))
{
var scale = Math.Clamp(1 / (35 / physics.FixturesMass), 0.35, 2.5);
_host.ExecuteCommand(null, "scale " + cocoon + " " + scale);
}
_itemSlots.SetLock(cocoon, BodySlot, false, slots);
_itemSlots.TryInsert(cocoon, BodySlot, args.Args.Target.Value, args.Args.User);
_itemSlots.SetLock(cocoon, BodySlot, true, slots);

var impact = (spawnProto == "CocoonedHumanoid") ? LogImpact.High : LogImpact.Medium;

_adminLogger.Add(LogType.Action, impact, $"{ToPrettyString(args.Args.User):player} cocooned {ToPrettyString(args.Args.Target.Value):target}");
args.Handled = true;
}
}
}
13 changes: 13 additions & 0 deletions Content.Server/Arachne/CocoonComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Content.Server.Arachne
{
[RegisterComponent]
public sealed partial class CocoonComponent : Component
{
public bool WasReplacementAccent = false;

public string OldAccent = "";

[DataField("damagePassthrough")]
public float DamagePassthrough = 0.5f;
}
}
9 changes: 9 additions & 0 deletions Content.Server/Vampire/BloodSuckedComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Content.Server.Vampiric
{
/// <summary>
/// For entities who have been succed.
/// </summary>
[RegisterComponent]
public sealed partial class BloodSuckedComponent : Component
{}
}
44 changes: 44 additions & 0 deletions Content.Server/Vampire/BloodSuckerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace Content.Server.Vampiric
{
[RegisterComponent]
public sealed partial class BloodSuckerComponent : Component
{
/// <summary>
/// How much to succ each time we succ.
/// </summary>
[DataField("unitsToSucc")]
public float UnitsToSucc = 20f;

/// <summary>
/// The time (in seconds) that it takes to succ an entity.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan Delay = TimeSpan.FromSeconds(4);

// ***INJECT WHEN SUCC***

/// <summary>
/// Whether to inject chems into a chemstream when we suck something.
/// </summary>
[DataField("injectWhenSucc")]
public bool InjectWhenSucc = false;

/// <summary>
/// How many units of our injected chem to inject.
/// </summary>
[DataField("unitsToInject")]
public float UnitsToInject = 5;

/// <summary>
/// Which reagent to inject.
/// </summary>
[DataField("injectReagent")]
public string InjectReagent = "";

/// <summary>
/// Whether we need to web the thing up first...
/// </summary>
[DataField("webRequired")]
public bool WebRequired = false;
}
}
Loading

0 comments on commit 981b7dd

Please sign in to comment.