Глобальная оптимизация и фиксы культа плоти

This commit is contained in:
Vigers Ray 2025-01-03 11:51:48 +03:00
parent 486b666199
commit bc46f14574
50 changed files with 2439 additions and 2968 deletions

View file

@ -3,7 +3,7 @@ using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;
namespace Content.Client._Sunrise.FleshCult;
public sealed class FleshCultistSystem : SharedFleshMobSystem
public sealed class FleshCultistSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;

View file

@ -1,11 +0,0 @@
using Content.Server.Flesh;
namespace Content.Client._Sunrise.FleshCult;
public sealed class FleshHuggerSystem: SharedFleshHuggerSystem
{
public override void Initialize()
{
}
}

View file

@ -1,11 +0,0 @@
using Content.Shared._Sunrise.FleshCult;
namespace Content.Client._Sunrise.FleshCult;
public sealed class FleshMobSystem : SharedFleshCultistSystem
{
public override void Initialize()
{
base.Initialize();
}
}

View file

@ -35,6 +35,18 @@ public sealed partial class AdminVerbSystem
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultThiefRule = "Thief";
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultVampireRule = "Vampire";
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultChangelingRule = "Changeling";
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultFleshCultRule = "FleshCult";
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultAssaultOpsRule = "AssaultOps";
[ValidatePrototypeId<StartingGearPrototype>]
private const string PirateGearId = "PirateGear";
@ -161,7 +173,7 @@ public sealed partial class AdminVerbSystem
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Changeling/changeling_abilities.rsi"), "transform"),
Act = () =>
{
_antag.ForceMakeAntag<ChangelingRuleComponent>(targetPlayer, "Changeling");
_antag.ForceMakeAntag<ChangelingRuleComponent>(targetPlayer, DefaultChangelingRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-changeling"),
@ -175,7 +187,7 @@ public sealed partial class AdminVerbSystem
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_vampire.rsi"), "unholystrength"),
Act = () =>
{
_antag.ForceMakeAntag<VampireRuleComponent>(targetPlayer, "Vampire");
_antag.ForceMakeAntag<VampireRuleComponent>(targetPlayer, DefaultVampireRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-vampire"),
@ -189,13 +201,12 @@ public sealed partial class AdminVerbSystem
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster46_contraband"),
Act = () =>
{
_antag.ForceMakeAntag<AssaultOpsRuleComponent>(targetPlayer, "AssaultOps");
_antag.ForceMakeAntag<AssaultOpsRuleComponent>(targetPlayer, DefaultAssaultOpsRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-assault-operative"),
};
// На время пока не будут закончены все новые режимы.
//args.Verbs.Add(assaultOperative);
args.Verbs.Add(assaultOperative);
Verb fleshCultist = new()
{
@ -204,12 +215,11 @@ public sealed partial class AdminVerbSystem
Icon = new SpriteSpecifier.Texture(new ResPath("_Sunrise/FleshCult/Interface/Actions/fleshCultistFleshHeart.png")),
Act = () =>
{
_antag.ForceMakeAntag<FleshCultRuleComponent>(targetPlayer, "FleshCult");
_antag.ForceMakeAntag<FleshCultRuleComponent>(targetPlayer, DefaultFleshCultRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-flesh-cultist"),
};
// На время пока не будут закончены все новые режимы.
//args.Verbs.Add(fleshCultist);
args.Verbs.Add(fleshCultist);
}
}

View file

@ -7,7 +7,7 @@ using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Commands
{
[AdminCommand(AdminFlags.Host)] // На время пока не будут закончены все новые режимы.
[AdminCommand(AdminFlags.Round)]
public sealed class SetGamePresetCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entity = default!;

View file

@ -327,7 +327,7 @@ public sealed partial class GameTicker
#region Command Implementations
[AdminCommand(AdminFlags.Host)] // На время пока не будут закончены все новые режимы.
[AdminCommand(AdminFlags.Fun)]
private void AddGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
{
if (args.Length == 0)

View file

@ -0,0 +1,81 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server._Sunrise.FleshCult
{
[RegisterComponent]
public sealed partial class FleshAbilitiesComponent : Component
{
[DataField("startingActions", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> StartingActions = new();
[DataField]
public List<EntityUid> Actions = new();
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ActionFleshCultistDevourId = "FleshCultistDevour";
[ViewVariables(VVAccess.ReadWrite), DataField("bloodWhitelist")]
public List<string> BloodWhitelist = new()
{
"Blood",
"CopperBlood",
"InsectBlood",
"AmmoniaBlood",
"ZombieBlood"
};
[DataField("devourTime")] public float DevourTime = 10f;
[DataField("devourSound")]
public SoundSpecifier DevourSound = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/devour_flesh_cultist.ogg");
[DataField("adrenalinReagents")] public Solution AdrenalinReagents = new()
{
Contents = { new ReagentQuantity(new ReagentId("Ephedrine", null), 10) }
};
[DataField("healDevourReagents")] public Solution HealDevourReagents = new()
{
Contents =
{
new ReagentQuantity(new ReagentId("Carol", null), 20),
}
};
[DataField("healBloodAbsorbReagents")] public Solution HealBloodAbsorbReagents = new()
{
Contents =
{
new ReagentQuantity(new ReagentId("Carol", null), 1),
}
};
[DataField]
public SoundSpecifier BloodAbsorbSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg");
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string BulletAcidSpawnId = "BulletSplashAcid";
[DataField]
public SoundSpecifier SoundBulletAcid = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/flesh_cultist_mutation.ogg");
[DataField]
public SoundSpecifier SoundMutation = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/flesh_cultist_mutation.ogg");
[DataField("fleshHeartId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)),
ViewVariables(VVAccess.ReadWrite)]
public string FleshHeartId = "FleshHeart";
[ViewVariables(VVAccess.ReadWrite), DataField("soundThrowWorm")]
public SoundSpecifier? SoundThrowHugger = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/throw_worm.ogg");
[ViewVariables(VVAccess.ReadWrite),
DataField("huggerMobSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string HuggerMobSpawnId = "MobFleshHugger";
}
}

View file

@ -0,0 +1,8 @@
namespace Content.Server._Sunrise.FleshCult
{
[RegisterComponent]
public sealed partial class FleshBodyModComponent : Component
{
}
}

View file

@ -0,0 +1,569 @@
using System.Linq;
using System.Numerics;
using Content.Server.Body.Components;
using Content.Server.Construction.Components;
using Content.Server.Traits.Assorted;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Cuffs.Components;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Maps;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Robust.Shared.Audio;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultSystem
{
private void InitializeAbilities()
{
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistHandTransformEvent>(OnHandTransformEvent);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistBodyTransformEvent>(OnBodyModificationAction);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistAdrenalinActionEvent>(OnAdrenalinActionEvent);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistCreateFleshHeartActionEvent>(OnCreateFleshHeartActionEvent);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistThrowHuggerActionEvent>(OnThrowHugger);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistAcidSpitActionEvent>(OnAcidSpit);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistAbsorbBloodPoolActionEvent>(AbsormBloodPool);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistDevourActionEvent>(OnDevourAction);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistDevourDoAfterEvent>(OnDevourDoAfter);
SubscribeLocalEvent<FleshAbilitiesComponent, FleshCultistUnlockAbilityEvent>(OnUnlockAbility);
SubscribeLocalEvent<FleshAbilitiesComponent, ComponentStartup>(OnStartup);
}
private void OnUnlockAbility(EntityUid uid, FleshAbilitiesComponent component,
FleshCultistUnlockAbilityEvent args)
{
var action = _action.AddAction(uid, args.Prototype);
if (action != null)
component.Actions.Add(action.Value);
}
private void OnStartup(EntityUid uid, FleshAbilitiesComponent component, ComponentStartup args)
{
foreach (var componentStartingAction in component.StartingActions)
{
var action = _action.AddAction(uid, componentStartingAction);
if (action != null)
component.Actions.Add(action.Value);
}
}
private void OnDevourAction(EntityUid uid, FleshAbilitiesComponent component, FleshCultistDevourActionEvent args)
{
if (args.Handled)
return;
var target = args.Target;
if (!TryComp<MobStateComponent>(target, out var targetState))
return;
if (!TryComp<BloodstreamComponent>(target, out var bloodstream))
return;
var hasAppearance = false;
{
switch (targetState.CurrentState)
{
case MobState.Dead:
if (EntityManager.TryGetComponent(target, out HumanoidAppearanceComponent? humanoidAppearance))
{
if (!_speciesWhitelist.Contains(humanoidAppearance.Species))
{
_popup.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-not-have-flesh"),
uid, uid);
return;
}
if (TryComp<FixturesComponent>(target, out var fixturesComponent))
{
if (fixturesComponent.Fixtures["fix1"].Density <= 60)
{
_popup.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-invalid"),
uid, uid);
return;
}
}
hasAppearance = true;
}
else
{
if (!component.BloodWhitelist.Contains(bloodstream.BloodReagent))
{
_popup.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-not-have-flesh"),
uid, uid);
return;
}
if (bloodstream.BloodMaxVolume < 30)
{
_popup.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-invalid"),
uid, uid);
return;
}
}
var saturation = MatchSaturation(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
if (TryComp<FleshCultistComponent>(uid, out var fleshCultistComponent) &&
fleshCultistComponent.Hunger + saturation >= fleshCultistComponent.MaxHunger)
{
_popup.PopupEntity(
Loc.GetString("flesh-cultist-devout-not-hungry"),
uid, uid);
return;
}
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager ,uid, component.DevourTime,
new FleshCultistDevourDoAfterEvent(), uid, target: target, used: uid)
{
BreakOnMove = true,
});
args.Handled = true;
break;
case MobState.Invalid:
case MobState.Critical:
case MobState.Alive:
default:
_popup.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-alive"),
uid, uid);
break;
}
}
}
private void OnDevourDoAfter(EntityUid uid, FleshAbilitiesComponent component, FleshCultistDevourDoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
if (args.Args.Target == null)
return;
if (!TryComp<BloodstreamComponent>(args.Args.Target.Value, out var bloodstream))
return;
var hasAppearance = false;
var xform = Transform(args.Args.Target.Value);
var coordinates = xform.Coordinates;
_audioSystem.PlayPvs(component.DevourSound, coordinates, AudioParams.Default.WithVariation(0.025f).WithMaxDistance(5f));
_popup.PopupEntity(Loc.GetString("flesh-cultist-devour-target",
("Entity", uid), ("Target", args.Args.Target)), uid);
if (bloodstream.BloodSolution != null)
{
_bloodstreamSystem.SpillAllSolutions(args.Args.Target.Value, bloodstream);
}
if (TryComp<HumanoidAppearanceComponent>(args.Args.Target, out var HuAppComponent))
{
if (TryComp(args.Args.Target.Value, out ContainerManagerComponent? container))
{
foreach (var cont in container.GetAllContainers().ToArray())
{
foreach (var ent in cont.ContainedEntities.ToArray())
{
if (HasComp<BodyPartComponent>(ent))
{
continue;
}
_containerSystem.Remove(ent, cont, force: true);
Transform(ent).Coordinates = coordinates;
}
}
}
// SUNRISE-TODO: Убрать конечности хирургией а тело заменить на скелета
if (TryComp<BodyComponent>(args.Args.Target, out var bodyComponent))
{
var parts = _body.GetBodyChildren(args.Args.Target, bodyComponent).ToArray();
foreach (var part in parts)
{
if (part.Component.PartType == BodyPartType.Head)
continue;
if (part.Component.PartType == BodyPartType.Torso)
{
foreach (var organ in _body.GetPartOrgans(part.Id, part.Component))
{
//_body.RemoveOrgan(organ.Id);
QueueDel(organ.Id);
}
}
else
{
QueueDel(part.Id);
}
}
}
var skeletonSprites = _prototypeManager.Index<HumanoidSpeciesBaseSpritesPrototype>("MobSkeletonSprites");
foreach (var (key, id) in skeletonSprites.Sprites)
{
if (key != HumanoidVisualLayers.Head)
{
_sharedHuApp.SetBaseLayerId(args.Args.Target.Value, key, id, humanoid: HuAppComponent);
}
}
if (TryComp<FixturesComponent>(args.Args.Target, out var fixturesComponent))
{
_physics.SetDensity(args.Args.Target.Value, "fix1", fixturesComponent.Fixtures["fix1"], 50);
}
if (TryComp<AppearanceComponent>(args.Args.Target, out var appComponent))
{
_sharedAppearance.SetData(args.Args.Target.Value, DamageVisualizerKeys.Disabled, true, appComponent);
}
hasAppearance = true;
}
var saturation = MatchSaturation(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
var evolutionPoint = MatchEvolutionPoint(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
var healPoint = MatchHealPoint(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
RemComp<BloodstreamComponent>(args.Args.Target.Value);
EnsureComp<UnrevivableComponent>(args.Args.Target.Value);
if (!hasAppearance)
{
QueueDel(args.Args.Target.Value);
}
if (_solutionContainerSystem.TryGetInjectableSolution(uid, out var injectableSolution, out _))
{
var transferSolution = new Solution();
foreach (var solution in component.HealDevourReagents)
{
transferSolution.AddReagent(solution.Reagent, solution.Quantity * healPoint);
}
_solutionContainerSystem.TryAddSolution(injectableSolution.Value, transferSolution);
}
if (TryComp<FleshCultistComponent>(uid, out var fleshCultistComponent))
{
fleshCultistComponent.Hunger += saturation;
_store.TryAddCurrency(new Dictionary<string, FixedPoint2>
{ {fleshCultistComponent.StolenCurrencyPrototype, evolutionPoint} }, uid);
}
}
private void AbsormBloodPool(EntityUid uid,
FleshAbilitiesComponent component,
FleshCultistAbsorbBloodPoolActionEvent args)
{
if (args.Handled)
return;
var xform = Transform(uid);
var puddles = new ValueList<(EntityUid Entity, string Solution)>();
puddles.Clear();
foreach (var entity in _lookup.GetEntitiesInRange(xform.MapPosition, 1f))
{
if (TryComp<PuddleComponent>(entity, out var puddle))
{
puddles.Add((entity, puddle.SolutionName));
}
}
if (puddles.Count == 0)
{
_popup.PopupEntity(Loc.GetString("flesh-cultist-not-find-puddles"),
uid, uid, PopupType.Large);
return;
}
var absorbBlood = new Solution();
foreach (var (puddle, solution) in puddles)
{
if (!_solutionContainerSystem.TryGetSolution(puddle, solution, out var puddleSolution))
{
continue;
}
foreach (var puddleSolutionContent in puddleSolution.Value.Comp.Solution.ToList())
{
if (!component.BloodWhitelist.Contains(puddleSolutionContent.Reagent.Prototype))
continue;
var blood = puddleSolution.Value.Comp.Solution.SplitSolutionWithOnly(
puddleSolutionContent.Quantity, puddleSolutionContent.Reagent.Prototype);
absorbBlood.AddSolution(blood, _prototypeManager);
}
var ev = new SolutionContainerChangedEvent(puddleSolution.Value.Comp.Solution, solution);
RaiseLocalEvent(puddle, ref ev);
}
if (absorbBlood.Volume == 0)
{
_popup.PopupEntity(Loc.GetString("flesh-cultist-cant-absorb-puddle"),
uid, uid, PopupType.Large);
return;
}
_audioSystem.PlayPvs(component.BloodAbsorbSound, uid);
_popup.PopupEntity(Loc.GetString("flesh-cultist-absorb-puddle", ("Entity", uid)),
uid, uid, PopupType.Large);
var transferSolution = new Solution();
foreach (var solution in component.HealBloodAbsorbReagents)
{
transferSolution.AddReagent(solution.Reagent, solution.Quantity * (absorbBlood.Volume / 10));
}
if (_solutionContainerSystem.TryGetInjectableSolution(uid, out var injectableSolution, out var _))
{
_solutionContainerSystem.TryAddSolution(injectableSolution.Value, transferSolution);
}
absorbBlood.RemoveAllSolution();
args.Handled = true;
}
private void OnAcidSpit(EntityUid uid, FleshAbilitiesComponent component, FleshCultistAcidSpitActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var acidBullet = Spawn(component.BulletAcidSpawnId, Transform(uid).Coordinates);
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
var userVelocity = _physics.GetMapLinearVelocity(uid);
_gunSystem.ShootProjectile(acidBullet, direction, userVelocity, uid, uid);
_audioSystem.PlayPvs(component.SoundBulletAcid, uid, component.SoundBulletAcid.Params);
}
private void OnHandTransformEvent(EntityUid uid, FleshAbilitiesComponent component, FleshCultistHandTransformEvent args)
{
if (args.Handled)
return;
if (TryComp<CuffableComponent>(uid, out var cuffableComponent) && cuffableComponent.CuffedHandCount > 0)
{
_cuffable.Uncuff(uid, uid, cuffableComponent.LastAddedCuffs);
}
var hands = _handsSystem.EnumerateHands(uid);
var enumerateHands = hands as Hand[] ?? Enumerable.ToArray(hands);
foreach (var hand in enumerateHands)
{
if (hand.Container == null)
continue;
foreach (var containedEntity in hand.Container.ContainedEntities)
{
if (!TryComp(containedEntity, out MetaDataComponent? metaData) || metaData.EntityPrototype == null)
continue;
if (!HasComp<FleshHandModComponent>(containedEntity))
{
if (hand != enumerateHands.First())
continue;
var isDrop = _handsSystem.TryDrop(uid, checkActionBlocker: false);
if (metaData.EntityPrototype.ID == args.Prototype)
continue;
if (isDrop)
continue;
_popup.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"), uid, uid, PopupType.Large);
return;
}
if (metaData.EntityPrototype.ID == args.Prototype)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popup.PopupEntity(Loc.GetString("flesh-cultist-transform-mod-to-hand",
("User", uid), ("Mod", containedEntity)), uid, PopupType.LargeCaution);
QueueDel(containedEntity);
EnsureComp<CuffableComponent>(uid);
args.Handled = true;
return;
}
}
}
var modEntity = Spawn(args.Prototype, Transform(uid).Coordinates);
var isPickup = _handsSystem.TryPickup(uid, modEntity, checkActionBlocker: false, animateUser: false, animate: false);
if (isPickup)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popup.PopupEntity(Loc.GetString("flesh-cultist-transform-hand-to-mod", ("User", uid), ("Mod", modEntity)), uid, PopupType.LargeCaution);
// Удаляем компонент наручников, если есть
if (HasComp<CuffableComponent>(uid))
{
EntityManager.RemoveComponent<CuffableComponent>(uid);
}
}
else
{
Logger.Error($"Failed to equip {args.Prototype} to hand, removing entity");
QueueDel(modEntity);
}
args.Handled = true;
}
private void OnBodyModificationAction(
EntityUid uid,
FleshAbilitiesComponent component,
FleshCultistBodyTransformEvent args)
{
if (args.Handled)
return;
foreach (var slot in args.CheckSlots)
{
if (_inventory.TryGetSlotEntity(uid, slot, out var entity) &&
HasComp<FleshBodyModComponent>(entity))
{
_popup.PopupEntity(Loc.GetString("flesh-cultist-transform-conflict"),
uid, uid, PopupType.Large);
return;
}
}
_inventory.TryGetSlotEntity(uid, args.TargetSlot, out var equippedItem);
if (equippedItem != null)
{
if (HasComp<FleshBodyModComponent>(equippedItem.Value))
{
_popup.PopupEntity(Loc.GetString("flesh-cultist-transform-conflict"),
uid, uid, PopupType.Large);
return;
}
if (TryComp(equippedItem.Value, out MetaDataComponent? metaData) && metaData.EntityPrototype != null &&
metaData.EntityPrototype.ID == args.Prototype)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popup.PopupEntity(Loc.GetString("flesh-cultist-transform-body-remove",
("User", uid), ("Mod", equippedItem)), uid, PopupType.LargeCaution);
EntityManager.DeleteEntity(equippedItem.Value);
_movement.RefreshMovementSpeedModifiers(uid);
args.Handled = true;
return;
}
_inventory.TryUnequip(uid, args.TargetSlot, true, true);
}
var newBodyMod = Spawn(args.Prototype, Transform(uid).Coordinates);
var equipped = _inventory.TryEquip(uid, newBodyMod, args.TargetSlot, true);
if (!equipped)
{
QueueDel(newBodyMod);
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popup.PopupEntity(Loc.GetString("flesh-cultist-transform-body-add",
("User", uid), ("Mod", newBodyMod)), uid, PopupType.LargeCaution);
args.Handled = true;
}
}
private void OnAdrenalinActionEvent(EntityUid uid, FleshAbilitiesComponent component, FleshCultistAdrenalinActionEvent args)
{
if (!_solutionContainerSystem.TryGetInjectableSolution(uid, out var injectableSolution, out var _))
return;
var transferSolution = new Solution();
foreach (var solution in component.AdrenalinReagents)
{
transferSolution.AddReagent(solution.Reagent, solution.Quantity);
}
_solutionContainerSystem.TryAddSolution(injectableSolution.Value, transferSolution);
args.Handled = true;
}
private void OnCreateFleshHeartActionEvent(EntityUid uid, FleshAbilitiesComponent component, FleshCultistCreateFleshHeartActionEvent args)
{
var xform = Transform(uid);
var radius = 1.5f;
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
{
_popup.PopupEntity(Loc.GetString("flesh-cultist-cant-spawn-flesh-heart-in-space",
("Entity", uid)), uid, PopupType.Large);
return;
}
var offsetValue = Vector2Helpers.Normalized(xform.LocalRotation.ToWorldVec());
var targetCord = xform.Coordinates.Offset(offsetValue).SnapToGrid(EntityManager);
var tilerefs = Enumerable.ToArray<TileRef>(grid.GetLocalTilesIntersecting(
new Box2(targetCord.Position + new Vector2(-radius, -radius), targetCord.Position + new Vector2(radius, radius))));
foreach (var tileref in tilerefs)
{
foreach (var entity in tileref.GetEntitiesInTile())
{
PhysicsComponent? physics = null; // We use this to check if it's impassable
if (HasComp<MobStateComponent>(entity) && entity != uid || // Is it a mob?
Resolve(entity, ref physics, false) && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0 ||
HasComp<ConstructionComponent>(entity) && entity != uid) // Is construction?
{
_popup.PopupEntity(Loc.GetString("flesh-cultist-cant-spawn-flesh-heart-here",
("Entity", uid)), uid, PopupType.Large);
return;
}
}
}
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
EntityManager.SpawnEntity(component.FleshHeartId, targetCord);
args.Handled = true;
}
private void OnThrowHugger(EntityUid uid, FleshAbilitiesComponent component, FleshCultistThrowHuggerActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var hugger = Spawn(component.HuggerMobSpawnId, Transform(uid).Coordinates);
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
_throwing.TryThrow(hugger, direction, 7F, uid, 10F);
if (component.SoundThrowHugger != null)
{
_audioSystem.PlayPvs(component.SoundThrowHugger, uid, component.SoundThrowHugger.Params);
}
_popup.PopupEntity(Loc.GetString("flesh-cultist-throw-hugger"), uid, uid,
PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-cultist-throw-hugger-others", ("Entity", uid)),
uid, Filter.PvsExcept(uid), true, PopupType.LargeCaution);
}
}

View file

@ -0,0 +1,432 @@
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Body.Components;
using Content.Server.Flash.Components;
using Content.Server.Forensics;
using Content.Server.Temperature.Components;
using Content.Shared._Sunrise.CollectiveMind;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Actions;
using Content.Shared.Body.Part;
using Content.Shared.Chemistry.Components;
using Content.Shared.Cuffs.Components;
using Content.Shared.Electrocution;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.Components;
using Content.Shared.Humanoid;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory.Events;
using Content.Shared.Mobs;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Store.Components;
using Content.Shared.Sunrise.CollectiveMind;
using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultSystem
{
[ValidatePrototypeId<CollectiveMindPrototype>]
private const string FleshCollectiveMindProto = "FleshCult";
[ValidatePrototypeId<TagPrototype>]
private const string FleshTagProto = "Flesh";
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultFleshCultRule = "FleshCult";
[ValidatePrototypeId<EntityPrototype>]
private const string CreateFleshHeartObjective = "CreateFleshHeartObjective";
[ValidatePrototypeId<EntityPrototype>]
private const string FleshCultSurviveObjective = "FleshCultSurviveObjective";
private void InitializeCultist()
{
SubscribeLocalEvent<FleshCultistComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<FleshCultistComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistInsulatedImmunityMutationEvent>(OnInsulatedImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistPressureImmunityMutationEvent>(OnPressureImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistFlashImmunityMutationEvent>(OnFlashImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistRespiratorImmunityMutationEvent>(OnRespiratorImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistColdTempImmunityMutationEvent>(OnColdTempImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, IsEquippingAttemptEvent>(OnBeingEquippedAttempt);
SubscribeLocalEvent<FleshCultistComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistShopActionEvent>(OnShop);
}
private void OnShop(EntityUid uid, FleshCultistComponent component, FleshCultistShopActionEvent args)
{
if (!TryComp<StoreComponent>(uid, out var store))
return;
_store.ToggleUi(uid, uid, store);
}
private void OnMobStateChanged(EntityUid uid, FleshCultistComponent component, MobStateChangedEvent args)
{
switch (args.NewMobState)
{
case MobState.Critical:
{
EnsureComp<CuffableComponent>(uid);
var hands = _handsSystem.EnumerateHands(uid);
var enumerateHands = hands as Hand[] ?? hands.ToArray();
foreach (var enumerateHand in enumerateHands)
{
if (enumerateHand.Container == null)
continue;
foreach (var containerContainedEntity in enumerateHand.Container.ContainedEntities)
{
if (HasComp<FleshHandModComponent>(containerContainedEntity))
continue;
QueueDel(containerContainedEntity);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
}
}
break;
}
case MobState.Dead:
{
_inventory.TryGetSlotEntity(uid, "shoes", out var shoes);
if (shoes != null)
{
if (HasComp<FleshBodyModComponent>(shoes))
{
EntityManager.DeleteEntity(shoes.Value);
_movement.RefreshMovementSpeedModifiers(uid);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
}
}
_inventory.TryGetSlotEntity(uid, "outerClothing", out var outerClothing);
if (outerClothing != null)
{
if (HasComp<FleshBodyModComponent>(outerClothing))
{
EntityManager.DeleteEntity(outerClothing.Value);
_movement.RefreshMovementSpeedModifiers(uid);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
}
}
ParasiteComesOut(uid, component);
break;
}
}
}
private void OnBeingEquippedAttempt(EntityUid uid, FleshCultistComponent component, IsEquippingAttemptEvent args)
{
if (args.Slot is not ("socks" or "outerClothing"))
return;
_inventory.TryGetSlotEntity(uid, "shoes", out var shoes);
if (shoes == null)
return;
if (HasComp<FleshBodyModComponent>(shoes))
return;
if (args.Slot is "outerClothing" && !_tagSystem.HasTag(args.Equipment, "FullBodyOuter"))
return;
_popup.PopupEntity(Loc.GetString("flesh-cultist-equiped-outer-clothing-blocked",
("Entity", uid)), uid, PopupType.Large);
args.Cancel();
}
private void OnStartup(EntityUid uid, FleshCultistComponent component, ComponentStartup args)
{
ChangeParasiteHunger(uid, 0, component);
if (TryComp(uid, out ActionsComponent? actionsComponent))
{
_action.AddAction(uid, ref component.ActionFleshCultistShopEntity, component.ActionFleshCultistShop, component: actionsComponent);
var fleshAbilities = EnsureComp<FleshAbilitiesComponent>(uid);
var devourAction = _action.AddAction(uid, fleshAbilities.ActionFleshCultistDevourId, component: actionsComponent);
if (devourAction != null)
fleshAbilities.Actions.Add(devourAction.Value);
}
var storeComp = EnsureComp<StoreComponent>(uid);
var collectiveMindComponent = EnsureComp<CollectiveMindComponent>(uid);
if (!collectiveMindComponent.Minds.Contains(FleshCollectiveMindProto))
collectiveMindComponent.Minds.Add(FleshCollectiveMindProto);
storeComp.Categories.Add("FleshCultistPassiveSkills");
storeComp.Categories.Add("FleshCultistActiveSkills");
storeComp.Categories.Add("FleshCultistWeapon");
storeComp.Categories.Add("FleshCultistArmor");
storeComp.CurrencyWhitelist.Add("StolenMutationPoint");
storeComp.BuySuccessSound = component.BuySuccesSound;
storeComp.RefundAllowed = false;
EnsureComp<IgnoreFleshSpiderWebComponent>(uid);
if (HasComp<HungerComponent>(uid))
RemComp<HungerComponent>(uid);
if (HasComp<ThirstComponent>(uid))
RemComp<ThirstComponent>(uid);
_tagSystem.AddTag(uid, FleshTagProto);
if (TryComp<HumanoidAppearanceComponent>(uid, out var appearance))
{
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.RLeg);
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.LLeg);
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.RFoot);
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.LFoot);
Dirty(uid, appearance);
}
}
private void OnShutdown(EntityUid uid, FleshCultistComponent component, ComponentShutdown args)
{
if (TryComp(uid, out ActionsComponent? actionsComponent) && TryComp(uid, out FleshAbilitiesComponent? abilitiesComponent))
{
_action.RemoveAction(uid, component.ActionFleshCultistShopEntity, comp: actionsComponent);
foreach (var abilitiesComponentAction in abilitiesComponent.Actions)
{
_action.RemoveAction(uid, abilitiesComponentAction, comp: actionsComponent);
}
}
RemCompDeferred<IgnoreFleshSpiderWebComponent>(uid);
RemCompDeferred<InsulatedComponent>(uid);
RemCompDeferred<FlashImmunityComponent>(uid);
RemCompDeferred<RespiratorImmunityComponent>(uid);
RemCompDeferred<PressureImmunityComponent>(uid);
RemCompDeferred<FlashImmunityComponent>(uid);
if (TryComp(uid, out CollectiveMindComponent? collectiveMind))
{
if (collectiveMind.Minds.Contains(FleshCollectiveMindProto))
collectiveMind.Minds.Remove(FleshCollectiveMindProto);
}
EnsureComp<HungerComponent>(uid);
EnsureComp<ThirstComponent>(uid);
_alerts.ClearAlert(uid, component.MutationPointAlert);
_tagSystem.RemoveTag(uid, FleshTagProto);
if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
{
_roles.MindRemoveRole<FleshCultistRoleComponent>((mindId, mind));
var indexesToRemove = new List<int>();
for (var i = 0; i < mind.Objectives.Count; i++)
{
var mindObjective = mind.Objectives[i];
var prototypeId = MetaData(mindObjective).EntityPrototype!.ID;
if (prototypeId is CreateFleshHeartObjective || prototypeId is FleshCultSurviveObjective)
{
indexesToRemove.Add(i);
}
}
for (var i = indexesToRemove.Count - 1; i >= 0; i--)
{
_mindSystem.TryRemoveObjective(mindId, mind, indexesToRemove[i]);
}
}
}
private void OnInsulatedImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistInsulatedImmunityMutationEvent args)
{
EnsureComp<InsulatedComponent>(uid);
}
private void OnPressureImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistPressureImmunityMutationEvent args)
{
EnsureComp<PressureImmunityComponent>(uid);
}
private void OnFlashImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistFlashImmunityMutationEvent args)
{
EnsureComp<FlashImmunityComponent>(uid);
}
private void OnRespiratorImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistRespiratorImmunityMutationEvent args)
{
EnsureComp<RespiratorImmunityComponent>(uid);
}
private void OnColdTempImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistColdTempImmunityMutationEvent args)
{
if (TryComp<TemperatureComponent>(uid, out var tempComponent))
{
tempComponent.ColdDamageThreshold = 0;
}
}
private bool ChangeParasiteHunger(EntityUid uid, FixedPoint2 amount, FleshCultistComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
component.Hunger += amount;
if (TryComp<StoreComponent>(uid, out var store))
_store.UpdateUserInterface(uid, uid, store);
_alerts.ShowAlert(uid, component.MutationPointAlert, (short) Math.Clamp(Math.Round(component.Hunger.Float() / 10f), 0, 16));
return true;
}
private int MatchSaturation(int bloodVolume, bool hasAppearance)
{
if (hasAppearance)
{
return 80;
}
return bloodVolume switch
{
>= 300 => 60,
>= 150 => 40,
>= 100 => 20,
_ => 10
};
}
private int MatchEvolutionPoint(int bloodVolume, bool hasAppearance)
{
if (hasAppearance)
{
return 20;
}
return bloodVolume switch
{
>= 300 => 15,
>= 150 => 10,
>= 100 => 5,
_ => 0
};
}
private float MatchHealPoint(int bloodVolume, bool hasAppearance)
{
if (hasAppearance)
{
return 1;
}
return bloodVolume switch
{
>= 300 => 0.8f,
>= 150 => 0.6f,
>= 100 => 0.4f,
_ => 0.2f
};
}
private bool ParasiteComesOut(EntityUid uid, FleshCultistComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
var xform = Transform(uid);
var coordinates = xform.Coordinates;
var abommob = Spawn(component.FleshMutationMobId, _transformSystem.GetMapCoordinates(uid));
if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
{
_mindSystem.TransferTo(mindId, abommob, ghostCheckOverride: true);
}
_popup.PopupEntity(Loc.GetString("flesh-pudge-transform-user", ("EntityTransform", uid)),
uid, uid, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-transform-others",
("Entity", uid), ("EntityTransform", abommob)), abommob, Filter.PvsExcept(abommob),
true, PopupType.LargeCaution);
_audioSystem.PlayPvs(component.SoundMutation, coordinates, AudioParams.Default.WithVariation(0.025f));
if (TryComp(uid, out ContainerManagerComponent? container))
{
foreach (var cont in container.GetAllContainers().ToArray())
{
foreach (var ent in cont.ContainedEntities.ToArray())
{
if (HasComp<BodyPartComponent>(ent))
continue;
if (HasComp<UnremoveableComponent>(ent))
continue;
_containerSystem.Remove(ent, cont, force: true);
Transform(ent).Coordinates = coordinates;
}
}
}
if (TryComp<BloodstreamComponent>(uid, out var bloodstream))
{
var tempSol = new Solution() { MaxVolume = 5 };
if (bloodstream.BloodSolution == null)
return false;
tempSol.AddSolution(bloodstream.BloodSolution.Value.Comp.Solution, _prototypeManager);
if (_puddleSystem.TrySpillAt(uid, tempSol.SplitSolution(50), out var puddleUid))
{
if (TryComp<DnaComponent>(uid, out var dna))
{
var comp = EnsureComp<ForensicsComponent>(puddleUid);
comp.DNAs.Add(dna.DNA);
}
}
}
QueueDel(uid);
return true;
}
public void UpdateCultist(float frameTime)
{
base.Update(frameTime);
var curTime = _timing.CurTime;
foreach (var rev in EntityQuery<FleshCultistComponent>())
{
rev.Accumulator += frameTime;
if (rev.Accumulator <= 1)
continue;
rev.Accumulator -= 1;
if (rev.Hunger <= 40)
{
rev.AccumulatorStarveNotify += 1;
if (rev.AccumulatorStarveNotify > 30)
{
rev.AccumulatorStarveNotify = 0;
_popup.PopupEntity(Loc.GetString("flesh-cultist-hungry"),
rev.Owner, rev.Owner, PopupType.Large);
}
}
if (rev.Hunger < 0)
{
ParasiteComesOut(rev.Owner, rev);
}
ChangeParasiteHunger(rev.Owner, rev.HungerСonsumption, rev);
}
}
}

View file

@ -0,0 +1,425 @@
using System.Linq;
using System.Numerics;
using Content.Server._Sunrise.FleshCult.FleshGrowth;
using Content.Server._Sunrise.FleshCult.GameRule;
using Content.Server.Body.Components;
using Content.Server.Traits.Assorted;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Body.Components;
using Content.Shared.Body.Organ;
using Content.Shared.Body.Part;
using Content.Shared.Damage;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Flesh;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultSystem
{
[Dependency] private readonly FleshCultRuleSystem _fleshCultRule = default!;
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
public void InitializeHeart()
{
SubscribeLocalEvent<FleshHeartComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<FleshHeartComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<FleshHeartComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<FleshHeartComponent, DragDropTargetEvent>(HandleDragDropOn);
SubscribeLocalEvent<FleshHeartComponent, FleshHeartDragFinished>(OnDragFinished);
SubscribeLocalEvent<FleshHeartComponent, ComponentInit>(OnComponentInit);
}
private void OnStartup(EntityUid uid, FleshHeartComponent component, ComponentStartup args)
{
_fleshCultRule.StartGameRule();
var xform = Transform(uid);
RaiseLocalEvent(new FleshHeartStatusChangeEvent()
{
FleshHeartUid = uid,
OwningStation = xform.GridUid,
Status = FleshHeartStatus.Base
});
}
private void OnShutdown(EntityUid uid, FleshHeartComponent component, ComponentShutdown args)
{
_audioSystem.Stop(component.AmbientAudioStream);
}
private void OnDestruction(EntityUid uid, FleshHeartComponent component, DestructionEventArgs args)
{
if (component.Status != HeartStatus.Base)
{
_audioSystem.Stop(component.AmbientAudioStream);
var xform = Transform(uid);
var coordinates = xform.Coordinates;
foreach (var ent in component.BodyContainer.ContainedEntities.ToArray())
{
_containerSystem.Remove(ent, component.BodyContainer, force: true);
Transform(ent).Coordinates = coordinates;
}
var fleshTilesQuery = EntityQueryEnumerator<SpreaderFleshComponent>();
while (fleshTilesQuery.MoveNext(out var ent, out var comp))
{
if (comp.Source != uid)
continue;
if (!TryComp<TagComponent>(ent, out var tagComponent))
continue;
if (_tagSystem.HasAllTags(tagComponent, "Wall", "Flesh"))
_damageableSystem.TryChangeDamage(ent, component.DamageMobsIfHeartDestruct);
else
QueueDel(ent);
}
var fleshWalls = new List<EntityUid>();
var fleshWallsQuery = EntityQueryEnumerator<TagComponent>();
while (fleshWallsQuery.MoveNext(out var ent, out var comp))
{
if (!TryComp<TagComponent>(ent, out var tagComponent))
continue;
var isFleshWall = _tagSystem.HasAllTags(tagComponent, "Wall", "Flesh");
if (isFleshWall)
{
fleshWalls.Add(ent);
}
}
foreach (var mob in component.EdgeMobs.ToArray())
{
_damageableSystem.TryChangeDamage(mob, component.DamageMobsIfHeartDestruct);
}
RaiseLocalEvent(new FleshHeartStatusChangeEvent()
{
FleshHeartUid = uid,
OwningStation = xform.GridUid,
Status = FleshHeartStatus.Destruction
});
}
}
public void UpdateHeart(float frameTime)
{
var fleshCultRule = EntityQuery<FleshCultRuleComponent>().FirstOrDefault();
if (fleshCultRule == null)
return;
var fleshHeartQuery = EntityQueryEnumerator<FleshHeartComponent, TransformComponent>();
while (fleshHeartQuery.MoveNext(out var ent, out var comp, out var xform))
{
var owningStation = _stationSystem.GetOwningStation(ent, xform);
if (owningStation != fleshCultRule.TargetStation)
continue;
switch (comp.Status)
{
case HeartStatus.Base:
{
comp.Accumulator += frameTime;
if (comp.Accumulator <= 1)
continue;
comp.Accumulator -= 1;
if (comp.BodyContainer.ContainedEntities.Count >= comp.BodyToFinalStage)
{
comp.SpawnMobsAccumulator = 500;
comp.Status = HeartStatus.Active;
RaiseLocalEvent(new FleshHeartStatusChangeEvent()
{
FleshHeartUid = ent,
OwningStation = xform.GridUid,
Status = FleshHeartStatus.Active
});
_pointLight.SetEnabled(ent, true);
_chatSystem.DispatchGlobalAnnouncement(
Loc.GetString("flesh-heart-activate-warning"),
colorOverride: Color.Red);
var stationUid = _stationSystem.GetOwningStation(ent);
SpawnFleshFloorOnOpenTiles(ent, comp, Transform(ent), 1);
_roundEndSystem.CancelRoundEndCountdown(stationUid);
_audioSystem.PlayPvs(comp.TransformSound, ent, comp.TransformSound.Params);
comp.AmbientAudioStream = _audioSystem.PlayGlobal(
"/Audio/_Sunrise/FleshCult/flesh_heart.ogg", Filter.Broadcast(), true,
AudioParams.Default.WithLoop(true).WithVolume(-3f))!.Value.Entity;
_sharedAppearance.SetData(ent, FleshHeartVisuals.State, FleshHeartStatus.Active);
}
break;
}
case HeartStatus.Active:
{
comp.SpawnMobsAccumulator += frameTime;
comp.SpawnObjectsAccumulator += frameTime;
comp.FinalStageAccumulator += frameTime;
if (comp.SpawnMobsAccumulator >= comp.SpawnMobsFrequency)
{
comp.SpawnMobsAccumulator = 0;
SpawnMonstersOnOpenTiles(comp, xform, comp.SpawnMobsAmount, comp.SpawnMobsRadius);
}
if (comp.SpawnObjectsAccumulator >= comp.SpawnObjectsFrequency)
{
comp.SpawnObjectsAccumulator = 0;
// SpawnObjectsOnOpenTiles(comp, xform, comp.SpawnObjectsAmount, comp.SpawnObjectsRadius);
}
if (comp.FinalStageAccumulator >= comp.TimeLiveFinalHeartToWin)
{
RaiseLocalEvent(new FleshHeartStatusChangeEvent()
{
FleshHeartUid = ent,
OwningStation = owningStation,
Status = FleshHeartStatus.Final
});
comp.Status = HeartStatus.Disable;
}
break;
}
case HeartStatus.Disable:
{
break;
}
}
}
}
#region Interaction
private void HandleDragDropOn(EntityUid uid, FleshHeartComponent component, ref DragDropTargetEvent args)
{
if (!CanAbsorb(uid, args.Dragged, component))
{
_popup.PopupEntity(Loc.GetString("flesh-heart-cant-absorb-targer"),
args.User, PopupType.Large);
return;
}
if (!TryComp<FixturesComponent>(args.Dragged, out var fixturesComponent))
{
_popup.PopupEntity(Loc.GetString("flesh-heart-cant-absorb-targer"),
args.User, PopupType.Large);
return;
}
if (fixturesComponent.Fixtures["fix1"].Density <= 60)
{
_popup.PopupEntity(
Loc.GetString("flesh-heart-cant-absorb-targer"),
uid, PopupType.Large);
return;
}
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.EntryDelay, new FleshHeartDragFinished(), uid, target: args.Dragged, used: uid)
{
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = false,
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
args.Handled = true;
}
private void OnComponentInit(EntityUid uid, FleshHeartComponent cryoPodComponent, ComponentInit args)
{
cryoPodComponent.BodyContainer = _containerSystem.EnsureContainer<Container>(uid, "bodyContainer");
}
private void OnDragFinished(EntityUid uid, FleshHeartComponent component, FleshHeartDragFinished args)
{
if (args.Cancelled || args.Handled || args.Args.Target == null)
return;
if (!TryComp<FixturesComponent>(args.Args.Target.Value, out var fixturesComponent))
{
_popup.PopupEntity(Loc.GetString("flesh-heart-cant-absorb-targer"),
args.User, PopupType.Large);
return;
}
var xform = Transform(args.Args.Target.Value);
if (TryComp(args.Args.Target.Value, out ContainerManagerComponent? container))
{
foreach (var cont in container.GetAllContainers().ToArray())
{
foreach (var ent in cont.ContainedEntities.ToArray())
{
{
if (HasComp<BodyPartComponent>(ent))
{
continue;
}
_containerSystem.Remove(ent, cont, force: true);
Transform(ent).Coordinates = xform.Coordinates;
}
}
}
}
// SUNRISE-TODO: Убрать конечности хирургией а тело заменить на скелета
if (TryComp<HumanoidAppearanceComponent>(args.Args.Target.Value, out var HuAppComponent))
{
if (TryComp<BloodstreamComponent>(args.Args.Target.Value, out var bloodstreamComponent))
_bloodstreamSystem.TryModifyBloodLevel(args.Args.Target.Value, -300, bloodstreamComponent);
if (TryComp<BodyComponent>(args.Args.Target.Value, out var bodyComponent))
{
var parts = _body.GetBodyChildren(args.Args.Target.Value, bodyComponent).ToArray();
foreach (var part in parts)
{
if (part.Component.PartType == BodyPartType.Head)
continue;
if (part.Component.PartType == BodyPartType.Torso)
{
foreach (var organ in _body.GetPartOrgans(part.Id, part.Component))
{
//_body.RemoveOrgan(organ.Id);
QueueDel(organ.Id);
}
}
else
{
QueueDel(part.Id);
}
}
}
var skeletonSprites = _prototypeManager.Index<HumanoidSpeciesBaseSpritesPrototype>("MobSkeletonSprites");
foreach (var (key, id) in skeletonSprites.Sprites)
{
if (key != HumanoidVisualLayers.Head)
{
_sharedHuApp.SetBaseLayerId(args.Args.Target.Value, key, id, humanoid: HuAppComponent);
}
}
_physics.SetDensity(args.Args.Target.Value, "fix1", fixturesComponent.Fixtures["fix1"], 50);
if (TryComp<AppearanceComponent>(args.Args.Target.Value, out var appComponent))
{
_sharedAppearance.SetData(args.Args.Target.Value, DamageVisualizerKeys.Disabled, true, appComponent);
_damageableSystem.TryChangeDamage(args.Args.Target.Value,
new DamageSpecifier() { DamageDict = { { "Slash", 100 } } });
}
EnsureComp<UnrevivableComponent>(args.Args.Target.Value);
_containerSystem.Insert(args.Args.Target.Value, component.BodyContainer, force: true);
_audioSystem.PlayPvs(component.TransformSound, uid, component.TransformSound.Params);
}
args.Handled = true;
}
#endregion
private bool CanAbsorb(EntityUid uid, EntityUid dragged, FleshHeartComponent component)
{
if (!TryComp<MobStateComponent>(dragged, out var stateComponent))
return false;
if (stateComponent.CurrentState != MobState.Dead)
return false;
if (!Transform(uid).Anchored)
return false;
if (!TryComp<HumanoidAppearanceComponent>(dragged, out var humanoidAppearance))
return false;
if (!_speciesWhitelist.Contains(humanoidAppearance.Species))
return false;
return !TryComp<MindContainerComponent>(dragged, out var mindComp) || true;
}
private void SpawnFleshFloorOnOpenTiles(EntityUid fleshHeart, FleshHeartComponent component, TransformComponent xform, float radius)
{
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return;
var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray();
foreach (var tileref in tilerefs)
{
var canSpawnFloor = true;
foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices).ToList())
{
if (_tagSystem.HasAnyTag(ent, "Wall", "Window", "Flesh"))
canSpawnFloor = false;
}
if (canSpawnFloor)
{
var location = _mapSystem.ToCenterCoordinates(tileref, grid);
var fleshTile = EntityManager.SpawnEntity(component.FleshTileId, location);
var spreaderFleshComponent = EnsureComp<SpreaderFleshComponent>(fleshTile);
spreaderFleshComponent.Source = fleshHeart;
}
}
}
private void SpawnMonstersOnOpenTiles(FleshHeartComponent component, TransformComponent xform, int amount, float radius)
{
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return;
var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray();
_random.Shuffle(tilerefs);
var physQuery = GetEntityQuery<PhysicsComponent>();
var amountCounter = 0;
foreach (var tileref in tilerefs)
{
var valid = true;
foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices))
{
if (!physQuery.TryGetComponent(ent, out var body))
continue;
if (body.BodyType != BodyType.Static ||
!body.Hard ||
(body.CollisionLayer & (int) CollisionGroup.Impassable) == 0)
continue;
valid = false;
break;
}
if (!valid)
continue;
amountCounter++;
var randomMob = _random.Pick(component.Spawns);
var location = _mapSystem.ToCenterCoordinates(tileref, grid);
var mob = Spawn(randomMob, location);
component.EdgeMobs.Add(mob);
if (amountCounter >= amount)
return;
}
}
public sealed class FleshHeartStatusChangeEvent : EntityEventArgs
{
public EntityUid FleshHeartUid;
public EntityUid? OwningStation;
public FleshHeartStatus Status;
}
}

View file

@ -0,0 +1,257 @@
using System.Linq;
using Content.Server.Nutrition.Components;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Hands;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory.Events;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Player;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultSystem
{
public void InitializeHugger()
{
SubscribeLocalEvent<FleshHuggerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<FleshHuggerComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<FleshHuggerComponent, ThrowDoHitEvent>(OnWormDoHit);
SubscribeLocalEvent<FleshHuggerComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<FleshHuggerComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<FleshHuggerComponent, GotEquippedHandEvent>(OnGotEquippedHand);
SubscribeLocalEvent<FleshHuggerComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<FleshHuggerComponent, FleshHuggerJumpActionEvent>(OnJump);
SubscribeLocalEvent<FleshHuggerComponent, FleshHuggerGetOffFromFaceActionEvent>(OnGetOff);
}
private void OnMapInit(EntityUid uid, FleshHuggerComponent component, MapInitEvent args)
{
_action.AddAction(uid, component.ActionFleshHuggerJumpId);
_action.AddAction(uid, component.ActionFleshHuggerGetOffId);
}
private void OnWormDoHit(EntityUid uid, FleshHuggerComponent component, ThrowDoHitEvent args)
{
if (component.IsDeath)
return;
if (HasComp<FleshCultistComponent>(args.Target))
return;
if (!HasComp<HumanoidAppearanceComponent>(args.Target))
return;
if (TryComp(args.Target, out MobStateComponent? mobState))
{
if (mobState.CurrentState is not MobState.Alive)
{
return;
}
}
_inventory.TryGetSlotEntity(args.Target, "head", out var headItem);
if (HasComp<IngestionBlockerComponent>(headItem))
return;
_inventory.TryGetSlotEntity(args.Target, "mask", out var maskItem);
if (HasComp<IdentityBlockerComponent>(maskItem))
return;
_inventory.TryUnequip(args.Target, "head", true);
_inventory.TryUnequip(args.Target, "eyes", true);
_inventory.TryUnequip(args.Target, "mask", true);
var equipped = _inventory.TryEquip(args.Target, uid, "mask", true);
if (!equipped)
return;
component.EquipedOn = args.Target;
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-user"),
args.Target, args.Target, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-mob",
("entity", args.Target)),
uid, uid, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-others",
("entity", args.Target)), args.Target, Filter.PvsExcept(uid), true, PopupType.Large);
EntityManager.EnsureComponent<PacifiedComponent>(uid);
_stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(component.ParalyzeTime), true);
_damageableSystem.TryChangeDamage(args.Target, component.Damage, origin: args.Thrown);
}
private void OnGotEquipped(EntityUid uid, FleshHuggerComponent component, GotEquippedEvent args)
{
if (args.Slot != "mask")
return;
component.EquipedOn = args.Equipee;
EntityManager.EnsureComponent<TemporaryBlindnessComponent>(args.Equipee);
EntityManager.EnsureComponent<PacifiedComponent>(uid);
}
private void OnGotEquippedHand(EntityUid uid, FleshHuggerComponent component, GotEquippedHandEvent args)
{
if (HasComp<FleshCultistComponent>(args.User))
return;
if (component.IsDeath)
return;
_damageableSystem.TryChangeDamage(args.User, component.Damage);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-bite-user"),
args.User, args.User);
}
private void OnGotUnequipped(EntityUid uid, FleshHuggerComponent component, GotUnequippedEvent args)
{
if (args.Slot != "mask")
return;
if (HasComp<PacifiedComponent>(uid))
EntityManager.RemoveComponent<PacifiedComponent>(uid);
if (HasComp<TemporaryBlindnessComponent>(component.EquipedOn))
EntityManager.RemoveComponent<TemporaryBlindnessComponent>(args.Equipee);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(3), true);
component.EquipedOn = new EntityUid();
}
private void OnMeleeHit(EntityUid uid, FleshHuggerComponent component, MeleeHitEvent args)
{
if (!args.HitEntities.Any())
return;
foreach (var entity in args.HitEntities)
{
if (!HasComp<HumanoidAppearanceComponent>(entity))
return;
if (TryComp(entity, out MobStateComponent? mobState))
{
if (mobState.CurrentState is not MobState.Alive)
{
return;
}
}
_inventory.TryGetSlotEntity(entity, "head", out var headItem);
if (HasComp<IngestionBlockerComponent>(headItem))
return;
_inventory.TryGetSlotEntity(entity, "mask", out var maskItem);
if (HasComp<IdentityBlockerComponent>(maskItem))
return;
var random = new Random();
var shouldEquip = random.Next(1, 101) <= component.ChansePounce;
if (!shouldEquip)
return;
_inventory.TryUnequip(entity, "head", true);
_inventory.TryUnequip(entity, "eyes", true);
_inventory.TryUnequip(entity, "mask", true);
var equipped = _inventory.TryEquip(entity, uid, "mask", true);
if (!equipped)
return;
component.EquipedOn = entity;
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-user"),
entity, entity, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-mob", ("entity", entity)),
uid, uid, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-others",
("entity", entity)), entity, Filter.PvsExcept(entity), true, PopupType.Large);
EntityManager.EnsureComponent<PacifiedComponent>(uid);
_stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(component.ParalyzeTime), true);
_damageableSystem.TryChangeDamage(entity, component.Damage, origin: entity);
break;
}
}
private static void OnMobStateChanged(EntityUid uid, FleshHuggerComponent component, MobStateChangedEvent args)
{
if (args.NewMobState == MobState.Dead)
{
component.IsDeath = true;
}
}
private void OnGetOff(EntityUid uid, FleshHuggerComponent component, FleshHuggerGetOffFromFaceActionEvent args)
{
if (args.Handled)
return;
if (component.EquipedOn is not { Valid: true } targetId)
{
_popup.PopupEntity(Loc.GetString("flesh-worm-cant-get-off"),
uid, uid, PopupType.LargeCaution);
return;
}
_inventory.TryUnequip(targetId, "mask", true, true);
component.EquipedOn = new EntityUid();
args.Handled = true;
}
private void OnJump(EntityUid uid, FleshHuggerComponent component, FleshHuggerJumpActionEvent args)
{
if (args.Handled)
return;
if (component.EquipedOn is { Valid: true })
{
_popup.PopupEntity(Loc.GetString("flesh-worm-cant-jump"),
uid, uid, PopupType.LargeCaution);
return;
}
args.Handled = true;
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
_throwing.TryThrow(uid, direction, 7F, uid, 10F);
if (component.SoundJump != null)
{
_audioSystem.PlayPvs(component.SoundJump, uid, component.SoundJump.Params);
}
}
public void UpdateHugger(float frameTime)
{
foreach (var comp in EntityQuery<FleshHuggerComponent>())
{
comp.Accumulator += frameTime;
if (comp.Accumulator <= comp.DamageFrequency)
continue;
comp.Accumulator = 0;
if (comp.EquipedOn is not { Valid: true } targetId)
continue;
if (HasComp<FleshCultistComponent>(comp.EquipedOn))
return;
if (TryComp(targetId, out MobStateComponent? mobState))
{
if (mobState.CurrentState is not MobState.Alive)
{
_inventory.TryUnequip(targetId, "mask", true, true);
comp.EquipedOn = new EntityUid();
return;
}
}
_damageableSystem.TryChangeDamage(targetId, comp.Damage);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-user"),
targetId, targetId, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-others",
("entity", targetId)), targetId, Filter.PvsExcept(targetId), true);
}
}
}

View file

@ -0,0 +1,29 @@
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Mobs;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultSystem
{
public void InitializeMob()
{
SubscribeLocalEvent<FleshMobComponent, MobStateChangedEvent>(OnMobStateChanged);
}
private void OnMobStateChanged(EntityUid uid, FleshMobComponent component, MobStateChangedEvent args)
{
if (args.NewMobState != MobState.Dead)
return;
if (component.SoundDeath == null)
return;
_audioSystem.PlayPvs(component.SoundDeath, uid, component.SoundDeath.Params);
var coords = _transformSystem.GetMapCoordinates(uid);
for (var i = 0; i < component.DeathMobSpawnCount; i++)
{
Spawn(component.DeathMobSpawnId, coords);
}
}
}

View file

@ -0,0 +1,120 @@
using Content.Server._Sunrise.FleshCult.GameRule;
using Content.Server.Sunrise.FleshCult;
using Content.Shared.Humanoid;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
using Content.Shared.Popups;
using Robust.Shared.Player;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultSystem
{
private void InitializeVirus()
{
SubscribeLocalEvent<PendingFleshCultistComponent, MapInitEvent>(OnPendingMapInit);
}
private void OnPendingMapInit(EntityUid uid, PendingFleshCultistComponent component, MapInitEvent args)
{
component.NextParalyze = _timing.CurTime + TimeSpan.FromSeconds(1f);
component.NextScream = _timing.CurTime + TimeSpan.FromSeconds(1f);
}
public void UpdateVirus(float frameTime)
{
var curTime = _timing.CurTime;
var query = EntityQueryEnumerator<PendingFleshCultistComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.CurrentStage == PendingFleshCultistStage.Final)
continue;
comp.Accumulator += frameTime;
var stageTimer = comp.CurrentStage switch
{
PendingFleshCultistStage.First => comp.FirstStageTimer,
PendingFleshCultistStage.Second => comp.SecondStageTimer,
_ => 0f
};
if (comp.Accumulator >= stageTimer)
{
comp.Accumulator = 0f;
comp.CurrentStage = GetNextStage(comp.CurrentStage);
}
switch (comp.CurrentStage)
{
case PendingFleshCultistStage.First:
if (comp.NextScream <= curTime)
{
comp.NextScream = curTime + TimeSpan.FromSeconds(comp.ScreamInterval);
_chatSystem.TryEmoteWithChat(uid, "Scream");
}
if (comp.NextStutter <= curTime)
{
comp.NextStutter = curTime + TimeSpan.FromSeconds(comp.ScreamInterval);
_stuttering.DoStutter(uid, TimeSpan.FromSeconds(comp.StutterTime), true);
}
break;
case PendingFleshCultistStage.Second:
if (comp.NextParalyze <= curTime)
{
comp.NextParalyze = curTime + TimeSpan.FromSeconds(comp.ParalyzeInterval);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(comp.ParalyzeTime), true);
}
if (comp.NextJitter <= curTime)
{
comp.NextJitter = curTime + TimeSpan.FromSeconds(comp.JitterInterval);
_jittering.DoJitter(uid, TimeSpan.FromSeconds(comp.JitterTime), true);
}
break;
case PendingFleshCultistStage.Third:
{
if (!HasComp<MindContainerComponent>(uid) || !TryComp<ActorComponent>(uid, out var targetActor))
return;
var targetPlayer = targetActor.PlayerSession;
if (HasComp<MindShieldComponent>(uid))
{
// SUNRISE-TODO: Сделать это внутри системы майншилда
_popup.PopupEntity("Активация самоуничтожения импланта защиты разума", uid, PopupType.LargeCaution);
_body.GibBody(uid, true);
_explosionSystem.QueueExplosion(uid, "Default", 50, 5, 30, canCreateVacuum: false);
break;
}
if (!TryComp<HumanoidAppearanceComponent>(uid, out var humanoidAppearance))
break;
if (_speciesWhitelist.Contains(humanoidAppearance.Species))
{
RemCompDeferred<PendingFleshCultistComponent>(uid);
break;
}
_antag.ForceMakeAntag<FleshCultRuleComponent>(targetPlayer, DefaultFleshCultRule);
comp.CurrentStage = PendingFleshCultistStage.Final;
RemCompDeferred<PendingFleshCultistComponent>(uid);
break;
}
}
}
}
private PendingFleshCultistStage GetNextStage(PendingFleshCultistStage currentStage)
{
return currentStage switch
{
PendingFleshCultistStage.First => PendingFleshCultistStage.Second,
PendingFleshCultistStage.Second => PendingFleshCultistStage.Third,
PendingFleshCultistStage.Third => PendingFleshCultistStage.Final,
_ => currentStage,
};
}
}

View file

@ -0,0 +1,118 @@
using Content.Server.Actions;
using Content.Server.Antag;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Cuffs;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Humanoid;
using Content.Server.Mind;
using Content.Server.Popups;
using Content.Server.RoundEnd;
using Content.Server.Station.Systems;
using Content.Server.Store.Systems;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Alert;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Inventory;
using Content.Shared.Jittering;
using Content.Shared.Movement.Systems;
using Content.Shared.Roles;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.Stunnable;
using Content.Shared.Tag;
using Content.Shared.Throwing;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultSystem : EntitySystem
{
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly HumanoidAppearanceSystem _sharedHuApp = default!;
[Dependency] private readonly SharedAppearanceSystem _sharedAppearance = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly CuffableSystem _cuffable = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly SharedJitteringSystem _jittering = default!;
[Dependency] private readonly SharedStutteringSystem _stuttering = default!;
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
private readonly List<string> _speciesWhitelist =
[
"Human",
"Reptilian",
"Dwarf",
"Vulpkanin",
"Felinid",
"Moth",
"Swine",
"Arachnid",
"Demon",
"Vox",
"HumanoidXeno",
"Predator",
"Tajaran"
];
public override void Initialize()
{
base.Initialize();
InitializeVirus();
InitializeAbilities();
InitializeCultist();
InitializeMob();
InitializeHugger();
InitializeHeart();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateCultist(frameTime);
UpdateHugger(frameTime);
UpdateVirus(frameTime);
UpdateHeart(frameTime);
}
}

View file

@ -1,644 +0,0 @@
using System.Linq;
using System.Numerics;
using Content.Server.Construction.Components;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Chemistry.Components;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Cuffs.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Maps;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultistSystem
{
private void InitializeAbilities()
{
SubscribeLocalEvent<FleshCultistComponent, FleshCultistBladeActionEvent>(OnBladeActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistClawActionEvent>(OnClawActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistFistActionEvent>(OnFistActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistSpikeHandGunActionEvent>(OnSpikeHandGunActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistArmorActionEvent>(OnArmorActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistHeavyArmorActionEvent>(OnHeavyArmorActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistSpiderLegsActionEvent>(OnSpiderLegsActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistAdrenalinActionEvent>(OnAdrenalinActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistCreateFleshHeartActionEvent>(OnCreateFleshHeartActionEvent);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistThrowHuggerActionEvent>(OnThrowHugger);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistAcidSpitActionEvent>(OnAcidSpit);
}
private void OnAcidSpit(EntityUid uid, FleshCultistComponent component, FleshCultistAcidSpitActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var acidBullet = Spawn(component.BulletAcidSpawnId, Transform(uid).Coordinates);
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
var userVelocity = _physics.GetMapLinearVelocity(uid);
_gunSystem.ShootProjectile(acidBullet, direction, userVelocity, uid, uid);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
}
private void OnBladeActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistBladeActionEvent args)
{
if (args.Handled)
return;
if (TryComp<CuffableComponent>(uid, out var cuffableComponent))
{
if (cuffableComponent.CuffedHandCount > 0)
_cuffable.Uncuff(uid, uid, cuffableComponent.LastAddedCuffs);
}
var hands = _handsSystem.EnumerateHands(uid);
var enumerateHands = hands as Hand[] ?? Enumerable.ToArray<Hand>(hands);
foreach (var enumerateHand in enumerateHands)
{
if (enumerateHand.Container == null)
continue;
foreach (var containerContainedEntity in enumerateHand.Container.ContainedEntities)
{
if (!TryComp(containerContainedEntity, out MetaDataComponent? metaData))
continue;
if (metaData.EntityPrototype == null)
continue;
if (!HasComp<_Sunrise.FleshCult.FleshHandModComponent>(containerContainedEntity))
{
if (enumerateHand != enumerateHands.First())
continue;
var isDrop = _handsSystem.TryDrop(uid, checkActionBlocker: false);
if (metaData.EntityPrototype.ID == component.BladeSpawnId)
continue;
if (isDrop)
continue;
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
return;
}
{
if (enumerateHand != enumerateHands.First())
{
if (metaData.EntityPrototype.ID != component.BladeSpawnId)
continue;
QueueDel(containerContainedEntity);
}
else
{
if (metaData.EntityPrototype.ID != component.BladeSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
}
else
{
QueueDel(containerContainedEntity);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-blade-in-hand",
("Entity", uid)), uid, PopupType.LargeCaution);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
EnsureComp<CuffableComponent>(uid);
args.Handled = true;
}
return;
}
}
}
}
var blade = Spawn(component.BladeSpawnId, Transform(uid).Coordinates);
var isPickup = _handsSystem.TryPickup(uid, blade, checkActionBlocker: false,
animateUser: false, animate: false);
if (isPickup)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-hand-in-blade", ("Entity", uid)),
uid, PopupType.LargeCaution);
if (HasComp<CuffableComponent>(uid))
{
EntityManager.RemoveComponent<CuffableComponent>(uid);
}
}
else
{
Logger.Error("Failed to equip blade to hand, removing blade");
QueueDel(blade);
}
args.Handled = true;
}
private void OnClawActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistClawActionEvent args)
{
if (args.Handled)
return;
if (TryComp<CuffableComponent>(uid, out var cuffableComponent))
{
if (cuffableComponent.CuffedHandCount > 0)
_cuffable.Uncuff(uid, uid, cuffableComponent.LastAddedCuffs);
}
var hands = _handsSystem.EnumerateHands(uid);
var enumerateHands = hands as Hand[] ?? Enumerable.ToArray<Hand>(hands);
foreach (var enumerateHand in enumerateHands)
{
if (enumerateHand.Container == null)
continue;
foreach (var containerContainedEntity in enumerateHand.Container.ContainedEntities)
{
if (!TryComp(containerContainedEntity, out MetaDataComponent? metaData))
continue;
if (metaData.EntityPrototype == null)
continue;
if (!HasComp<_Sunrise.FleshCult.FleshHandModComponent>(containerContainedEntity))
{
if (enumerateHand != enumerateHands.First())
continue;
var isDrop = _handsSystem.TryDrop(uid, checkActionBlocker: false);
if (metaData.EntityPrototype.ID == component.ClawSpawnId)
continue;
if (isDrop)
continue;
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
return;
}
{
if (enumerateHand != enumerateHands.First())
{
if (metaData.EntityPrototype.ID != component.ClawSpawnId)
continue;
QueueDel(containerContainedEntity);
}
else
{
if (metaData.EntityPrototype.ID != component.ClawSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
}
else
{
QueueDel(containerContainedEntity);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-claw-in-hand",
("Entity", uid)), uid, PopupType.LargeCaution);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
EnsureComp<CuffableComponent>(uid);
args.Handled = true;
}
return;
}
}
}
}
var claw = Spawn(component.ClawSpawnId, Transform(uid).Coordinates);
var isPickup = _handsSystem.TryPickup(uid, claw, checkActionBlocker: false,
animateUser: false, animate: false);
if (isPickup)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-hand-in-claw", ("Entity", uid)),
uid, PopupType.LargeCaution);
if (HasComp<CuffableComponent>(uid))
EntityManager.RemoveComponent<CuffableComponent>(uid);
}
else
{
QueueDel(claw);
}
args.Handled = true;
}
private void OnFistActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistFistActionEvent args)
{
if (args.Handled)
return;
if (TryComp<CuffableComponent>(uid, out var cuffableComponent))
{
if (cuffableComponent.CuffedHandCount > 0)
_cuffable.Uncuff(uid, uid, cuffableComponent.LastAddedCuffs);
}
var hands = _handsSystem.EnumerateHands(uid);
var enumerateHands = hands as Hand[] ?? Enumerable.ToArray<Hand>(hands);
foreach (var enumerateHand in enumerateHands)
{
if (enumerateHand.Container == null)
continue;
foreach (var containerContainedEntity in enumerateHand.Container.ContainedEntities)
{
if (!TryComp(containerContainedEntity, out MetaDataComponent? metaData))
continue;
if (metaData.EntityPrototype == null)
continue;
if (!HasComp<_Sunrise.FleshCult.FleshHandModComponent>(containerContainedEntity))
{
if (enumerateHand != enumerateHands.First())
continue;
var isDrop = _handsSystem.TryDrop(uid, checkActionBlocker: false);
if (metaData.EntityPrototype.ID == component.FistSpawnId)
continue;
if (isDrop)
continue;
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
return;
}
{
if (enumerateHand != enumerateHands.First())
{
if (metaData.EntityPrototype.ID != component.FistSpawnId)
continue;
QueueDel(containerContainedEntity);
}
else
{
if (metaData.EntityPrototype.ID != component.FistSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
}
else
{
QueueDel(containerContainedEntity);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-claw-in-hand",
("Entity", uid)), uid, PopupType.LargeCaution);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
EnsureComp<CuffableComponent>(uid);
args.Handled = true;
}
return;
}
}
}
}
var fist = Spawn(component.FistSpawnId, Transform(uid).Coordinates);
var isPickup = _handsSystem.TryPickup(uid, fist, checkActionBlocker: false,
animateUser: false, animate: false);
if (isPickup)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-hand-in-claw", ("Entity", uid)),
uid, PopupType.LargeCaution);
if (HasComp<CuffableComponent>(uid))
EntityManager.RemoveComponent<CuffableComponent>(uid);
}
else
{
QueueDel(fist);
}
args.Handled = true;
}
private void OnSpikeHandGunActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistSpikeHandGunActionEvent args)
{
if (args.Handled)
return;
if (TryComp<CuffableComponent>(uid, out var cuffableComponent))
{
if (cuffableComponent.CuffedHandCount > 0)
_cuffable.Uncuff(uid, uid, cuffableComponent.LastAddedCuffs);
}
var hands = _handsSystem.EnumerateHands(uid);
var enumerateHands = hands as Hand[] ?? Enumerable.ToArray<Hand>(hands);
foreach (var enumerateHand in enumerateHands)
{
if (enumerateHand.Container == null)
continue;
foreach (var containerContainedEntity in enumerateHand.Container.ContainedEntities)
{
if (!TryComp(containerContainedEntity, out MetaDataComponent? metaData))
continue;
if (metaData.EntityPrototype == null)
continue;
if (!HasComp<_Sunrise.FleshCult.FleshHandModComponent>(containerContainedEntity))
{
if (enumerateHand != enumerateHands.First())
continue;
var isDrop = _handsSystem.TryDrop(uid, checkActionBlocker: false);
if (metaData.EntityPrototype.ID == component.SpikeHandGunSpawnId)
continue;
if (isDrop)
continue;
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
return;
}
{
if (enumerateHand != enumerateHands.First())
{
if (metaData.EntityPrototype.ID != component.SpikeHandGunSpawnId)
continue;
QueueDel(containerContainedEntity);
}
else
{
if (metaData.EntityPrototype.ID != component.SpikeHandGunSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-user-hand-blocked"),
uid, uid, PopupType.Large);
}
else
{
QueueDel(containerContainedEntity);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-spike-gun-in-hand",
("Entity", uid)), uid, PopupType.LargeCaution);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
EnsureComp<CuffableComponent>(uid);
args.Handled = true;
}
return;
}
}
}
}
var claw = Spawn(component.SpikeHandGunSpawnId, Transform(uid).Coordinates);
var isPickup = _handsSystem.TryPickup(uid, claw, checkActionBlocker: false,
animateUser: false, animate: false);
if (isPickup)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-hand-in-spike-gun", ("Entity", uid)),
uid, PopupType.LargeCaution);
if (HasComp<CuffableComponent>(uid))
RemComp<CuffableComponent>(uid);
}
else
{
QueueDel(claw);
}
args.Handled = true;
}
private void OnArmorActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistArmorActionEvent args)
{
_inventory.TryGetSlotEntity(uid, "outerClothing", out var outerClothing);
if (outerClothing != null)
{
if (!TryComp(outerClothing, out MetaDataComponent? metaData))
return;
if (metaData.EntityPrototype == null)
return;
if (metaData.EntityPrototype.ID == component.HeavyArmorSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-blocked"),
uid, uid, PopupType.Large);
}
else if (metaData.EntityPrototype.ID != component.ArmorSpawnId)
{
_inventory.TryUnequip(uid, "outerClothing", true, true);
var armor = Spawn(component.ArmorSpawnId, Transform(uid).Coordinates);
var equipped = _inventory.TryEquip(uid, armor, "outerClothing", true);
if (!equipped)
{
QueueDel(armor);
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-on",
("Entity", uid)), uid, PopupType.LargeCaution);
args.Handled = true;
}
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-off",
("Entity", uid)), uid, PopupType.LargeCaution);
EntityManager.DeleteEntity(outerClothing.Value);
_movement.RefreshMovementSpeedModifiers(uid);
args.Handled = true;
}
}
else
{
var armor = Spawn(component.ArmorSpawnId, Transform(uid).Coordinates);
var equipped = _inventory.TryEquip(uid, armor, "outerClothing", true);
if (!equipped)
{
QueueDel(armor);
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-on",
("Entity", uid)), uid, PopupType.LargeCaution);
args.Handled = true;
}
}
}
private void OnHeavyArmorActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistHeavyArmorActionEvent args)
{
_inventory.TryGetSlotEntity(uid, "shoes", out var shoes);
if (shoes != null)
{
if (!TryComp(shoes, out MetaDataComponent? metaData))
return;
if (metaData.EntityPrototype == null)
return;
if (metaData.EntityPrototype.ID == component.SpiderLegsSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-blocked"),
uid, uid, PopupType.Large);
return;
}
}
_inventory.TryGetSlotEntity(uid, "outerClothing", out var outerClothing);
if (outerClothing != null)
{
if (!TryComp(outerClothing, out MetaDataComponent? metaData))
return;
if (metaData.EntityPrototype == null)
return;
if (metaData.EntityPrototype.ID == component.ArmorSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-blocked"),
uid, uid, PopupType.Large);
}
else if (metaData.EntityPrototype.ID != component.HeavyArmorSpawnId)
{
_inventory.TryUnequip(uid, "outerClothing", true, true);
var armor = Spawn(component.HeavyArmorSpawnId, Transform(uid).Coordinates);
var equipped = _inventory.TryEquip(uid, armor, "outerClothing", true);
if (!equipped)
{
QueueDel(armor);
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-on",
("Entity", uid)), uid, PopupType.LargeCaution);
args.Handled = true;
}
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-off",
("Entity", uid)), uid, PopupType.LargeCaution);
EntityManager.DeleteEntity(outerClothing.Value);
_movement.RefreshMovementSpeedModifiers(uid);
args.Handled = true;
}
}
else
{
var armor = Spawn(component.HeavyArmorSpawnId, Transform(uid).Coordinates);
var equipped = _inventory.TryEquip(uid, armor, "outerClothing", true);
if (!equipped)
{
QueueDel(armor);
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-armor-on",
("Entity", uid)), uid, PopupType.LargeCaution);
args.Handled = true;
}
}
}
private void OnSpiderLegsActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistSpiderLegsActionEvent args)
{
_inventory.TryGetSlotEntity(uid, "outerClothing", out var outerClothing);
if (outerClothing != null)
{
if (!TryComp(outerClothing, out MetaDataComponent? metaData))
return;
if (metaData.EntityPrototype == null)
return;
if (metaData.EntityPrototype.ID == component.ArmorSpawnId || metaData.EntityPrototype.ID == component.HeavyArmorSpawnId)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-spider-legs-blocked"),
uid, uid, PopupType.Large);
args.Handled = true;
return;
}
if (_tagSystem.HasTag(outerClothing.Value, "FullBodyOuter"))
{
_inventory.TryUnequip(uid, "outerClothing", true, true);
args.Handled = true;
}
}
_inventory.TryGetSlotEntity(uid, "shoes", out var shoes);
if (shoes != null)
{
if (!TryComp(shoes, out MetaDataComponent? metaData))
return;
if (metaData.EntityPrototype == null)
return;
if (metaData.EntityPrototype.ID == component.SpiderLegsSpawnId)
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-spider-legs-off",
("Entity", uid)), uid, PopupType.LargeCaution);
EntityManager.DeleteEntity(shoes.Value);
_movement.RefreshMovementSpeedModifiers(uid);
args.Handled = true;
return;
}
_inventory.TryUnequip(uid, "shoes", true, true);
}
_inventory.TryUnequip(uid, "socks", true, true);
var legs = Spawn(component.SpiderLegsSpawnId, Transform(uid).Coordinates);
var equipped = _inventory.TryEquip(uid, legs, "shoes", true, true);
if (!equipped)
{
QueueDel(legs);
}
else
{
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-transform-spider-legs-on",
("Entity", uid)), uid, PopupType.LargeCaution);
args.Handled = true;
}
}
private void OnAdrenalinActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistAdrenalinActionEvent args)
{
if (!_solutionContainerSystem.TryGetInjectableSolution(uid, out var injectableSolution, out var _))
return;
var transferSolution = new Solution();
foreach (var solution in component.AdrenalinReagents)
{
transferSolution.AddReagent(solution.Reagent, solution.Quantity);
}
_solutionContainerSystem.TryAddSolution(injectableSolution.Value, transferSolution);
args.Handled = true;
}
private void OnCreateFleshHeartActionEvent(EntityUid uid, FleshCultistComponent component, FleshCultistCreateFleshHeartActionEvent args)
{
var xform = Transform(uid);
var radius = 1.5f;
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-cant-spawn-flesh-heart-in-space",
("Entity", uid)), uid, PopupType.Large);
return;
}
var offsetValue = Vector2Helpers.Normalized(xform.LocalRotation.ToWorldVec());
var targetCord = xform.Coordinates.Offset(offsetValue).SnapToGrid(EntityManager);
var tilerefs = Enumerable.ToArray<TileRef>(grid.GetLocalTilesIntersecting(
new Box2(targetCord.Position + new Vector2(-radius, -radius), targetCord.Position + new Vector2(radius, radius))));
foreach (var tileref in tilerefs)
{
foreach (var entity in tileref.GetEntitiesInTile())
{
PhysicsComponent? physics = null; // We use this to check if it's impassable
if (HasComp<MobStateComponent>(entity) && entity != uid || // Is it a mob?
Resolve(entity, ref physics, false) && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0 ||
HasComp<ConstructionComponent>(entity) && entity != uid) // Is construction?
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-cant-spawn-flesh-heart-here",
("Entity", uid)), uid, PopupType.Large);
return;
}
}
}
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
EntityManager.SpawnEntity(component.FleshHeartId, targetCord);
args.Handled = true;
}
private void OnThrowHugger(EntityUid uid, FleshCultistComponent component, FleshCultistThrowHuggerActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var hugger = Spawn(component.HuggerMobSpawnId, Transform(uid).Coordinates);
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
_throwing.TryThrow(hugger, direction, 7F, uid, 10F);
if (component.SoundThrowHugger != null)
{
_audioSystem.PlayPvs(component.SoundThrowHugger, uid, component.SoundThrowHugger.Params);
}
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-throw-hugger"), uid, uid,
PopupType.LargeCaution);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-throw-hugger-others", ("Entity", uid)),
uid, Filter.PvsExcept(uid), true, PopupType.LargeCaution);
}
}

View file

@ -1,808 +0,0 @@
using System.Linq;
using Content.Server._Sunrise.FleshCult.GameRule;
using Content.Server.Actions;
using Content.Server.Atmos.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Cuffs;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Flash.Components;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.Humanoid;
using Content.Server.Mind;
using Content.Server.Popups;
using Content.Server.Store.Systems;
using Content.Server.Sunrise.FleshCult;
using Content.Server.Temperature.Components;
using Content.Server.Traits.Assorted;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Alert;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Cuffs.Components;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Electrocution;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Jittering;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.Store.Components;
using Content.Shared.Stunnable;
using Content.Shared.Sunrise.CollectiveMind;
using Content.Shared.Tag;
using Content.Shared.Throwing;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server._Sunrise.FleshCult;
public sealed partial class FleshCultistSystem : SharedFleshCultistSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly HumanoidAppearanceSystem _sharedHuApp = default!;
[Dependency] private readonly SharedAppearanceSystem _sharedAppearance = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly CuffableSystem _cuffable = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly FleshCultRuleSystem _fleshCultRule = default!;
[Dependency] private readonly SharedJitteringSystem _jittering = default!;
[Dependency] private readonly SharedStutteringSystem _stuttering = default!;
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FleshCultistComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistShopActionEvent>(OnShop);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistInsulatedImmunityMutationEvent>(OnInsulatedImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistPressureImmunityMutationEvent>(OnPressureImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistFlashImmunityMutationEvent>(OnFlashImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistRespiratorImmunityMutationEvent>(OnRespiratorImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistColdTempImmunityMutationEvent>(OnColdTempImmunityMutation);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistDevourActionEvent>(OnDevourAction);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistDevourDoAfterEvent>(OnDevourDoAfter);
SubscribeLocalEvent<FleshCultistComponent, IsEquippingAttemptEvent>(OnBeingEquippedAttempt);
SubscribeLocalEvent<FleshCultistComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<FleshCultistComponent, FleshCultistAbsorbBloodPoolActionEvent>(OnAbsormBloodPoolActionEvent);
SubscribeLocalEvent<PendingFleshCultistComponent, MapInitEvent>(OnPendingMapInit);
InitializeAbilities();
}
private void OnPendingMapInit(EntityUid uid, PendingFleshCultistComponent component, MapInitEvent args)
{
component.NextParalyze = _timing.CurTime + TimeSpan.FromSeconds(1f);
component.NextScream = _timing.CurTime + TimeSpan.FromSeconds(1f);
}
private void OnMobStateChanged(EntityUid uid, FleshCultistComponent component, MobStateChangedEvent args)
{
switch (args.NewMobState)
{
case MobState.Critical:
{
EnsureComp<CuffableComponent>(uid);
var hands = _handsSystem.EnumerateHands(uid);
var enumerateHands = hands as Hand[] ?? hands.ToArray();
foreach (var enumerateHand in enumerateHands)
{
if (enumerateHand.Container == null)
continue;
foreach (var containerContainedEntity in enumerateHand.Container.ContainedEntities)
{
if (HasComp<_Sunrise.FleshCult.FleshHandModComponent>(containerContainedEntity))
continue;
QueueDel(containerContainedEntity);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
}
}
break;
}
case MobState.Dead:
{
_inventory.TryGetSlotEntity(uid, "shoes", out var shoes);
if (shoes != null)
{
if (TryComp(shoes, out MetaDataComponent? metaData))
{
if (metaData.EntityPrototype != null)
{
if (metaData.EntityPrototype.ID == component.SpiderLegsSpawnId)
{
EntityManager.DeleteEntity(shoes.Value);
_movement.RefreshMovementSpeedModifiers(uid);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
}
}
}
}
_inventory.TryGetSlotEntity(uid, "outerClothing", out var outerClothing);
if (outerClothing != null)
{
if (TryComp(outerClothing, out MetaDataComponent? metaData))
{
if (metaData.EntityPrototype != null)
{
if (metaData.EntityPrototype.ID == component.ArmorSpawnId ||
metaData.EntityPrototype.ID == component.HeavyArmorSpawnId)
{
EntityManager.DeleteEntity(outerClothing.Value);
_movement.RefreshMovementSpeedModifiers(uid);
_audioSystem.PlayPvs(component.SoundMutation, uid, component.SoundMutation.Params);
}
}
}
}
ParasiteComesOut(uid, component);
break;
}
}
}
private void OnBeingEquippedAttempt(EntityUid uid, FleshCultistComponent component, IsEquippingAttemptEvent args)
{
if (args.Slot is not ("socks" or "outerClothing"))
return;
_inventory.TryGetSlotEntity(uid, "shoes", out var shoes);
if (shoes == null)
return;
if (!TryComp(shoes, out MetaDataComponent? metaData))
return;
if (metaData.EntityPrototype == null)
return;
if (metaData.EntityPrototype.ID != component.SpiderLegsSpawnId)
return;
if (args.Slot is "outerClothing" && !_tagSystem.HasTag(args.Equipment, "FullBodyOuter"))
return;
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-equiped-outer-clothing-blocked",
("Entity", uid)), uid, PopupType.Large);
args.Cancel();
}
private void OnStartup(EntityUid uid, FleshCultistComponent component, ComponentStartup args)
{
ChangeParasiteHunger(uid, 0, component);
_action.AddAction(uid, "FleshCultistShop");
_action.AddAction(uid, "FleshCultistDevour");
_action.AddAction(uid, "FleshCultistAbsorbBloodPool");
var storeComp = EnsureComp<StoreComponent>(uid);
var collectiveMindComponent = EnsureComp<CollectiveMindComponent>(uid);
if (!collectiveMindComponent.Minds.Contains("FleshCult"))
collectiveMindComponent.Minds.Add("FleshCult");
storeComp.Categories.Add("FleshCultistPassiveSkills");
storeComp.Categories.Add("FleshCultistActiveSkills");
storeComp.Categories.Add("FleshCultistWeapon");
storeComp.Categories.Add("FleshCultistArmor");
storeComp.CurrencyWhitelist.Add("StolenMutationPoint");
storeComp.BuySuccessSound = component.BuySuccesSound;
storeComp.RefundAllowed = false;
EnsureComp<IgnoreFleshSpiderWebComponent>(uid);
if (HasComp<HungerComponent>(uid))
RemComp<HungerComponent>(uid);
if (HasComp<ThirstComponent>(uid))
RemComp<ThirstComponent>(uid);
_tagSystem.AddTag(uid, "Flesh");
if (TryComp<HumanoidAppearanceComponent>(uid, out var appearance))
{
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.RLeg);
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.LLeg);
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.RFoot);
appearance.HideLayersOnEquip.Add(HumanoidVisualLayers.LFoot);
Dirty(uid, appearance);
}
}
private void OnInsulatedImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistInsulatedImmunityMutationEvent args)
{
EnsureComp<InsulatedComponent>(uid);
}
private void OnPressureImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistPressureImmunityMutationEvent args)
{
EnsureComp<PressureImmunityComponent>(uid);
}
private void OnFlashImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistFlashImmunityMutationEvent args)
{
EnsureComp<FlashImmunityComponent>(uid);
}
private void OnRespiratorImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistRespiratorImmunityMutationEvent args)
{
EnsureComp<RespiratorImmunityComponent>(uid);
}
private void OnColdTempImmunityMutation(EntityUid uid, FleshCultistComponent component,
FleshCultistColdTempImmunityMutationEvent args)
{
if (TryComp<TemperatureComponent>(uid, out var tempComponent))
{
tempComponent.ColdDamageThreshold = 0;
}
}
private void OnShop(EntityUid uid, FleshCultistComponent component, FleshCultistShopActionEvent args)
{
if (!TryComp<StoreComponent>(uid, out var store))
return;
_store.ToggleUi(uid, uid, store);
}
private bool ChangeParasiteHunger(EntityUid uid, FixedPoint2 amount, FleshCultistComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
component.Hunger += amount;
if (TryComp<StoreComponent>(uid, out var store))
_store.UpdateUserInterface(uid, uid, store);
_alerts.ShowAlert(uid, component.MutationPointAlert, (short) Math.Clamp(Math.Round(component.Hunger.Float() / 10f), 0, 16));
return true;
}
private void OnDevourAction(EntityUid uid, FleshCultistComponent component, FleshCultistDevourActionEvent args)
{
if (args.Handled)
return;
var target = args.Target;
if (!TryComp<MobStateComponent>(target, out var targetState))
return;
if (!TryComp<BloodstreamComponent>(target, out var bloodstream))
return;
var hasAppearance = false;
{
switch (targetState.CurrentState)
{
case MobState.Dead:
if (EntityManager.TryGetComponent(target, out HumanoidAppearanceComponent? humanoidAppearance))
{
if (!component.SpeciesWhitelist.Contains(humanoidAppearance.Species))
{
_popupSystem.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-not-have-flesh"),
uid, uid);
return;
}
if (TryComp<FixturesComponent>(target, out var fixturesComponent))
{
if (fixturesComponent.Fixtures["fix1"].Density <= 60)
{
_popupSystem.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-invalid"),
uid, uid);
return;
}
}
hasAppearance = true;
}
else
{
if (bloodstream.BloodReagent != "Blood")
{
_popupSystem.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-not-have-flesh"),
uid, uid);
return;
}
if (bloodstream.BloodMaxVolume < 30)
{
_popupSystem.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-invalid"),
uid, uid);
return;
}
}
var saturation = MatchSaturation(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
if (component.Hunger + saturation >= component.MaxHunger)
{
_popupSystem.PopupEntity(
Loc.GetString("flesh-cultist-devout-not-hungry"),
uid, uid);
return;
}
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager ,uid, component.DevourTime,
new FleshCultistDevourDoAfterEvent(), uid, target: target, used: uid)
{
BreakOnMove = true,
});
args.Handled = true;
break;
case MobState.Invalid:
case MobState.Critical:
case MobState.Alive:
default:
_popupSystem.PopupEntity(
Loc.GetString("flesh-cultist-devout-target-alive"),
uid, uid);
break;
}
}
}
private void OnDevourDoAfter(EntityUid uid, FleshCultistComponent component, FleshCultistDevourDoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
if (args.Args.Target == null)
return;
if (!TryComp<BloodstreamComponent>(args.Args.Target.Value, out var bloodstream))
return;
var hasAppearance = false;
var xform = Transform(args.Args.Target.Value);
var coordinates = xform.Coordinates;
_audioSystem.PlayPvs(component.DevourSound, coordinates, AudioParams.Default.WithVariation(0.025f).WithMaxDistance(5f));
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-devour-target",
("Entity", uid), ("Target", args.Args.Target)), uid);
if (bloodstream.BloodSolution != null)
{
_bloodstreamSystem.SpillAllSolutions(args.Args.Target.Value, bloodstream);
}
if (TryComp<HumanoidAppearanceComponent>(args.Args.Target, out var HuAppComponent))
{
if (TryComp(args.Args.Target.Value, out ContainerManagerComponent? container))
{
foreach (var cont in container.GetAllContainers().ToArray())
{
foreach (var ent in cont.ContainedEntities.ToArray())
{
if (HasComp<BodyPartComponent>(ent))
{
continue;
}
_containerSystem.Remove(ent, cont, force: true);
Transform(ent).Coordinates = coordinates;
}
}
}
if (TryComp<BodyComponent>(args.Args.Target, out var bodyComponent))
{
var parts = _body.GetBodyChildren(args.Args.Target, bodyComponent).ToArray();
foreach (var part in parts)
{
if (part.Component.PartType == BodyPartType.Head)
continue;
if (part.Component.PartType == BodyPartType.Torso)
{
foreach (var organ in _body.GetPartOrgans(part.Id, part.Component))
{
//_body.RemoveOrgan(organ.Id);
QueueDel(organ.Id);
}
}
else
{
QueueDel(part.Id);
}
}
}
var skeletonSprites = _proto.Index<HumanoidSpeciesBaseSpritesPrototype>("MobSkeletonSprites");
foreach (var (key, id) in skeletonSprites.Sprites)
{
if (key != HumanoidVisualLayers.Head)
{
_sharedHuApp.SetBaseLayerId(args.Args.Target.Value, key, id, humanoid: HuAppComponent);
}
}
if (TryComp<FixturesComponent>(args.Args.Target, out var fixturesComponent))
{
_physics.SetDensity(args.Args.Target.Value, "fix1", fixturesComponent.Fixtures["fix1"], 50);
}
if (TryComp<AppearanceComponent>(args.Args.Target, out var appComponent))
{
_sharedAppearance.SetData(args.Args.Target.Value, DamageVisualizerKeys.Disabled, true, appComponent);
}
hasAppearance = true;
}
var saturation = MatchSaturation(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
var evolutionPoint = MatchEvolutionPoint(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
var healPoint = MatchHealPoint(bloodstream.BloodMaxVolume.Value / 100, hasAppearance);
RemComp<BloodstreamComponent>(args.Args.Target.Value);
EnsureComp<UnrevivableComponent>(args.Args.Target.Value);
if (!hasAppearance)
{
QueueDel(args.Args.Target.Value);
}
if (_solutionContainerSystem.TryGetInjectableSolution(uid, out var injectableSolution, out _))
{
var transferSolution = new Solution();
foreach (var solution in component.HealDevourReagents)
{
transferSolution.AddReagent(solution.Reagent, solution.Quantity * healPoint);
}
_solutionContainerSystem.TryAddSolution(injectableSolution.Value, transferSolution);
}
component.Hunger += saturation;
_store.TryAddCurrency(new Dictionary<string, FixedPoint2>
{ {component.StolenCurrencyPrototype, evolutionPoint} }, uid);
}
private int MatchSaturation(int bloodVolume, bool hasAppearance)
{
if (hasAppearance)
{
return 100;
}
return bloodVolume switch
{
>= 300 => 80,
>= 150 => 60,
>= 100 => 40,
_ => 20
};
}
private int MatchEvolutionPoint(int bloodVolume, bool hasAppearance)
{
if (hasAppearance)
{
return 20;
}
return bloodVolume switch
{
>= 300 => 15,
>= 150 => 10,
>= 100 => 5,
_ => 0
};
}
private float MatchHealPoint(int bloodVolume, bool hasAppearance)
{
if (hasAppearance)
{
return 1;
}
return bloodVolume switch
{
>= 300 => 0.8f,
>= 150 => 0.6f,
>= 100 => 0.4f,
_ => 0.2f
};
}
private bool ParasiteComesOut(EntityUid uid, FleshCultistComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
var xform = Transform(uid);
var coordinates = xform.Coordinates;
var abommob = Spawn(component.FleshMutationMobId, _transformSystem.GetMapCoordinates(uid));
if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
{
_mindSystem.TransferTo(mindId, abommob, ghostCheckOverride: true);
}
_popupSystem.PopupEntity(Loc.GetString("flesh-pudge-transform-user", ("EntityTransform", uid)),
uid, uid, PopupType.LargeCaution);
_popupSystem.PopupEntity(Loc.GetString("flesh-pudge-transform-others",
("Entity", uid), ("EntityTransform", abommob)), abommob, Filter.PvsExcept(abommob),
true, PopupType.LargeCaution);
_audioSystem.PlayPvs(component.SoundMutation, coordinates, AudioParams.Default.WithVariation(0.025f));
if (TryComp(uid, out ContainerManagerComponent? container))
{
foreach (var cont in container.GetAllContainers().ToArray())
{
foreach (var ent in cont.ContainedEntities.ToArray())
{
if (HasComp<BodyPartComponent>(ent))
continue;
if (HasComp<UnremoveableComponent>(ent))
continue;
_containerSystem.Remove(ent, cont, force: true);
Transform(ent).Coordinates = coordinates;
}
}
}
if (TryComp<BloodstreamComponent>(uid, out var bloodstream))
{
var tempSol = new Solution() { MaxVolume = 5 };
if (bloodstream.BloodSolution == null)
return false;
tempSol.AddSolution(bloodstream.BloodSolution.Value.Comp.Solution, _proto);
if (_puddleSystem.TrySpillAt(uid, tempSol.SplitSolution(50), out var puddleUid))
{
if (TryComp<DnaComponent>(uid, out var dna))
{
var comp = EnsureComp<ForensicsComponent>(puddleUid);
comp.DNAs.Add(dna.DNA);
}
}
}
QueueDel(uid);
return true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var curTime = _timing.CurTime;
var query = EntityQueryEnumerator<PendingFleshCultistComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.CurrentStage == PendingFleshCultistStage.Final)
continue;
comp.Accumulator += frameTime;
var stageTimer = comp.CurrentStage switch
{
PendingFleshCultistStage.First => comp.FirstStageTimer,
PendingFleshCultistStage.Second => comp.SecondStageTimer,
_ => 0f
};
if (comp.Accumulator >= stageTimer)
{
comp.Accumulator = 0f;
comp.CurrentStage = GetNextStage(comp.CurrentStage);
}
switch (comp.CurrentStage)
{
case PendingFleshCultistStage.First:
if (comp.NextScream <= curTime)
{
comp.NextScream = curTime + TimeSpan.FromSeconds(comp.ScreamInterval);
_chatSystem.TryEmoteWithChat(uid, "Scream");
}
if (comp.NextStutter <= curTime)
{
comp.NextStutter = curTime + TimeSpan.FromSeconds(comp.ScreamInterval);
_stuttering.DoStutter(uid, TimeSpan.FromSeconds(comp.StutterTime), true);
}
break;
case PendingFleshCultistStage.Second:
if (comp.NextParalyze <= curTime)
{
comp.NextParalyze = curTime + TimeSpan.FromSeconds(comp.ParalyzeInterval);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(comp.ParalyzeTime), true);
}
if (comp.NextJitter <= curTime)
{
comp.NextJitter = curTime + TimeSpan.FromSeconds(comp.JitterInterval);
_jittering.DoJitter(uid, TimeSpan.FromSeconds(comp.JitterTime), true);
}
break;
case PendingFleshCultistStage.Third:
{
if (!TryComp<MindContainerComponent>(uid, out var targetMindComp))
return;
if (HasComp<MindShieldComponent>(uid))
{
_popupSystem.PopupEntity("Активация самоуничтожения импланта защиты разума", uid, PopupType.LargeCaution);
_body.GibBody(uid, true);
_explosionSystem.QueueExplosion(uid, "Default", 50, 5, 30, canCreateVacuum: false);
return;
}
var fleshCultRule = _fleshCultRule.StartGameRule();
_fleshCultRule.MakeCultist(uid, 0, fleshCultRule);
comp.CurrentStage = PendingFleshCultistStage.Final;
break;
}
}
}
foreach (var rev in EntityQuery<FleshCultistComponent>())
{
rev.Accumulator += frameTime;
if (rev.Accumulator <= 1)
continue;
rev.Accumulator -= 1;
if (rev.Hunger <= 40)
{
rev.AccumulatorStarveNotify += 1;
if (rev.AccumulatorStarveNotify > 30)
{
rev.AccumulatorStarveNotify = 0;
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-hungry"),
rev.Owner, rev.Owner, PopupType.Large);
}
}
if (rev.Hunger < 0)
{
ParasiteComesOut(rev.Owner, rev);
}
ChangeParasiteHunger(rev.Owner, rev.HungerСonsumption, rev);
}
}
private PendingFleshCultistStage GetNextStage(PendingFleshCultistStage currentStage)
{
return currentStage switch
{
PendingFleshCultistStage.First => PendingFleshCultistStage.Second,
PendingFleshCultistStage.Second => PendingFleshCultistStage.Third,
PendingFleshCultistStage.Third => PendingFleshCultistStage.Final,
_ => currentStage,
};
}
private void OnAbsormBloodPoolActionEvent(EntityUid uid, FleshCultistComponent component,
FleshCultistAbsorbBloodPoolActionEvent args)
{
if (args.Handled)
return;
var xform = Transform(uid);
var puddles = new ValueList<(EntityUid Entity, string Solution)>();
puddles.Clear();
foreach (var entity in _lookup.GetEntitiesInRange(xform.MapPosition, 1f))
{
if (TryComp<PuddleComponent>(entity, out var puddle))
{
puddles.Add((entity, puddle.SolutionName));
}
}
if (puddles.Count == 0)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-not-find-puddles"),
uid, uid, PopupType.Large);
return;
}
var absorbBlood = new Solution();
foreach (var (puddle, solution) in puddles)
{
if (!_solutionContainerSystem.TryGetSolution(puddle, solution, out var puddleSolution))
{
continue;
}
foreach (var puddleSolutionContent in puddleSolution.Value.Comp.Solution.ToList())
{
if (!component.BloodWhitelist.Contains(puddleSolutionContent.Reagent.Prototype))
continue;
var blood = puddleSolution.Value.Comp.Solution.SplitSolutionWithOnly(
puddleSolutionContent.Quantity, puddleSolutionContent.Reagent.Prototype);
absorbBlood.AddSolution(blood, _proto);
}
var ev = new SolutionContainerChangedEvent(puddleSolution.Value.Comp.Solution, solution);
RaiseLocalEvent(puddle, ref ev);
}
if (absorbBlood.Volume == 0)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-cant-absorb-puddle"),
uid, uid, PopupType.Large);
return;
}
_audioSystem.PlayPvs(component.BloodAbsorbSound, uid, component.BloodAbsorbSound.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-absorb-puddle", ("Entity", uid)),
uid, uid, PopupType.Large);
var transferSolution = new Solution();
foreach (var solution in component.HealBloodAbsorbReagents)
{
transferSolution.AddReagent(solution.Reagent, solution.Quantity * (absorbBlood.Volume / 10));
}
if (_solutionContainerSystem.TryGetInjectableSolution(uid, out var injectableSolution, out var _))
{
_solutionContainerSystem.TryAddSolution(injectableSolution.Value, transferSolution);
}
absorbBlood.RemoveAllSolution();
args.Handled = true;
}
}

View file

@ -1,6 +1,6 @@
namespace Content.Server._Sunrise.FleshCult.FleshGrowth;
[RegisterComponent, Access(typeof(SpreaderFleshSystem), typeof(FleshHeartSystem))]
[RegisterComponent, Access(typeof(SpreaderFleshSystem), typeof(FleshCultSystem))]
public sealed partial class SpreaderFleshComponent : Component
{
[DataField("chance", required: true)]

View file

@ -1,466 +0,0 @@
using System.Linq;
using System.Numerics;
using Content.Server._Sunrise.FleshCult.FleshGrowth;
using Content.Server._Sunrise.FleshCult.GameRule;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Humanoid;
using Content.Server.Popups;
using Content.Server.RoundEnd;
using Content.Server.Station.Systems;
using Content.Server.Traits.Assorted;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Damage;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Flesh;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
using Content.Shared.Tag;
using Robust.Server.Containers;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._Sunrise.FleshCult
{
public sealed class FleshHeartSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly HumanoidAppearanceSystem _sharedHuApp = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedAppearanceSystem _sharedAppearance = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FleshHeartComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<FleshHeartComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<FleshHeartComponent, DragDropTargetEvent>(HandleDragDropOn);
SubscribeLocalEvent<FleshHeartComponent, FleshHeartDragFinished>(OnDragFinished);
SubscribeLocalEvent<FleshHeartComponent, ComponentInit>(OnComponentInit);
}
private void OnShutdown(EntityUid uid, FleshHeartComponent component, ComponentShutdown args)
{
_audioSystem.Stop(component.AmbientAudioStream);
}
private void OnDestruction(EntityUid uid, FleshHeartComponent component, DestructionEventArgs args)
{
if (component.State != HeartStates.Base)
{
_audioSystem.Stop(component.AmbientAudioStream);
//var stationUid = _stationSystem.GetOwningStation(uid);
//if (stationUid != null)
//{
// _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true,
// true, true);
//}
var xform = Transform(uid);
var coordinates = xform.Coordinates;
foreach (var ent in component.BodyContainer.ContainedEntities.ToArray())
{
_containerSystem.Remove(ent, component.BodyContainer, force: true);
Transform(ent).Coordinates = coordinates;
}
var fleshTilesQuery = EntityQueryEnumerator<SpreaderFleshComponent>();
while (fleshTilesQuery.MoveNext(out var ent, out var comp))
{
if (comp.Source != uid)
continue;
if (!TryComp<TagComponent>(ent, out var tagComponent))
continue;
if (_tagSystem.HasAllTags(tagComponent, "Wall", "Flesh"))
_damageableSystem.TryChangeDamage(ent, component.DamageMobsIfHeartDestruct);
else
QueueDel(ent);
}
var fleshWalls = new List<EntityUid>();
var fleshWallsQuery = EntityQueryEnumerator<TagComponent>();
while (fleshWallsQuery.MoveNext(out var ent, out var comp))
{
if (!TryComp<TagComponent>(ent, out var tagComponent))
continue;
var isFleshWall = _tagSystem.HasAllTags(tagComponent, "Wall", "Flesh");
if (isFleshWall)
{
fleshWalls.Add(ent);
}
}
foreach (var mob in component.EdgeMobs.ToArray())
{
_damageableSystem.TryChangeDamage(mob, component.DamageMobsIfHeartDestruct);
}
RaiseLocalEvent(new FleshHeartDestructionEvent()
{
FleshHeardUid = uid,
OwningStation = xform.GridUid
});
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var fleshHeartQuery = EntityQueryEnumerator<FleshHeartComponent, TransformComponent>();
while (fleshHeartQuery.MoveNext(out var ent, out var comp, out var xform))
{
var fleshCultRule = EntityQuery<FleshCultRuleComponent>().FirstOrDefault();
if (fleshCultRule == null)
{
continue;
}
var owningStation = _stationSystem.GetOwningStation(ent, xform);
if (owningStation != fleshCultRule.TargetStation)
continue;
switch (comp.State)
{
case HeartStates.Base:
{
comp.Accumulator += frameTime;
if (comp.Accumulator <= 1)
continue;
comp.Accumulator -= 1;
if (comp.BodyContainer.ContainedEntities.Count >= comp.BodyToFinalStage)
{
comp.SpawnMobsAccumulator = 500;
comp.State = HeartStates.Active;
RaiseLocalEvent(new FleshHeartActivateEvent()
{
FleshHeardUid = ent,
OwningStation = xform.GridUid
});
_chat.DispatchGlobalAnnouncement(
Loc.GetString("flesh-heart-activate-warning"),
colorOverride: Color.Red);
// _audioSystem.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
var stationUid = _stationSystem.GetOwningStation(ent);
//if (stationUid != null)
//{
// _alertLevel.SetLevel(stationUid.Value, comp.AlertLevelOnActivate, false,
// true, true, true);
//}
//_audioSystem.PlayGlobal(
// "/Audio/_Sunrise/FleshCult/flesh_heart_activate.ogg", Filter.Broadcast(), true,
// AudioParams.Default);
SpawnFleshFloorOnOpenTiles(ent, comp, Transform(ent), 1);
_roundEndSystem.CancelRoundEndCountdown(stationUid);
_audioSystem.PlayPvs(comp.TransformSound, ent, comp.TransformSound.Params);
comp.AmbientAudioStream = _audioSystem.PlayGlobal(
"/Audio/_Sunrise/FleshCult/flesh_heart.ogg", Filter.Broadcast(), true,
AudioParams.Default.WithLoop(true).WithVolume(-3f))!.Value.Entity;
_appearance.SetData(ent, FleshHeartVisuals.State, FleshHeartStatus.Active);
}
break;
}
case HeartStates.Active:
{
comp.SpawnMobsAccumulator += frameTime;
comp.SpawnObjectsAccumulator += frameTime;
comp.FinalStageAccumulator += frameTime;
if (comp.SpawnMobsAccumulator >= comp.SpawnMobsFrequency)
{
comp.SpawnMobsAccumulator = 0;
SpawnMonstersOnOpenTiles(comp, xform, comp.SpawnMobsAmount, comp.SpawnMobsRadius);
}
if (comp.SpawnObjectsAccumulator >= comp.SpawnObjectsFrequency)
{
comp.SpawnObjectsAccumulator = 0;
// SpawnObjectsOnOpenTiles(comp, xform, comp.SpawnObjectsAmount, comp.SpawnObjectsRadius);
}
if (comp.FinalStageAccumulator >= comp.TimeLiveFinalHeartToWin)
{
comp.State = HeartStates.Disable;
RaiseLocalEvent(new FleshHeartFinalEvent()
{
FleshHeardUid = ent,
OwningStation = owningStation,
});
}
break;
}
case HeartStates.Disable:
{
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
#region Interaction
private void HandleDragDropOn(EntityUid uid, FleshHeartComponent component, ref DragDropTargetEvent args)
{
if (!CanAbsorb(uid, args.Dragged, component))
{
_popup.PopupEntity(Loc.GetString("flesh-heart-cant-absorb-targer"),
args.User, PopupType.Large);
return;
}
if (!TryComp<FixturesComponent>(args.Dragged, out var fixturesComponent))
{
_popup.PopupEntity(Loc.GetString("flesh-heart-cant-absorb-targer"),
args.User, PopupType.Large);
return;
}
if (fixturesComponent.Fixtures["fix1"].Density <= 60)
{
_popup.PopupEntity(
Loc.GetString("flesh-heart-cant-absorb-targer"),
uid, PopupType.Large);
return;
}
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.EntryDelay, new FleshHeartDragFinished(), uid, target: args.Dragged, used: uid)
{
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = false,
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
args.Handled = true;
}
protected void OnComponentInit(EntityUid uid, FleshHeartComponent cryoPodComponent, ComponentInit args)
{
cryoPodComponent.BodyContainer = _containerSystem.EnsureContainer<Container>(uid, "bodyContainer");
}
private void OnDragFinished(EntityUid uid, FleshHeartComponent component, FleshHeartDragFinished args)
{
if (args.Cancelled || args.Handled || args.Args.Target == null)
return;
if (!TryComp<FixturesComponent>(args.Args.Target.Value, out var fixturesComponent))
{
_popup.PopupEntity(Loc.GetString("flesh-heart-cant-absorb-targer"),
args.User, PopupType.Large);
return;
}
var xform = Transform(args.Args.Target.Value);
if (TryComp(args.Args.Target.Value, out ContainerManagerComponent? container))
{
foreach (var cont in container.GetAllContainers().ToArray())
{
foreach (var ent in cont.ContainedEntities.ToArray())
{
{
if (HasComp<BodyPartComponent>(ent))
{
continue;
}
_containerSystem.Remove(ent, cont, force: true);
Transform(ent).Coordinates = xform.Coordinates;
}
}
}
}
if (TryComp<HumanoidAppearanceComponent>(args.Args.Target.Value, out var HuAppComponent))
{
if (TryComp<BodyComponent>(args.Args.Target.Value, out var bodyComponent))
{
var parts = _body.GetBodyChildren(args.Args.Target.Value, bodyComponent).ToArray();
foreach (var part in parts)
{
if (part.Component.PartType == BodyPartType.Head)
continue;
if (part.Component.PartType == BodyPartType.Torso)
{
foreach (var organ in _body.GetPartOrgans(part.Id, part.Component))
{
_body.RemoveOrgan(organ.Id);
}
}
else
{
QueueDel(part.Id);
}
}
}
_bloodstreamSystem.TryModifyBloodLevel(args.Args.Target.Value, -300);
var skeletonSprites = _proto.Index<HumanoidSpeciesBaseSpritesPrototype>("MobSkeletonSprites");
foreach (var (key, id) in skeletonSprites.Sprites)
{
if (key != HumanoidVisualLayers.Head)
{
_sharedHuApp.SetBaseLayerId(args.Args.Target.Value, key, id, humanoid: HuAppComponent);
}
}
_physics.SetDensity(args.Args.Target.Value, "fix1", fixturesComponent.Fixtures["fix1"], 50);
if (TryComp<AppearanceComponent>(args.Args.Target.Value, out var appComponent))
{
_sharedAppearance.SetData(args.Args.Target.Value, DamageVisualizerKeys.Disabled, true, appComponent);
_damageableSystem.TryChangeDamage(args.Args.Target.Value,
new DamageSpecifier() { DamageDict = { { "Slash", 100 } } });
}
EnsureComp<UnrevivableComponent>(args.Args.Target.Value);
_containerSystem.Insert(args.Args.Target.Value, component.BodyContainer, force: true);
_audioSystem.PlayPvs(component.TransformSound, uid, component.TransformSound.Params);
}
args.Handled = true;
}
#endregion
private bool CanAbsorb(EntityUid uid, EntityUid dragged, FleshHeartComponent component)
{
if (!TryComp<MobStateComponent>(dragged, out var stateComponent))
return false;
if (stateComponent.CurrentState != MobState.Dead)
return false;
if (!Transform(uid).Anchored)
return false;
if (!TryComp<HumanoidAppearanceComponent>(dragged, out var humanoidAppearance))
return false;
if (!(component.SpeciesWhitelist.Contains(humanoidAppearance.Species)))
return false;
return !TryComp<MindContainerComponent>(dragged, out var mindComp) || true;
}
private void SpawnFleshFloorOnOpenTiles(EntityUid fleshHeart, FleshHeartComponent component, TransformComponent xform, float radius)
{
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return;
var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray();
foreach (var tileref in tilerefs)
{
var canSpawnFloor = true;
foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices).ToList())
{
if (_tagSystem.HasAnyTag(ent, "Wall", "Window", "Flesh"))
canSpawnFloor = false;
}
if (canSpawnFloor)
{
var location = _mapSystem.ToCenterCoordinates(tileref, grid);
var fleshTile = EntityManager.SpawnEntity(component.FleshTileId, location);
var spreaderFleshComponent = EnsureComp<SpreaderFleshComponent>(fleshTile);
spreaderFleshComponent.Source = fleshHeart;
}
}
}
private void SpawnMonstersOnOpenTiles(FleshHeartComponent component, TransformComponent xform, int amount, float radius)
{
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return;
var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray();
_random.Shuffle(tilerefs);
var physQuery = GetEntityQuery<PhysicsComponent>();
var amountCounter = 0;
foreach (var tileref in tilerefs)
{
var valid = true;
foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices))
{
if (!physQuery.TryGetComponent(ent, out var body))
continue;
if (body.BodyType != BodyType.Static ||
!body.Hard ||
(body.CollisionLayer & (int) CollisionGroup.Impassable) == 0)
continue;
valid = false;
break;
}
if (!valid)
continue;
amountCounter++;
var randomMob = _random.Pick(component.Spawns);
var location = _mapSystem.ToCenterCoordinates(tileref, grid);
var mob = Spawn(randomMob, location);
component.EdgeMobs.Add(mob);
if (amountCounter >= amount)
return;
}
}
public sealed class FleshHeartFinalEvent : EntityEventArgs
{
public EntityUid FleshHeardUid;
public EntityUid? OwningStation;
}
public sealed class FleshHeartActivateEvent : EntityEventArgs
{
public EntityUid FleshHeardUid;
public EntityUid? OwningStation;
}
public sealed class FleshHeartDestructionEvent : EntityEventArgs
{
public EntityUid FleshHeardUid;
public EntityUid? OwningStation;
}
}
}

View file

@ -1,283 +0,0 @@
using System.Linq;
using Content.Server.Actions;
using Content.Server.Flesh;
using Content.Server.Nutrition.Components;
using Content.Server.Popups;
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.CombatMode;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Damage;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Hands;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Melee.Events;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using FleshHuggerComponent = Content.Shared._Sunrise.FleshCult.FleshHuggerComponent;
namespace Content.Server._Sunrise.FleshCult
{
public sealed class FleshHuggerSystem : SharedFleshHuggerSystem
{
[Dependency] private SharedStunSystem _stunSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<FleshHuggerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<FleshHuggerComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<FleshHuggerComponent, ThrowDoHitEvent>(OnWormDoHit);
SubscribeLocalEvent<FleshHuggerComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<FleshHuggerComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<FleshHuggerComponent, GotEquippedHandEvent>(OnGotEquippedHand);
SubscribeLocalEvent<FleshHuggerComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<FleshHuggerComponent, FleshHuggerJumpActionEvent>(OnJump);
SubscribeLocalEvent<FleshHuggerComponent, FleshHuggerGetOffFromFaceActionEvent>(OnGetOff);
}
private void OnMapInit(EntityUid uid, FleshHuggerComponent component, MapInitEvent args)
{
_action.AddAction(uid, component.ActionFleshHuggerJumpId);
_action.AddAction(uid, component.ActionFleshHuggerGetOffId);
}
private void OnWormDoHit(EntityUid uid, FleshHuggerComponent component, ThrowDoHitEvent args)
{
if (component.IsDeath)
return;
if (HasComp<FleshCultistComponent>(args.Target))
return;
if (!HasComp<HumanoidAppearanceComponent>(args.Target))
return;
if (TryComp(args.Target, out MobStateComponent? mobState))
{
if (mobState.CurrentState is not MobState.Alive)
{
return;
}
}
_inventory.TryGetSlotEntity(args.Target, "head", out var headItem);
if (HasComp<IngestionBlockerComponent>(headItem))
return;
_inventory.TryGetSlotEntity(args.Target, "mask", out var maskItem);
if (HasComp<IdentityBlockerComponent>(maskItem))
return;
_inventory.TryUnequip(args.Target, "head", true);
_inventory.TryUnequip(args.Target, "eyes", true);
_inventory.TryUnequip(args.Target, "mask", true);
var equipped = _inventory.TryEquip(args.Target, uid, "mask", true);
if (!equipped)
return;
component.EquipedOn = args.Target;
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-user"),
args.Target, args.Target, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-mob",
("entity", args.Target)),
uid, uid, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-others",
("entity", args.Target)), args.Target, Filter.PvsExcept(uid), true, PopupType.Large);
EntityManager.EnsureComponent<PacifiedComponent>(uid);
_stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(component.ParalyzeTime), true);
_damageableSystem.TryChangeDamage(args.Target, component.Damage, origin: args.Thrown);
}
private void OnGotEquipped(EntityUid uid, FleshHuggerComponent component, GotEquippedEvent args)
{
if (args.Slot != "mask")
return;
component.EquipedOn = args.Equipee;
EntityManager.EnsureComponent<TemporaryBlindnessComponent>(args.Equipee);
EntityManager.EnsureComponent<PacifiedComponent>(uid);
}
private void OnGotEquippedHand(EntityUid uid, FleshHuggerComponent component, GotEquippedHandEvent args)
{
if (HasComp<_Sunrise.FleshCult.FleshPudgeComponent>(args.User))
return;
if (HasComp<FleshCultistComponent>(args.User))
return;
if (component.IsDeath)
return;
_damageableSystem.TryChangeDamage(args.User, component.Damage);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-bite-user"),
args.User, args.User);
}
private void OnGotUnequipped(EntityUid uid, FleshHuggerComponent component, GotUnequippedEvent args)
{
if (args.Slot != "mask")
return;
if (HasComp<PacifiedComponent>(uid))
EntityManager.RemoveComponent<PacifiedComponent>(uid);
if (HasComp<TemporaryBlindnessComponent>(component.EquipedOn))
EntityManager.RemoveComponent<TemporaryBlindnessComponent>(args.Equipee);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(3), true);
component.EquipedOn = new EntityUid();
}
private void OnMeleeHit(EntityUid uid, FleshHuggerComponent component, MeleeHitEvent args)
{
if (!args.HitEntities.Any())
return;
foreach (var entity in args.HitEntities)
{
if (!HasComp<HumanoidAppearanceComponent>(entity))
return;
if (TryComp(entity, out MobStateComponent? mobState))
{
if (mobState.CurrentState is not MobState.Alive)
{
return;
}
}
_inventory.TryGetSlotEntity(entity, "head", out var headItem);
if (HasComp<IngestionBlockerComponent>(headItem))
return;
_inventory.TryGetSlotEntity(entity, "mask", out var maskItem);
if (HasComp<IdentityBlockerComponent>(maskItem))
return;
var random = new Random();
var shouldEquip = random.Next(1, 101) <= component.ChansePounce;
if (!shouldEquip)
return;
_inventory.TryUnequip(entity, "head", true);
_inventory.TryUnequip(entity, "eyes", true);
_inventory.TryUnequip(entity, "mask", true);
var equipped = _inventory.TryEquip(entity, uid, "mask", true);
if (!equipped)
return;
component.EquipedOn = entity;
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-user"),
entity, entity, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-hit-mob", ("entity", entity)),
uid, uid, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-others",
("entity", entity)), entity, Filter.PvsExcept(entity), true, PopupType.Large);
EntityManager.EnsureComponent<PacifiedComponent>(uid);
_stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(component.ParalyzeTime), true);
_damageableSystem.TryChangeDamage(entity, component.Damage, origin: entity);
break;
}
}
private static void OnMobStateChanged(EntityUid uid, FleshHuggerComponent component, MobStateChangedEvent args)
{
if (args.NewMobState == MobState.Dead)
{
component.IsDeath = true;
}
}
private void OnGetOff(EntityUid uid, FleshHuggerComponent component, FleshHuggerGetOffFromFaceActionEvent args)
{
if (args.Handled)
return;
if (component.EquipedOn is not { Valid: true } targetId)
{
_popup.PopupEntity(Loc.GetString("flesh-worm-cant-get-off"),
uid, uid, PopupType.LargeCaution);
return;
}
_inventory.TryUnequip(targetId, "mask", true, true);
component.EquipedOn = new EntityUid();
args.Handled = true;
}
private void OnJump(EntityUid uid, FleshHuggerComponent component, FleshHuggerJumpActionEvent args)
{
if (args.Handled)
return;
if (component.EquipedOn is { Valid: true })
{
_popup.PopupEntity(Loc.GetString("flesh-worm-cant-jump"),
uid, uid, PopupType.LargeCaution);
return;
}
args.Handled = true;
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
_throwing.TryThrow(uid, direction, 7F, uid, 10F);
if (component.SoundJump != null)
{
_audioSystem.PlayPvs(component.SoundJump, uid, component.SoundJump.Params);
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var comp in EntityQuery<FleshHuggerComponent>())
{
comp.Accumulator += frameTime;
if (comp.Accumulator <= comp.DamageFrequency)
continue;
comp.Accumulator = 0;
if (comp.EquipedOn is not { Valid: true } targetId)
continue;
if (HasComp<FleshCultistComponent>(comp.EquipedOn))
return;
if (TryComp(targetId, out MobStateComponent? mobState))
{
if (mobState.CurrentState is not MobState.Alive)
{
_inventory.TryUnequip(targetId, "mask", true, true);
comp.EquipedOn = new EntityUid();
return;
}
}
_damageableSystem.TryChangeDamage(targetId, comp.Damage);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-user"),
targetId, targetId, PopupType.LargeCaution);
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-eat-face-others",
("entity", targetId)), targetId, Filter.PvsExcept(targetId), true);
}
}
}
}

View file

@ -1,38 +0,0 @@
using Content.Shared._Sunrise.FleshCult;
using Content.Shared.Flesh;
using Content.Shared.Mobs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
namespace Content.Server._Sunrise.FleshCult
{
public sealed class FleshMobSystem : SharedFleshMobSystem
{
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FleshMobComponent, MobStateChangedEvent>(OnMobStateChanged);
}
private void OnMobStateChanged(EntityUid uid, FleshMobComponent component, MobStateChangedEvent args)
{
if (args.NewMobState != MobState.Dead)
return;
if (component.SoundDeath == null)
return;
_audioSystem.PlayPvs(component.SoundDeath, uid, component.SoundDeath.Params);
var coords = _transformSystem.GetMapCoordinates(uid);
for (var i = 0; i < component.DeathMobSpawnCount; i++)
{
Spawn(component.DeathMobSpawnId, coords);
}
}
}
}

View file

@ -1,53 +0,0 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server._Sunrise.FleshCult
{
[RegisterComponent]
public sealed partial class FleshPudgeComponent : Component
{
[DataField("actionThrowWorm", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ActionThrowWormId = "FleshThrowWorm";
[DataField("actionAcidSpit", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ActionAcidSpitId = "FleshAcidSpit";
[DataField("actionAbsorbBloodPool", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ActionAbsorbBloodPoolId = "AbsorbBloodPool";
[ViewVariables(VVAccess.ReadWrite), DataField("soundThrowWorm")]
public SoundSpecifier? SoundThrowWorm = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/throw_worm.ogg");
[ViewVariables(VVAccess.ReadWrite),
DataField("faceHuggerMobSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string FaceHuggerMobSpawnId = "MobFleshHugger";
[ViewVariables(VVAccess.ReadWrite),
DataField("bulletAcidSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string BulletAcidSpawnId = "BulletSplashAcid";
[DataField("healBloodAbsorbReagents")] public Solution HealBloodAbsorbReagents = new()
{
Contents =
{
new ReagentQuantity(new ReagentId("Carol", null), 1),
}
};
[ViewVariables(VVAccess.ReadWrite), DataField("bloodWhitelist")]
public List<string> BloodWhitelist = new()
{
"Blood",
"CopperBlood",
"InsectBlood",
"AmmoniaBlood",
"ZombieBlood"
};
[DataField("bloodAbsorbSound")]
public SoundSpecifier BloodAbsorbSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg");
}
}

View file

@ -1,155 +0,0 @@
using System.Linq;
using Content.Server.Actions;
using Content.Server.Popups;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Fluids.Components;
using Content.Shared.Popups;
using Content.Shared.Throwing;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Prototypes;
namespace Content.Server._Sunrise.FleshCult
{
public sealed class FleshPudgeSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FleshPudgeComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<FleshPudgeComponent, FleshPudgeThrowFaceHuggerActionEvent>(OnThrowFaceHugger);
SubscribeLocalEvent<FleshPudgeComponent, FleshPudgeAbsorbBloodPoolActionEvent>(OnAbsorbBloodPoolActionEvent);
SubscribeLocalEvent<FleshPudgeComponent, FleshPudgeAcidSpitActionEvent>(OnAcidSpit);
}
private void OnThrowFaceHugger(EntityUid uid, FleshPudgeComponent component, FleshPudgeThrowFaceHuggerActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var worm = Spawn(component.FaceHuggerMobSpawnId, Transform(uid).Coordinates);
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
_throwing.TryThrow(worm, direction, 7F, uid, 10F);
if (component.SoundThrowWorm != null)
{
_audioSystem.PlayPvs(component.SoundThrowWorm, uid, component.SoundThrowWorm.Params);
}
_popupSystem.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-popup"), uid, PopupType.LargeCaution);
}
private void OnAcidSpit(EntityUid uid, FleshPudgeComponent component, FleshPudgeAcidSpitActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var acidBullet = Spawn(component.BulletAcidSpawnId, Transform(uid).Coordinates);
var xform = Transform(uid);
var mapCoords = args.Target.ToMap(_entityManager, _transformSystem);
var direction = mapCoords.Position - xform.MapPosition.Position;
var userVelocity = _physics.GetMapLinearVelocity(uid);
_gunSystem.ShootProjectile(acidBullet, direction, userVelocity, uid, uid);
_audioSystem.PlayPvs(component.BloodAbsorbSound, uid, component.BloodAbsorbSound.Params);
}
private void OnAbsorbBloodPoolActionEvent(EntityUid uid, FleshPudgeComponent component,
FleshPudgeAbsorbBloodPoolActionEvent args)
{
if (args.Handled)
return;
var xform = Transform(uid);
var puddles = new ValueList<(EntityUid Entity, string Solution)>();
puddles.Clear();
foreach (var entity in _lookup.GetEntitiesInRange(xform.MapPosition, 1f))
{
if (TryComp<PuddleComponent>(entity, out var puddle))
{
puddles.Add((entity, puddle.SolutionName));
}
}
if (puddles.Count == 0)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-not-find-puddles"),
uid, uid, PopupType.Large);
return;
}
var absorbBlood = new Solution();
foreach (var (puddle, solution) in puddles)
{
if (!_solutionSystem.TryGetSolution(puddle, solution, out var puddleSolution))
{
continue;
}
foreach (var puddleSolutionContent in puddleSolution.Value.Comp.Solution.ToList())
{
if (!component.BloodWhitelist.Contains(puddleSolutionContent.Reagent.Prototype))
continue;
var blood = puddleSolution.Value.Comp.Solution.SplitSolutionWithOnly(
puddleSolutionContent.Quantity, puddleSolutionContent.Reagent.Prototype);
absorbBlood.AddSolution(blood, _prototypeManager);
}
var ev = new SolutionContainerChangedEvent(puddleSolution.Value.Comp.Solution, solution);
RaiseLocalEvent(puddle, ref ev);
}
if (absorbBlood.Volume == 0)
{
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-cant-absorb-puddle"),
uid, uid, PopupType.Large);
return;
}
_audioSystem.PlayPvs(component.BloodAbsorbSound, uid, component.BloodAbsorbSound.Params);
_popupSystem.PopupEntity(Loc.GetString("flesh-cultist-absorb-puddle", ("Entity", uid)),
uid, uid, PopupType.Large);
var transferSolution = new Solution();
foreach (var solution in component.HealBloodAbsorbReagents)
{
transferSolution.AddReagent(solution.Reagent, solution.Quantity * (absorbBlood.Volume / 10));
}
if (_solutionSystem.TryGetInjectableSolution(uid, out var injectableSolution, out var _))
{
_solutionSystem.TryAddSolution(injectableSolution.Value, transferSolution);
}
absorbBlood.RemoveAllSolution();
args.Handled = true;
}
private void OnStartup(EntityUid uid, FleshPudgeComponent component, ComponentStartup args)
{
_action.AddAction(uid, component.ActionAcidSpitId);
_action.AddAction(uid, component.ActionThrowWormId);
_action.AddAction(uid, component.ActionAbsorbBloodPoolId);
}
}
}

View file

@ -16,8 +16,6 @@ public sealed partial class FleshCultRuleComponent : Component
public SoundSpecifier AddedSound = new SoundPathSpecifier(
"/Audio/_Sunrise/FleshCult/flesh_culstis_greeting.ogg");
public Dictionary<string, EntityUid> Cultists = new();
[DataField("fleshCultistPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public string FleshCultistPrototypeId = "FleshCultist";
@ -27,21 +25,15 @@ public sealed partial class FleshCultRuleComponent : Component
[DataField("faction", customTypeSerializer: typeof(PrototypeIdSerializer<NpcFactionPrototype>), required: true)]
public string Faction = default!;
public List<EntityUid> Cultists = new();
public int TotalCultists => Cultists.Count;
public readonly List<string> CultistsNames = new();
public WinTypes WinType = WinTypes.Fail;
public bool FleshHeartActive = false;
public Dictionary<EntityUid, FleshHeartStatus> FleshHearts = new();
public EntityUid? TargetStation;
[DataField]
public List<string> StarterItems = new() { "SyringeCarolNT", "SyringeCarolNT", "SyringeCarolNT" };
public List<string> SpeciesWhitelist = new()
{
"Human",
@ -51,16 +43,11 @@ public sealed partial class FleshCultRuleComponent : Component
"Felinid",
"Moth",
"Swine",
"Arachnid"
"Arachnid",
"Demon",
"Vox",
"HumanoidXeno",
"Predator",
"Tajaran"
};
public enum WinTypes
{
FleshHeartFinal,
AllCultistsDead,
Fail
}
public TimeSpan AnnounceAt = TimeSpan.Zero;
public Dictionary<ICommonSession, HumanoidCharacterProfile> StartCandidates = new();
}

View file

@ -1,5 +1,6 @@
using System.Linq;
using Content.Server.Antag;
using Content.Server.Antag.Components;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
@ -12,9 +13,11 @@ using Content.Shared._Sunrise.FleshCult;
using Content.Shared.FixedPoint;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mind;
using Content.Shared.NPC.Components;
using Content.Shared.NPC.Systems;
using Content.Shared.Roles;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
namespace Content.Server._Sunrise.FleshCult.GameRule;
@ -26,18 +29,54 @@ public sealed class FleshCultRuleSystem : GameRuleSystem<FleshCultRuleComponent>
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[ValidatePrototypeId<AntagPrototype>]
private const string LeaderAntagProto = "FleshCultistLeader";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FleshCultRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
SubscribeLocalEvent<FleshHeartSystem.FleshHeartActivateEvent>(OnFleshHeartActivate);
SubscribeLocalEvent<FleshHeartSystem.FleshHeartDestructionEvent>(OnFleshHeartDestruction);
SubscribeLocalEvent<FleshHeartSystem.FleshHeartFinalEvent>(OnFleshHeartFinal);
SubscribeLocalEvent<FleshCultRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
SubscribeLocalEvent<FleshCultRuleComponent, AntagSelectionCompleteEvent>(OnAfterAntagSelectionComplete);
SubscribeLocalEvent<FleshCultSystem.FleshHeartStatusChangeEvent>(OnFleshHeartStatusChange);
}
protected override void Started(EntityUid uid,
FleshCultRuleComponent component,
GameRuleComponent gameRule,
GameRuleStartedEvent args)
{
var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>();
var eligibleQuery = EntityQueryEnumerator<StationEventEligibleComponent, NpcFactionMemberComponent>();
while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member))
{
if (!_npcFaction.IsFactionHostile(component.Faction, (eligibleUid, member)))
continue;
eligible.Add((eligibleUid, eligibleComp, member));
}
if (eligible.Count == 0)
return;
component.TargetStation = RobustRandom.Pick(eligible);
}
protected override void Ended(EntityUid uid, FleshCultRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
foreach (var componentFleshHeart in component.FleshHearts)
{
QueueDel(componentFleshHeart.Key);
}
foreach (var cultist in component.Cultists)
{
RemComp<FleshCultistComponent>(cultist);
}
QueueDel(uid);
}
private void OnObjectivesTextPrepend(EntityUid uid, FleshCultRuleComponent comp, ref ObjectivesTextPrependEvent args)
@ -48,121 +87,37 @@ public sealed class FleshCultRuleSystem : GameRuleSystem<FleshCultRuleComponent>
args.Text += "\n" + Loc.GetString("flesh-cult-round-end-leader", ("name", mind.CharacterName)!, ("username", session!.Name));
}
private void OnFleshHeartActivate(FleshHeartSystem.FleshHeartActivateEvent ev)
private void OnFleshHeartStatusChange(FleshCultSystem.FleshHeartStatusChangeEvent ev)
{
var query = EntityQueryEnumerator<FleshCultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out var fleshCult, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
switch (ev.Status)
{
continue;
}
if (ev.OwningStation == null)
{
return;
}
if (fleshCult.TargetStation == null)
{
return;
}
if (!TryComp(fleshCult.TargetStation, out StationDataComponent? data))
{
return;
}
foreach (var grid in data.Grids)
{
if (grid != ev.OwningStation)
case FleshHeartStatus.Base:
{
continue;
fleshCult.FleshHearts.Add(ev.FleshHeartUid, FleshHeartStatus.Base);
break;
}
fleshCult.FleshHearts.Add(ev.FleshHeardUid, FleshHeartStatus.Active);
fleshCult.FleshHeartActive = true;
return;
}
}
}
private void OnFleshHeartDestruction(FleshHeartSystem.FleshHeartDestructionEvent ev)
{
var query = EntityQueryEnumerator<FleshCultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out var fleshCult, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
{
continue;
}
if (ev.OwningStation == null)
{
return;
}
if (fleshCult.TargetStation == null)
{
return;
}
if (!TryComp(fleshCult.TargetStation, out StationDataComponent? data))
{
return;
}
foreach (var grid in data.Grids)
{
if (grid != ev.OwningStation)
case FleshHeartStatus.Active:
{
continue;
if (fleshCult.FleshHearts.ContainsKey(ev.FleshHeartUid))
fleshCult.FleshHearts[ev.FleshHeartUid] = FleshHeartStatus.Active;
break;
}
if (fleshCult.FleshHearts.ContainsKey(ev.FleshHeardUid))
case FleshHeartStatus.Destruction:
{
fleshCult.FleshHearts[ev.FleshHeardUid] = FleshHeartStatus.Destruction;
if (fleshCult.FleshHearts.ContainsKey(ev.FleshHeartUid))
fleshCult.FleshHearts[ev.FleshHeartUid] = FleshHeartStatus.Destruction;
break;
}
fleshCult.FleshHeartActive = false;
return;
}
}
}
private void OnFleshHeartFinal(FleshHeartSystem.FleshHeartFinalEvent ev)
{
var query = EntityQueryEnumerator<FleshCultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out var fleshCult, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
{
continue;
}
if (ev.OwningStation == null)
{
return;
}
if (fleshCult.TargetStation == null)
{
return;
}
if (!TryComp(fleshCult.TargetStation, out StationDataComponent? data))
{
return;
}
foreach (var grid in data.Grids)
{
if (grid != ev.OwningStation)
case FleshHeartStatus.Final:
{
continue;
if (fleshCult.FleshHearts.ContainsKey(ev.FleshHeartUid))
fleshCult.FleshHearts[ev.FleshHeartUid] = FleshHeartStatus.Final;
_roundEndSystem.EndRound();
break;
}
fleshCult.WinType = FleshCultRuleComponent.WinTypes.FleshHeartFinal;
_roundEndSystem.EndRound();
return;
}
}
}
@ -172,10 +127,32 @@ public sealed class FleshCultRuleSystem : GameRuleSystem<FleshCultRuleComponent>
MakeCultist(args.EntityUid, 15, ent.Comp);
}
public void MakeCultistAdmin(EntityUid target, FixedPoint2 startingPoints)
private void OnAfterAntagSelectionComplete(Entity<FleshCultRuleComponent> ent, ref AntagSelectionCompleteEvent args)
{
var fleshCultRule = StartGameRule();
MakeCultist(target, startingPoints, fleshCultRule);
var leader = GetLeader(args.GameRule);
if (leader == null || !_mindSystem.TryGetMind(leader.Value, out var mindId, out var mind))
return;
ent.Comp.CultistsLeaderMind = mindId;
}
private EntityUid? GetLeader(Entity<AntagSelectionComponent> antagSelection)
{
EntityUid? leader = null;
foreach (var compSelectedMind in antagSelection.Comp.SelectedMinds)
{
if (!TryComp<MindComponent>(compSelectedMind.Item1, out var mindComp))
continue;
foreach (var roleInfo in _roles.MindGetAllRoleInfo((compSelectedMind.Item1, mindComp)))
{
if (roleInfo.Prototype != LeaderAntagProto || mindComp.CurrentEntity == null)
continue;
leader = mindComp.CurrentEntity.Value;
}
}
return leader;
}
public FleshCultRuleComponent StartGameRule()
@ -216,6 +193,8 @@ public sealed class FleshCultRuleSystem : GameRuleSystem<FleshCultRuleComponent>
_store.TryAddCurrency(new Dictionary<string, FixedPoint2>
{ {fleshCultistComponent.StolenCurrencyPrototype, startingPoints} }, mind.OwnedEntity.Value);
fleshCultRule.Cultists.Add(fleshCultist);
return true;
}
@ -282,7 +261,14 @@ public sealed class FleshCultRuleSystem : GameRuleSystem<FleshCultRuleComponent>
}
}
if (component.FleshHeartActive)
var fleshHeartActive = false;
foreach (var fleshCultFleshHeart in component.FleshHearts)
{
if (fleshCultFleshHeart.Value is FleshHeartStatus.Active or FleshHeartStatus.Final)
fleshHeartActive = true;
}
if (fleshHeartActive)
{
result += "\n" + Loc.GetString("flesh-cult-round-end-flesh-heart-succes");
}

View file

@ -101,7 +101,7 @@ public sealed partial class CCVars
/// If roles should be restricted based on time.
/// </summary>
public static readonly CVarDef<bool>
GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED);
GameRoleTimers = CVarDef.Create("game.role_timers", false, CVar.SERVER | CVar.REPLICATED); // Sunrise-Edit
/// <summary>
/// Override default role requirements using a <see cref="JobRequirementOverridePrototype"/>

View file

@ -175,6 +175,7 @@ public partial class ListingData : IEquatable<ListingData>
/// The event that is broadcast when the listing is purchased.
/// </summary>
[DataField]
[NonSerialized] // Sunrise-Edit
public object? ProductEvent;
[DataField]

View file

@ -1,3 +1,4 @@
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Item;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
@ -53,6 +54,10 @@ public sealed partial class EntityWhitelist
[DataField]
public List<ProtoId<TagPrototype>>? Tags;
// Sunrise-Start
public List<ProtoId<SpeciesPrototype>>? Species;
// Sunrise-End
/// <summary>
/// If false, an entity only requires one of these components or tags to pass the whitelist. If true, an
/// entity requires to have ALL of these components and tags to pass.

View file

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Humanoid;
using Content.Shared.Item;
using Content.Shared.Roles;
using Content.Shared.Tag;
@ -100,6 +101,11 @@ public sealed class EntityWhitelistSystem : EntitySystem
: _tag.HasAnyTag(uid, list.Tags);
}
// Sunrise-Start
if (list.Species != null && TryComp<HumanoidAppearanceComponent>(uid, out var appearance))
return list.Species.Contains(appearance.Species);
// Sunrise-End
return list.RequireAll;
}
/// The following are a list of "helper functions" that are basically the same as each other

View file

@ -1,6 +1,4 @@
using Content.Shared.Alert;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.StatusIcon;
using Content.Shared.Store;
@ -17,116 +15,24 @@ public sealed partial class FleshCultistComponent : Component
[ViewVariables(VVAccess.ReadWrite)] public FixedPoint2 Hunger = 100;
[ViewVariables(VVAccess.ReadWrite), DataField("hungerСonsumption")]
public FixedPoint2 HungerСonsumption = -0.05; // 200 hunger in 60 minutes
public FixedPoint2 HungerСonsumption = -0.025; // 100 hunger in 60 minutes
[ViewVariables(VVAccess.ReadWrite), DataField("maxHunger")]
public FixedPoint2 MaxHunger = 200;
public FixedPoint2 MaxHunger = 100;
[ViewVariables(VVAccess.ReadWrite),
DataField("bulletAcidSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string BulletAcidSpawnId = "BulletSplashAcid";
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ActionFleshCultistShop = "FleshCultistShop";
[ViewVariables(VVAccess.ReadWrite), DataField("speciesWhitelist")]
public List<string> SpeciesWhitelist = new()
{
"Human",
"Reptilian",
"Dwarf",
"Vulpkanin",
"Felinid",
"Moth",
"Swine",
"Arachnid",
};
[DataField("adrenalinReagents")] public Solution AdrenalinReagents = new()
{
Contents = { new ReagentQuantity(new ReagentId("Ephedrine", null), 10) }
};
[DataField("healDevourReagents")] public Solution HealDevourReagents = new()
{
Contents =
{
new ReagentQuantity(new ReagentId("Carol", null), 20),
}
};
[DataField("healBloodAbsorbReagents")] public Solution HealBloodAbsorbReagents = new()
{
Contents =
{
new ReagentQuantity(new ReagentId("Carol", null), 1),
}
};
[DataField("bloodAbsorbSound")]
public SoundSpecifier BloodAbsorbSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField("bloodWhitelist")]
public List<string> BloodWhitelist = new()
{
"Blood",
"CopperBlood",
"InsectBlood",
"AmmoniaBlood",
"ZombieBlood"
};
[DataField("devourTime")] public float DevourTime = 10f;
[DataField("devourSound")]
public SoundSpecifier DevourSound = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/devour_flesh_cultist.ogg");
[DataField]
public EntityUid? ActionFleshCultistShopEntity;
[DataField("stolenCurrencyPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<CurrencyPrototype>))]
public string StolenCurrencyPrototype = "StolenMutationPoint";
[ViewVariables(VVAccess.ReadWrite),
DataField("fleshBladeSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string BladeSpawnId = "FleshBlade";
[ViewVariables(VVAccess.ReadWrite),
DataField("fleshFistSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string FistSpawnId = "FleshFist";
[ViewVariables(VVAccess.ReadWrite),
DataField("clawSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ClawSpawnId = "FleshClaw";
[ViewVariables(VVAccess.ReadWrite),
DataField("spikeHandGunSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string SpikeHandGunSpawnId = "FleshSpikeHandGun";
[ViewVariables(VVAccess.ReadWrite),
DataField("armorSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ArmorSpawnId = "ClothingOuterArmorFlesh";
[ViewVariables(VVAccess.ReadWrite),
DataField("heavyArmorSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string HeavyArmorSpawnId = "ClothingOuterHeavyArmorFlesh";
[ViewVariables(VVAccess.ReadWrite),
DataField("spiderLegsSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string SpiderLegsSpawnId = "ClothingFleshSpiderLegs";
[ViewVariables(VVAccess.ReadWrite),
DataField("fleshMutationMobId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string FleshMutationMobId = "MobFleshPudge";
[ViewVariables(VVAccess.ReadWrite), DataField("soundMutation")]
public SoundSpecifier SoundMutation = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/flesh_cultist_mutation.ogg");
[DataField("fleshHeartId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)),
ViewVariables(VVAccess.ReadWrite)]
public string FleshHeartId = "FleshHeart";
[ViewVariables(VVAccess.ReadWrite), DataField("soundThrowWorm")]
public SoundSpecifier? SoundThrowHugger = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/throw_worm.ogg");
[ViewVariables(VVAccess.ReadWrite),
DataField("huggerMobSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string HuggerMobSpawnId = "MobFleshHugger";
public SoundSpecifier BuySuccesSound = new SoundPathSpecifier(
"/Audio/_Sunrise/FleshCult/flesh_cultist_buy_succes.ogg");
@ -139,4 +45,7 @@ public sealed partial class FleshCultistComponent : Component
[DataField]
public ProtoId<AlertPrototype> MutationPointAlert = "MutationPoint";
[DataField]
public SoundSpecifier SoundMutation = new SoundPathSpecifier("/Audio/_Sunrise/FleshCult/flesh_cultist_mutation.ogg");
}

View file

@ -11,36 +11,19 @@ namespace Content.Shared.Flesh
{
[DataField("transformSound")] public SoundSpecifier TransformSound = new SoundCollectionSpecifier("gib");
[ViewVariables(VVAccess.ReadWrite), DataField("speciesWhitelist")]
public List<string> SpeciesWhitelist = new()
{
"Human",
"Reptilian",
"Dwarf",
"Vulpkanin",
"Felinid",
"Moth",
"Swine",
"Arachnid",
};
[ViewVariables(VVAccess.ReadWrite)]
[DataField("entryDelay")]
public float EntryDelay = 10f;
public Container BodyContainer = default!;
[DataField("alertLevelOnActivate")] public string AlertLevelOnActivate = "gamma";
[DataField("alertLevelOnDeactivate")] public string AlertLevelOnDeactivate = "green";
public EntityUid? AmbientAudioStream = default;
[DataField("bodyToFinalStage"), ViewVariables(VVAccess.ReadWrite)]
public int BodyToFinalStage = 3; // default 3
[DataField("timeLiveFinalHeartToWin"), ViewVariables(VVAccess.ReadWrite)]
public int TimeLiveFinalHeartToWin = 900; // default 600
public int TimeLiveFinalHeartToWin = 600; // default 600
[DataField("spawnObjectsFrequency"), ViewVariables(VVAccess.ReadWrite)]
public float SpawnObjectsFrequency = 60;
@ -59,7 +42,7 @@ namespace Content.Shared.Flesh
public Dictionary<string, float> Spawns = new();
[DataField("spawnMobsFrequency"), ViewVariables(VVAccess.ReadWrite)]
public float SpawnMobsFrequency = 120;
public float SpawnMobsFrequency = 100;
[DataField("spawnMobsAmount"), ViewVariables(VVAccess.ReadWrite)]
public int SpawnMobsAmount = 10;
@ -80,7 +63,7 @@ namespace Content.Shared.Flesh
public float FinalStageAccumulator = 0;
[ViewVariables]
public HeartStates State = HeartStates.Base;
public HeartStatus Status = HeartStatus.Base;
public readonly HashSet<EntityUid> EdgeMobs = new();
@ -93,7 +76,7 @@ namespace Content.Shared.Flesh
}
}
public enum HeartStates
public enum HeartStatus
{
Base,
Active,

View file

@ -11,8 +11,9 @@ public enum FleshHeartVisuals : byte
[Serializable, NetSerializable]
public enum FleshHeartStatus
{
Base,
Active,
Disable,
Final,
Destruction
}

View file

@ -1,5 +1,4 @@
using Content.Server.Flesh;
using Content.Shared.Damage;
using Content.Shared.Damage;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@ -7,7 +6,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Shared._Sunrise.FleshCult
{
[Access(typeof(SharedFleshHuggerSystem))]
[RegisterComponent, NetworkedComponent]
public sealed partial class FleshHuggerComponent : Component
{

View file

@ -1,12 +1,12 @@
using Content.Shared.Actions;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared._Sunrise.FleshCult
{
[RegisterComponent]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
public sealed partial class FleshMobComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("soundDeath")]
@ -22,21 +22,16 @@ namespace Content.Shared._Sunrise.FleshCult
[DataField("fleshStatusIcon")]
public ProtoId<FactionIconPrototype> StatusIcon { get; set; } = "FleshFaction";
[DataField]
public TimeSpan PopupCooldown = TimeSpan.FromSeconds(3.0);
[DataField]
[AutoPausedField]
public TimeSpan? NextPopupTime;
[DataField]
public EntityUid? LastAttackedEntity;
public bool IsDeath = false;
}
}
public sealed partial class FleshPudgeThrowFaceHuggerActionEvent : WorldTargetActionEvent
{
}
public sealed partial class FleshPudgeAcidSpitActionEvent : WorldTargetActionEvent
{
}
public sealed partial class FleshPudgeAbsorbBloodPoolActionEvent : InstantActionEvent
{
}

View file

@ -1,52 +1,31 @@
using Content.Shared.Actions;
using Content.Shared.DoAfter;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.FleshCult;
[Serializable, NetSerializable]
public sealed partial class FleshCultistDevourDoAfterEvent : SimpleDoAfterEvent
public sealed partial class FleshCultistInsulatedImmunityMutationEvent : InstantActionEvent
{
}
[Serializable, NetSerializable]
public sealed partial class FleshCultistInfectionDoAfterEvent : SimpleDoAfterEvent
public sealed partial class FleshCultistPressureImmunityMutationEvent : InstantActionEvent
{
}
[Serializable, NetSerializable]
public sealed partial class FleshCultistInsulatedImmunityMutationEvent : SimpleDoAfterEvent
public sealed partial class FleshCultistFlashImmunityMutationEvent : InstantActionEvent
{
}
[Serializable, NetSerializable]
public sealed partial class FleshCultistPressureImmunityMutationEvent : SimpleDoAfterEvent
public sealed partial class FleshCultistRespiratorImmunityMutationEvent : InstantActionEvent
{
}
[Serializable, NetSerializable]
public sealed partial class FleshCultistFlashImmunityMutationEvent : SimpleDoAfterEvent
{
}
[Serializable, NetSerializable]
public sealed partial class FleshCultistRespiratorImmunityMutationEvent : SimpleDoAfterEvent
{
}
[Serializable, NetSerializable]
public sealed partial class FleshCultistColdTempImmunityMutationEvent : SimpleDoAfterEvent
{
}
public sealed partial class FleshCultistAcidSpitActionEvent : WorldTargetActionEvent
public sealed partial class FleshCultistColdTempImmunityMutationEvent : InstantActionEvent
{
}
@ -56,43 +35,6 @@ public sealed partial class FleshCultistShopActionEvent : InstantActionEvent
}
public sealed partial class FleshCultistBladeActionEvent : InstantActionEvent
{
}
public sealed partial class FleshCultistClawActionEvent : InstantActionEvent
{
}
public sealed partial class FleshCultistFistActionEvent : InstantActionEvent
{
}
public sealed partial class FleshCultistSpikeHandGunActionEvent : InstantActionEvent
{
}
public sealed partial class FleshCultistArmorActionEvent : InstantActionEvent
{
}
public sealed partial class FleshCultistHeavyArmorActionEvent : InstantActionEvent
{
}
public sealed partial class FleshCultistSpiderLegsActionEvent : InstantActionEvent
{
}
public sealed partial class FleshCultistAdrenalinActionEvent : InstantActionEvent
{
@ -103,11 +45,6 @@ public sealed partial class FleshCultistCreateFleshHeartActionEvent : InstantAct
}
public sealed partial class FleshCultistThrowHuggerActionEvent : WorldTargetActionEvent
{
}
public sealed partial class FleshCultistAbsorbBloodPoolActionEvent : InstantActionEvent
{
@ -118,5 +55,40 @@ public sealed partial class FleshCultistDevourActionEvent : EntityTargetActionEv
}
public sealed partial class FleshCultistThrowHuggerActionEvent : WorldTargetActionEvent
{
}
public sealed partial class FleshCultistAcidSpitActionEvent : WorldTargetActionEvent
{
}
[Serializable, NetSerializable]
public sealed partial class FleshCultistDevourDoAfterEvent : SimpleDoAfterEvent
{
}
public sealed partial class FleshCultistHandTransformEvent : InstantActionEvent
{
[DataField]
public EntProtoId Prototype;
}
public sealed partial class FleshCultistBodyTransformEvent : InstantActionEvent
{
[DataField]
public EntProtoId Prototype;
[DataField]
public string TargetSlot = string.Empty;
[DataField]
public List<string> CheckSlots = [];
}
public sealed partial class FleshCultistUnlockAbilityEvent : InstantActionEvent
{
[DataField]
public EntProtoId Prototype = string.Empty;
}

View file

@ -1,11 +0,0 @@
namespace Content.Shared._Sunrise.FleshCult;
public abstract class SharedFleshCultistSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
}
}

View file

@ -3,29 +3,30 @@ using Content.Shared.Actions;
using Content.Shared.Inventory.Events;
using Content.Shared.Popups;
namespace Content.Server.Flesh
namespace Content.Shared._Sunrise.FleshCult;
public sealed class SharedFleshHuggerSystem : EntitySystem
{
public class SharedFleshHuggerSystem : EntitySystem
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
SubscribeLocalEvent<FleshHuggerComponent, BeingUnequippedAttemptEvent>(OnUnequipAttempt);
}
public override void Initialize()
{
SubscribeLocalEvent<FleshHuggerComponent, BeingUnequippedAttemptEvent>(OnUnequipAttempt);
}
private void OnUnequipAttempt(EntityUid uid, FleshHuggerComponent component, BeingUnequippedAttemptEvent args)
{
if (args.Slot != "mask")
return;
if (component.EquipedOn != args.Unequipee)
return;
if (HasComp<FleshCultistComponent>(args.Unequipee))
return;
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-try-unequip"),
args.Unequipee, args.Unequipee, PopupType.Large);
args.Cancel();
}
private void OnUnequipAttempt(EntityUid uid, FleshHuggerComponent component, BeingUnequippedAttemptEvent args)
{
if (args.Slot != "mask")
return;
if (component.EquipedOn != args.Unequipee)
return;
if (HasComp<FleshCultistComponent>(args.Unequipee))
return;
_popup.PopupEntity(Loc.GetString("flesh-pudge-throw-hugger-try-unequip"),
args.Unequipee,
args.Unequipee,
PopupType.Large);
args.Cancel();
}
}

View file

@ -1,12 +1,15 @@
using Content.Shared.Flesh;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Robust.Shared.Timing;
namespace Content.Shared._Sunrise.FleshCult;
public abstract class SharedFleshMobSystem : EntitySystem
public sealed class SharedFleshMobSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
@ -15,30 +18,40 @@ public abstract class SharedFleshMobSystem : EntitySystem
SubscribeLocalEvent<FleshMobComponent, AttackAttemptEvent>(OnAttackAttempt);
}
private void OnAttackAttempt(EntityUid uid, FleshMobComponent component, AttackAttemptEvent args)
private void OnAttackAttempt(Entity<FleshMobComponent> fleshMob, ref AttackAttemptEvent args)
{
if (args.Cancelled)
if (args.Cancelled || args.Target == null)
return;
if (HasComp<FleshMobComponent>(args.Target))
{
_popup.PopupCursor(Loc.GetString("flesh-mob-cant-atack-flesh-mob"), uid,
PopupType.LargeCaution);
ShowPopup(fleshMob, args.Target.Value, Loc.GetString("flesh-mob-cant-atack-flesh-mob"));
args.Cancel();
}
if (HasComp<_Sunrise.FleshCult.FleshCultistComponent>(args.Target))
if (HasComp<FleshCultistComponent>(args.Target))
{
_popup.PopupCursor(Loc.GetString("flesh-mob-cant-atack-flesh-cultist"), uid,
PopupType.LargeCaution);
ShowPopup(fleshMob, args.Target.Value, Loc.GetString("flesh-mob-cant-atack-flesh-cultist"));
args.Cancel();
}
if (HasComp<FleshHeartComponent>(args.Target))
{
_popup.PopupCursor(Loc.GetString("flesh-mob-cant-atack-flesh-heart"), uid,
PopupType.LargeCaution);
ShowPopup(fleshMob, args.Target.Value, Loc.GetString("flesh-mob-cant-atack-flesh-heart"));
args.Cancel();
}
}
private void ShowPopup(Entity<FleshMobComponent> user, EntityUid target, string reason)
{
if (target == user.Comp.LastAttackedEntity
&& !(_timing.CurTime > user.Comp.NextPopupTime))
return;
var targetName = Identity.Entity(target, EntityManager);
_popup.PopupCursor(Loc.GetString(reason, ("entity", targetName)), user, PopupType.LargeCaution);
user.Comp.NextPopupTime = _timing.CurTime + user.Comp.PopupCooldown;
user.Comp.LastAttackedEntity = target;
}
}

View file

@ -1,15 +1,9 @@
flesh-cultist-transform-hand-in-blade = Рука { CAPITALIZE($Entity) } превращается в клинок из плоти.
flesh-cultist-transform-hand-in-claw = Рука { CAPITALIZE($Entity) } превращается в клешню из плоти.
flesh-cultist-transform-hand-in-spike-gun = Рука { CAPITALIZE($Entity) } превращается в шипострел из плоти.
flesh-cultist-transform-blade-in-hand = Клинок из плоти { CAPITALIZE($Entity) } превращается обратно в руку.
flesh-cultist-transform-claw-in-hand = Клешня из плоти { CAPITALIZE($Entity) } превращается обратно в руку.
flesh-cultist-transform-spike-gun-in-hand = Шипострел из плоти { CAPITALIZE($Entity) } превращается обратно в руку.
flesh-cultist-transform-armor-on = { CAPITALIZE($Entity) } покрывается броней из плоти.
flesh-cultist-transform-armor-off = { CAPITALIZE($Entity) } убирает свою броню из плоти.
flesh-cultist-transform-spider-legs-on = Ноги { CAPITALIZE($Entity) } превращаются в паучьи лапы из плоти.
flesh-cultist-transform-spider-legs-off = Паучьи лапы { CAPITALIZE($Entity) } превращаются обратно в человеческие ноги.
flesh-cultist-transform-hand-to-mod = Рука { CAPITALIZE( $User ) } превращается в { CAPITALIZE( $Mod ) }.
flesh-cultist-transform-mod-to-hand = { CAPITALIZE( $Mod ) } { CAPITALIZE( $User ) } превращается в обратно в руку.
flesh-cultist-transform-body-add = { CAPITALIZE( $User ) } отращивает { CAPITALIZE( $Mod ) }.
flesh-cultist-transform-body-remove = { CAPITALIZE( $User ) } скрывает { CAPITALIZE( $Mod ) }.
flesh-cultist-transform-user-hand-blocked = Данная рука занята другой модификацией.
flesh-cultist-transform-armor-blocked = Вы не можете использовать паучьи ноги и броню одновременно.
flesh-cultist-transform-conflict = Вы не можете использовать это сейчас.
flesh-cultist-transform-spider-legs-blocked = Вы не можете использовать броню с паучьими лапами.
flesh-cultist-infection-target-critical = Вы не можете заразить умирающее существо.
flesh-cultist-infection-target-dead = Вы не можете заразить мертвое существо.
@ -49,6 +43,8 @@ flesh-cultist-spike-gun-name = Рука шипострел
flesh-cultist-spike-gun-desc = Превращает активную руку в смертоносный шипострел из плоти.
flesh-cultist-armor-name = Броня из плоти
flesh-cultist-armor-desc = Облачает вас в броню из плоти и костей.
flesh-cultist-heavy-armor-name = Тяжелая броня из плоти
flesh-cultist-heavy-armor-desc = Облачает вас в тяжелую броню из плоти и костей.
flesh-cultist-spider-legs-name = Паучий облик
flesh-cultist-spider-legs-desc = Превращает часть вашего тела в паучий вид, давая небольшую защиту и большой прирост скорости.
flesh-cultist-absorb-blood-pool-name = Поглощение лужи крови
@ -83,11 +79,13 @@ flesh-cultist-adrenaline-evolution-desc = Получите возможност
flesh-cultist-throw-hugger-evolution-name = Бросок лицехвата
flesh-cultist-throw-hugger-evolution-desc = Получите возможность создать и кинуть лицехвата из плоти, который будет цеплятся за лица врагов.
flesh-cultist-acid-spit-evolution-name = Кислотный плевок
flesh-cultist-acid-spit-evolution-desc = Получите возможность преваться кислотой в ваших врагов.
flesh-cultist-acid-spit-evolution-desc = Получите возможность плеваться кислотой в ваших врагов.
flesh-cultist-create-flesh-heart-evolution-name = Создание сердца из плоти
flesh-cultist-create-flesh-heart-evolution-desc = Получите возможность создать сердце из плоти.
Его создание и пробуждение - ваша ключевая задача на станции.
Для пробуждения потребуется три тела развитых существ из плоти. После активации будьте готовы защищать его от любой угрозы.
flesh-cultist-absorb-blood-pool-evolution-name = Поглощение лужи крови
flesh-cultist-absorb-blood-pool-evolution-desc = Позволяет поглотить лужи крови для лечения.
flesh-cultist-insulated-immunity-evolution-name = Сопротивление
flesh-cultist-insulated-immunity-evolution-desc = Вы сможете спокойно работать с электричеством не опасаясь поражения напряжением.

View file

@ -17,7 +17,8 @@
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistBlade.png
event: !type:FleshCultistBladeActionEvent
event: !type:FleshCultistHandTransformEvent
prototype: FleshBlade
itemIconStyle: NoItem
useDelay: 10
checkCanInteract: false
@ -55,7 +56,8 @@
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistClaw.png
event: !type:FleshCultistClawActionEvent
event: !type:FleshCultistHandTransformEvent
prototype: FleshClaw
itemIconStyle: NoItem
useDelay: 10
checkCanInteract: false
@ -96,7 +98,8 @@
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistFist.png
event: !type:FleshCultistFistActionEvent
event: !type:FleshCultistHandTransformEvent
prototype: FleshFist
itemIconStyle: NoItem
useDelay: 10
checkCanInteract: false
@ -109,7 +112,8 @@
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistSpikeGun.png
event: !type:FleshCultistSpikeHandGunActionEvent
event: !type:FleshCultistHandTransformEvent
prototype: FleshSpikeHandGun
itemIconStyle: NoItem
useDelay: 10
checkCanInteract: false
@ -122,7 +126,11 @@
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistArmor.png
event: !type:FleshCultistArmorActionEvent
event: !type:FleshCultistBodyTransformEvent
prototype: ClothingOuterArmorFlesh
targetSlot: outerClothing
checkSlots:
- shoes
itemIconStyle: NoItem
useDelay: 30
checkCanInteract: false
@ -135,7 +143,11 @@
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistHeavyArmor.png
event: !type:FleshCultistHeavyArmorActionEvent
event: !type:FleshCultistBodyTransformEvent
prototype: ClothingOuterHeavyArmorFlesh
targetSlot: outerClothing
checkSlots:
- shoes
itemIconStyle: NoItem
useDelay: 30
checkCanInteract: false
@ -148,7 +160,11 @@
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistSpiderLegs.png
event: !type:FleshCultistSpiderLegsActionEvent
event: !type:FleshCultistBodyTransformEvent
prototype: ClothingFleshSpiderLegs
targetSlot: shoes
checkSlots:
- outerClothing
itemIconStyle: NoItem
useDelay: 30
checkCanInteract: false

View file

@ -1,38 +0,0 @@
- type: entity
id: FleshThrowWorm
name: flesh-pudge-throw-hugger-name
description: flesh-pudge-throw-hugger-desc
categories: [ HideSpawnMenu ]
components:
- type: WorldTargetAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshThrowHugger.png
itemIconStyle: NoItem
checkCanAccess: false
range: 200
event: !type:FleshPudgeThrowFaceHuggerActionEvent
useDelay: 240
- type: entity
id: FleshAcidSpit
name: flesh-cultist-acid-spit-name
description: flesh-cultist-acid-spit-desc
categories: [ HideSpawnMenu ]
components:
- type: WorldTargetAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshAcidSpit.png
itemIconStyle: NoItem
checkCanAccess: false
range: 200
event: !type:FleshPudgeAcidSpitActionEvent
useDelay: 60
- type: entity
id: AbsorbBloodPool
name: flesh-cultist-absorb-blood-pool-name
description: flesh-cultist-absorb-blood-pool-desc
categories: [ HideSpawnMenu ]
components:
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistAbsorbBloodPool.png
event: !type:FleshPudgeAbsorbBloodPoolActionEvent
useDelay: 30

View file

@ -7,4 +7,4 @@
- type: InstantAction
icon: _Sunrise/FleshCult/Interface/Actions/flesh_web.png
event: !type:SpiderWebActionEvent
useDelay: 30
useDelay: 15

View file

@ -2,7 +2,10 @@
id: FleshCultistBlade
name: flesh-cultist-blade-evolution-name
description: flesh-cultist-blade-evolution-desc
productAction: FleshCultistBlade
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistBlade.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistBlade
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 45
categories:
@ -90,7 +93,10 @@
id: FleshCultistClaw
name: flesh-cultist-claw-evolution-name
description: flesh-cultist-claw-evolution-desc
productAction: FleshCultistClaw
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistClaw.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistClaw
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 10
categories:
@ -103,7 +109,10 @@
id: FleshCultistFist
name: flesh-cultist-fist-evolution-name
description: flesh-cultist-fist-evolution-desc
productAction: FleshCultistFist
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistFist.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistFist
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 25
categories:
@ -116,7 +125,10 @@
id: FleshCultistSpikeGun
name: flesh-cultist-spike-gun-evolution-name
description: flesh-cultist-spike-gun-evolution-desc
productAction: FleshCultistSpikeGun
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistSpikeGun.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistSpikeGun
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 35
categories:
@ -129,7 +141,10 @@
id: FleshCultistMediumArmor
name: flesh-cultist-medium-armor-evolution-name
description: flesh-cultist-medium-armor-evolution-desc
productAction: FleshCultistArmor
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistArmor.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistArmor
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 25
categories:
@ -142,7 +157,10 @@
id: FleshCultistHeavyArmor
name: flesh-cultist-heavy-armor-evolution-name
description: flesh-cultist-heavy-armor-evolution-desc
productAction: FleshCultistHeavyArmor
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistHeavyArmor.png
raiseProductEventOnUser: true
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistHeavyArmor
cost:
StolenMutationPoint: 45
categories:
@ -155,7 +173,10 @@
id: FleshCultistSpiderlegs
name: flesh-cultist-spider-legs-evolution-name
description: flesh-cultist-spider-legs-evolution-desc
productAction: FleshCultistSpiderlegs
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistSpiderLegs.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistSpiderlegs
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 20
categories:
@ -168,7 +189,10 @@
id: FleshCultistAdrenalin
name: flesh-cultist-adrenaline-evolution-name
description: flesh-cultist-adrenaline-evolution-desc
productAction: FleshCultistAdrenalin
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistAdrenalin.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistAdrenalin
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 30
categories:
@ -181,7 +205,10 @@
id: FleshCultistCreateFleshHeart
name: flesh-cultist-create-flesh-heart-evolution-name
description: flesh-cultist-create-flesh-heart-evolution-desc
productAction: FleshCultistCreateFleshHeart
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistFleshHeart.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistCreateFleshHeart
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 50
categories:
@ -194,7 +221,10 @@
id: FleshCultistThrowHugger
name: flesh-cultist-throw-hugger-evolution-name
description: flesh-cultist-throw-hugger-evolution-desc
productAction: FleshCultistThrowHugger
icon: _Sunrise/FleshCult/Interface/Actions/fleshThrowHugger.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistThrowHugger
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 30
categories:
@ -207,7 +237,10 @@
id: FleshCultistAcidSpit
name: flesh-cultist-acid-spit-evolution-name
description: flesh-cultist-acid-spit-evolution-desc
productAction: FleshCultistAcidSpit
icon: _Sunrise/FleshCult/Interface/Actions/fleshAcidSpit.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistAcidSpit
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 30
categories:
@ -215,3 +248,19 @@
conditions:
- !type:ListingLimitedStockCondition
stock: 1
- type: listing
id: FleshCultistAbsorbBloodPool
name: flesh-cultist-absorb-blood-pool-evolution-name
description: flesh-cultist-absorb-blood-pool-evolution-desc
icon: _Sunrise/FleshCult/Interface/Actions/fleshCultistAbsorbBloodPool.png
productEvent: !type:FleshCultistUnlockAbilityEvent
prototype: FleshCultistAbsorbBloodPool
raiseProductEventOnUser: true
cost:
StolenMutationPoint: 10
categories:
- FleshCultistActiveSkills
conditions:
- !type:ListingLimitedStockCondition
stock: 1

View file

@ -27,6 +27,10 @@
- LFoot
- RLeg
- LLeg
- type: FleshBodyMod
- type: Tag
tags:
- HidesLegs
- type: entity
categories: [ HideSpawnMenu ]
@ -45,13 +49,14 @@
- type: Armor
modifiers:
coefficients:
Blunt: 0.5
Slash: 0.5
Piercing: 0.2
Blunt: 0.6
Slash: 0.6
Piercing: 0.6
Heat: 0.8
- type: ExplosionResistance
damageCoefficient: 0.9
- type: GroupExamine
- type: FleshBodyMod
- type: entity
@ -74,10 +79,10 @@
- type: Armor
modifiers:
coefficients:
Blunt: 0.6
Slash: 0.6
Piercing: 0.3
Heat: 0.9
Blunt: 0.4
Slash: 0.4
Piercing: 0.4
Heat: 0.6
- type: ClothingSpeedModifier
walkModifier: 0.8
sprintModifier: 0.8
@ -88,6 +93,7 @@
deleteOnDrop: true
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetHeavyArmorFlesh
- type: FleshBodyMod
- type: entity

View file

@ -47,8 +47,9 @@
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: PointLight
radius: 10
energy: 1
radius: 25
energy: 10
enabled: false
castShadows: true
color: "#87031f"
- type: FleshHeart
@ -58,9 +59,9 @@
types:
Slash: 700
spawns:
MobFleshSpider: 0.20
MobFleshSpider: 0.30
MobFleshPudge: 0.20
MobFleshBall: 0.30
MobFleshBall: 0.20
MobFleshBat: 0.30
spawnMobsAmount: 3
spawnMobsAmount: 5
spawnMobsFrequency: 180

View file

@ -5,10 +5,12 @@
- fs
name: flesh-cult-title
description: flesh-cult-description
showInVote: false # Пока не закончим другие режимы.
showInVote: true
hide: true
rules:
- FleshCult
- LiteSubGamemodesRule
- BasicStationEventScheduler
- MeteorSwarmScheduler
- SpaceTrafficControlEventScheduler
- BasicRoundstartVariation

View file

@ -21,7 +21,7 @@
- Prying
useSound: /Audio/Effects/gib2.ogg
- type: Prying
speedModifier: 2.5
speedModifier: 5
pryPowered: true
force: true
- type: MeleeWeapon

View file

@ -135,6 +135,10 @@
collection: FootstepSpiderLegs
params:
volume: 10
- type: Hands
showInHands: false
disableExplosionRecursion: true
canBeStripped: false
- type: entity
parent: BaseMobFleshCult
@ -156,28 +160,18 @@
- type: Prying
pryPowered: true
force: true
speedModifier: 1.5
speedModifier: 7
useSound:
path: /Audio/Items/crowbar.ogg
- type: Butcherable
spawned:
- id: FoodMeat
amount: 5
- type: FleshPudge
bloodAbsorbSound:
path: /Audio/Effects/Fluids/splat.ogg
healBloodAbsorbReagents:
reagents:
- data: null
ReagentId: Carol
Quantity: 1
bulletAcidSpawnId: BulletSplashAcid
faceHuggerMobSpawnId: MobFleshHugger
soundThrowWorm: !type:SoundPathSpecifier
path: /Audio/_Sunrise/FleshCult/throw_worm.ogg
actionAbsorbBloodPool: AbsorbBloodPool
actionAcidSpit: FleshAcidSpit
actionThrowWorm: FleshThrowWorm
- type: FleshAbilities
startingActions:
- FleshCultistThrowHugger
- FleshCultistAcidSpit
- FleshCultistAbsorbBloodPool
- type: Vocal
sounds:
Unsexed: FleshPudgeEmote
@ -240,7 +234,7 @@
fix1:
shape:
!type:PhysShapeCircle
radius: 0.40
radius: 0.35
density: 800
mask:
- MobMask
@ -248,6 +242,10 @@
- MobLayer
- type: Puller
needsHands: false
- type: Hands
showInHands: false
disableExplosionRecursion: true
canBeStripped: false
- type: entity

View file

@ -8,8 +8,8 @@
issuer: flesh-cult
- type: RoleRequirement
roles:
components:
- FleshCultistRole
mindRoles:
- FleshCultistRole
- type: entity
categories: [ HideSpawnMenu ]

View file

@ -23,6 +23,19 @@
- type: NpcFactionMember
factions:
- FleshHuman
whitelist:
species:
- Human
- Reptilian
- Dwarf
- Vulpkanin
- Felinid
- Moth
- Arachnid
- Swine
- Demon
- Vox
- Tajaran
blacklist:
components:
- AntagImmune