Skip to content

Commit

Permalink
Vulpkanin Update (#715)
Browse files Browse the repository at this point in the history
![WarningTrystan](https://github.com/user-attachments/assets/958f868b-11b9-48f0-80ab-13d9ff243f06)
<!--
This is a semi-strict format, you can add/remove sections as needed but
the order/format should be kept the same
Remove these comments before submitting
-->

<!--
Explain this PR in as much detail as applicable

Some example prompts to consider:
How might this affect the game? The codebase?
What might be some alternatives to this?
How/Who does this benefit/hurt [the game/codebase]?
-->

This PR is the rework regarding the unique feature for vulpkanins, this
is mostly due to the "FORCED" Issue by VM:
Simple-Station/Einstein-Engines#711

This PR will mostly add the new features that will mostly make
Vulpkanins unique.

For Vulpkanin Stats changed please check this PR:
Simple-Station/Einstein-Engines#713

- Flash Damge: Flashable has 2 new variables "EyeDamageChance" (Float)
and "EyeDamage" (int), those are default to 0 but if changed could give
a chance from 0 to 1 to give EyeDamage from the EyeDamage Value, this is
not fixed to vulpkanin and can be added to anything with the "Flashable"
Component.
- ScentTracker: Add a new Forensics type "Scent", scent will spread on
everything you wear and only the ent with the "ScentTrackerSystem" can
track a scent, tracking a scent will leave an effect on those who has or
the item with the scent, scent can be cleaned away with soap or you can
compleatly generate a new scent of a person by cleaning yourself, note:
someone with a scent does not mean his the one making that scent, they
may just have an item with the scent in their bag!
- Vulpkanins Screams: I have 5 Fox Screams that need to be edited and
need to be added in-game for vulpkanins with a lisence, just need to
have the time to do it and this PR seem the perfect place for it.

---

<!--
A list of everything you have to do before this PR is "complete"
You probably won't have to complete everything before merging but it's
good to leave future references
-->

- [x] Flash Damage
- [x] Scent System
- [x] ScentTracker System
- [x] Vulpkanin Screams

---

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

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

![image](https://github.com/user-attachments/assets/3bd60c0f-2528-4be7-a52d-defe2990c475)

![image](https://github.com/user-attachments/assets/6756b6af-3f76-4faa-9fbd-c35964b267b3)

![image](https://github.com/user-attachments/assets/b4ff84a2-64eb-4985-876b-d3e93fc8bd12)

![image](https://github.com/user-attachments/assets/dd4b47ea-ae39-44c3-b5a2-27ee68703857)

</p>
</details>

---

<!--
You can add an author after the `:cl:` to change the name that appears
in the changelog (ex: `:cl: Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

:cl: FoxxoTrystan
- add: Forensics Scent Type and Vulpkanins can now track scents, better
keep yourself clean!
- tweak: Vulpkanins eyes are sensetive, please dont flash them with
lights as this could damage them.
- add: Vulpkanins now has their own screams!

---------

Signed-off-by: FoxxoTrystan <[email protected]>
Co-authored-by: VMSolidus <[email protected]>
Co-authored-by: Danger Revolution! <[email protected]>
  • Loading branch information
3 people authored and gluesniffler committed Sep 7, 2024
1 parent afbea0f commit 6a1ef1a
Show file tree
Hide file tree
Showing 22 changed files with 450 additions and 31 deletions.
31 changes: 31 additions & 0 deletions Content.Client/Forensics/ScentTrackerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Content.Shared.Forensics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Client.Player;

namespace Content.Client.Forensics
{
public sealed class ScentTrackerSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;

public override void Update(float frameTime)
{
base.Update(frameTime);

var query = AllEntityQuery<ForensicsComponent>();
while (query.MoveNext(out var uid, out var comp))
if (TryComp<ScentTrackerComponent>(_playerManager.LocalEntity, out var scentcomp)
&& scentcomp.Scent != string.Empty
&& scentcomp.Scent == comp.Scent
&& _timing.CurTime > comp.TargetTime)
{
comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(1.0f);
Spawn("ScentTrackEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System.Numerics;
using Content.Server.Forensics;
using Content.Shared.Forensics;
using Content.Server.Stack;
using Content.Shared.Destructible.Thresholds;
using Content.Shared.Prototypes;
using Content.Shared.Stacks;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
using Content.Server.Administration.Commands;

namespace Content.Server.Destructible.Thresholds.Behaviors
{
Expand Down Expand Up @@ -88,6 +90,7 @@ public void TransferForensics(EntityUid spawned, DestructibleSystem system, Enti

if (!system.Random.Prob(0.4f))
return;

comp.Fingerprints = forensicsComponent.Fingerprints;
comp.Fibers = forensicsComponent.Fibers;
}
Expand Down
29 changes: 16 additions & 13 deletions Content.Server/Flash/FlashSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using Content.Server.Light.EntitySystems;
using Content.Server.Popups;
using Content.Server.Stunnable;
using Content.Server.Traits.Assorted.Components;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Eye.Blinding.Systems;
using Content.Shared.Flash;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
Expand All @@ -21,7 +23,6 @@
using Robust.Shared.Audio;
using Robust.Shared.Random;
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
using Content.Server.Traits.Assorted.Components;

namespace Content.Server.Flash
{
Expand All @@ -39,6 +40,7 @@ internal sealed class FlashSystem : SharedFlashSystem
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly BlindableSystem _blindingSystem = default!;

public override void Initialize()
{
Expand All @@ -65,10 +67,7 @@ private void OnFlashMeleeHit(EntityUid uid, FlashComponent comp, MeleeHitEvent a
args.Handled = true;
foreach (var e in args.HitEntities)
{
var flashDuration = (float)comp.FlashDuration;
if (TryComp<FlashDurationModifierComponent>(e, out var modifier))
flashDuration *= modifier.DurationMultiplier;
Flash(e, args.User, uid, flashDuration, comp.SlowTo, melee: true, stunDuration: comp.MeleeStunDuration);
Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true, stunDuration: comp.MeleeStunDuration);
}
}

Expand Down Expand Up @@ -122,24 +121,31 @@ public void Flash(EntityUid target,
{
var attempt = new FlashAttemptEvent(target, user, used);
RaiseLocalEvent(target, attempt, true);

var modifierValue = TryComp<FlashPropertyModifierComponent>(target, out var modifier) ? modifier.DurationMultiplier : 1f;
Logger.Debug($"Modifier Value {modifierValue}");
if (attempt.Cancelled)
return;

// don't paralyze, slowdown or convert to rev if the target is immune to flashes
if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, TimeSpan.FromSeconds(flashDuration / 1000f), true))
if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, TimeSpan.FromSeconds(flashDuration * modifierValue / 1000f), true))
return;

if (stunDuration != null)
{
_stun.TryParalyze(target, stunDuration.Value, true);
_stun.TryParalyze(target, stunDuration.Value * modifierValue, true);
}
else
{
_stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration / 1000f), true,
_stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration * modifierValue / 1000f), true,
slowTo, slowTo);
}

if (TryComp<BlindableComponent>(target, out var blindable)
&& !blindable.IsBlind
&& modifier != null
&& _random.Prob(modifier.EyeDamageChance))
_blindingSystem.AdjustEyeDamage((target, blindable), modifier.EyeDamage);

if (displayPopup && user != null && target != user && Exists(user.Value))
{
_popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you",
Expand Down Expand Up @@ -178,10 +184,7 @@ public void FlashArea(Entity<FlashComponent?> source, EntityUid? user, float ran
continue;

// They shouldn't have flash removed in between right?
var flashDuration = duration;
if(TryComp<FlashDurationModifierComponent>(entity, out var modifier))
flashDuration *= modifier.DurationMultiplier;
Flash(entity, user, source, flashDuration, slowTo, displayPopup);
Flash(entity, user, source, duration, slowTo, displayPopup);
}

_audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f));
Expand Down
11 changes: 11 additions & 0 deletions Content.Server/Forensics/Components/ScentComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Content.Server.Forensics;

/// <summary>
/// This component is for mobs that have a Scent.
/// </summary>
[RegisterComponent]
public sealed partial class ScentComponent : Component
{
[DataField]
public string Scent = String.Empty;
}
103 changes: 95 additions & 8 deletions Content.Server/Forensics/Systems/ForensicsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Robust.Shared.Random;
using Content.Shared.Verbs;
using Robust.Shared.Utility;
using Content.Shared.Inventory.Events;

namespace Content.Server.Forensics
{
Expand All @@ -28,14 +29,16 @@ public sealed class ForensicsSystem : EntitySystem
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;

public override void Initialize()
{
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
SubscribeLocalEvent<FiberComponent, MapInitEvent>(OnFiberInit); // DeltaV #1455 - unique glove fibers
SubscribeLocalEvent<ScentComponent, DidEquipEvent>(OnEquip);
SubscribeLocalEvent<FiberComponent, MapInitEvent>(OnFiberInit);
SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit);
SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit);
SubscribeLocalEvent<ScentComponent, MapInitEvent>(OnScentInit);

SubscribeLocalEvent<ForensicsComponent, BeingGibbedEvent>(OnBeingGibbed);
SubscribeLocalEvent<ForensicsComponent, MeleeHitEvent>(OnMeleeHit);
Expand Down Expand Up @@ -65,6 +68,11 @@ private void OnInteract(EntityUid uid, FingerprintComponent component, ContactIn
ApplyEvidence(uid, args.Other);
}

private void OnEquip(EntityUid uid, ScentComponent component, DidEquipEvent args)
{
ApplyScent(uid, args.Equipment);
}

// DeltaV #1455 - unique glove fibers
private void OnFiberInit(EntityUid uid, FiberComponent component, MapInitEvent args)
{
Expand All @@ -88,6 +96,16 @@ private void OnDNAInit(EntityUid uid, DnaComponent component, MapInitEvent args)
}
}

private void OnScentInit(EntityUid uid, ScentComponent component, MapInitEvent args)
{
component.Scent = GenerateFingerprint(length: 5);

var updatecomp = EnsureComp<ForensicsComponent>(uid);
updatecomp.Scent = component.Scent;

Dirty(uid, updatecomp);
}

private void OnBeingGibbed(EntityUid uid, ForensicsComponent component, BeingGibbedEvent args)
{
string dna = Loc.GetString("forensics-dna-unknown");
Expand All @@ -100,6 +118,7 @@ private void OnBeingGibbed(EntityUid uid, ForensicsComponent component, BeingGib
var partComp = EnsureComp<ForensicsComponent>(part);
partComp.DNAs.Add(dna);
partComp.CanDnaBeCleaned = false;
Dirty(part, partComp);
}
}

Expand All @@ -115,11 +134,13 @@ private void OnMeleeHit(EntityUid uid, ForensicsComponent component, MeleeHitEve
component.DNAs.Add(hitEntityComp.DNA);
}
}
Dirty(uid, component);
}

private void OnRehydrated(Entity<ForensicsComponent> ent, ref GotRehydratedEvent args)
{
CopyForensicsFrom(ent.Comp, args.Target);
Dirty(args.Target, ent.Comp);
}

/// <summary>
Expand Down Expand Up @@ -167,7 +188,7 @@ public List<string> GetSolutionsDNA(Solution soln)
{
if (data is DnaData)
{
list.Add(((DnaData) data).DNA);
list.Add(((DnaData)data).DNA);
}
}
}
Expand Down Expand Up @@ -220,10 +241,10 @@ public bool TryStartCleaning(Entity<CleansForensicsComponent> cleanForensicsEnti

var totalPrintsAndFibers = forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count;
var hasRemovableDNA = forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned;
var cleanDelay = cleanForensicsEntity.Comp.CleanDelay;

if (hasRemovableDNA || totalPrintsAndFibers > 0)
{
var cleanDelay = cleanForensicsEntity.Comp.CleanDelay;
var doAfterArgs = new DoAfterArgs(EntityManager, user, cleanDelay, new CleanForensicsDoAfterEvent(), cleanForensicsEntity, target: target, used: cleanForensicsEntity)
{
NeedHand = true,
Expand All @@ -239,12 +260,31 @@ public bool TryStartCleaning(Entity<CleansForensicsComponent> cleanForensicsEnti

return true;
}

if (TryComp<ScentComponent>(target, out var scentComp))
{
cleanDelay += 30;
var doAfterArgs = new DoAfterArgs(EntityManager, user, cleanDelay, new CleanForensicsDoAfterEvent(), cleanForensicsEntity, target: target, used: cleanForensicsEntity)
{
BreakOnHandChange = true,
NeedHand = true,
BreakOnDamage = true,
BreakOnTargetMove = true,
MovementThreshold = 0.01f,
DistanceThreshold = 1.5f,
};

_doAfterSystem.TryStartDoAfter(doAfterArgs);

_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", target)), user, user);

return true;
}
else
{
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", target)), user, user, PopupType.MediumCaution);
return false;
}

}

private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component, CleanForensicsDoAfterEvent args)
Expand All @@ -257,6 +297,7 @@ private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component

targetComp.Fibers = new();
targetComp.Fingerprints = new();
targetComp.Scent = String.Empty;

if (targetComp.CanDnaBeCleaned)
targetComp.DNAs = new();
Expand All @@ -267,6 +308,30 @@ private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component

if (TryComp<ResidueComponent>(args.Used, out var residue))
targetComp.Residues.Add(string.IsNullOrEmpty(residue.ResidueColor) ? Loc.GetString("forensic-residue", ("adjective", residue.ResidueAdjective)) : Loc.GetString("forensic-residue-colored", ("color", residue.ResidueColor), ("adjective", residue.ResidueAdjective)));

// If the ent has a Scent Component, we compleatly generate a new one and apply the new scent to all currently weared items.
if (TryComp<ScentComponent>(args.Target, out var scentComp))
{
var generatedscent = GenerateFingerprint(length: 5);
scentComp.Scent = generatedscent;
targetComp.Scent = generatedscent;

if (args.Target is { Valid: true } target
&& _inventory.TryGetSlots(target, out var slotDefinitions))
foreach (var slot in slotDefinitions)
{
if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt))
continue;

EnsureComp<ForensicsComponent>(slotEnt.Value, out var recipientComp);
recipientComp.Scent = generatedscent;

Dirty(slotEnt.Value, recipientComp);
}
}

if (args.Target is { Valid: true } targetuid)
Dirty(targetuid, targetComp);
}

public string GenerateFingerprint(int length = 16) // DeltaV #1455 - allow changing the length of the fingerprint hash
Expand All @@ -279,14 +344,14 @@ public string GenerateFingerprint(int length = 16) // DeltaV #1455 - allow chang
public string GenerateDNA()
{
var letters = new[] { "A", "C", "G", "T" };
var DNA = string.Empty;
var dna = string.Empty;

for (var i = 0; i < 16; i++)
{
DNA += letters[_random.Next(letters.Length)];
dna += letters[_random.Next(letters.Length)];
}

return DNA;
return dna;
}

private void ApplyEvidence(EntityUid user, EntityUid target)
Expand All @@ -308,17 +373,37 @@ private void ApplyEvidence(EntityUid user, EntityUid target)
// End of DeltaV code

if (HasComp<FingerprintMaskComponent>(gloves))
{
Dirty(target, component);
return;
}
}
if (TryComp<FingerprintComponent>(user, out var fingerprint))
{
component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
Dirty(target, component);
}
}

private void ApplyScent(EntityUid user, EntityUid target)
{
if (HasComp<ScentComponent>(target))
return;

var component = EnsureComp<ForensicsComponent>(target);
if (TryComp<ScentComponent>(user, out var scent))
component.Scent = scent.Scent;

Dirty(target, component);
}

private void OnTransferDnaEvent(EntityUid uid, DnaComponent component, ref TransferDnaEvent args)
{
var recipientComp = EnsureComp<ForensicsComponent>(args.Recipient);
recipientComp.DNAs.Add(component.DNA);
recipientComp.CanDnaBeCleaned = args.CanDnaBeCleaned;

Dirty(args.Recipient, recipientComp);
}

#region Public API
Expand All @@ -336,6 +421,8 @@ public void TransferDna(EntityUid recipient, EntityUid donor, bool canDnaBeClean
EnsureComp<ForensicsComponent>(recipient, out var recipientComp);
recipientComp.DNAs.Add(donorComp.DNA);
recipientComp.CanDnaBeCleaned = canDnaBeCleaned;

Dirty(recipient, recipientComp);
}
}

Expand Down
Loading

0 comments on commit 6a1ef1a

Please sign in to comment.