Skip to content

Commit

Permalink
Vulpkanin Update (Simple-Station#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
-->

# Description

<!--
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#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#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.

---

# TODO

<!--
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>

---

# Changelog

<!--
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 tim-mcqueen-ttec committed Aug 18, 2024
1 parent 441a8f9 commit cef7b20
Show file tree
Hide file tree
Showing 23 changed files with 393 additions and 26 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,11 +1,12 @@
using System.Numerics;
using Content.Server.Forensics;
using Content.Shared.Forensics;
using Content.Server.Stack;
using Content.Shared.Prototypes;
using Content.Shared.Stacks;
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 @@ -85,6 +86,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
9 changes: 9 additions & 0 deletions Content.Server/Flash/FlashSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
using Robust.Shared.Timing;
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
using Content.Shared.Traits.Assorted.Components;
using Robust.Shared.Random;
using Content.Shared.Eye.Blinding.Systems;

namespace Content.Server.Flash
{
Expand All @@ -37,6 +39,8 @@ internal sealed class FlashSystem : SharedFlashSystem
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly BlindableSystem _blindingSystem = default!;

public override void Initialize()
{
Expand Down Expand Up @@ -139,6 +143,11 @@ public void Flash(EntityUid target,
flashable.Duration = flashDuration / 1000f; // TODO: Make this sane...
Dirty(target, flashable);

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

_stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true,
slowTo, slowTo);

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;
}
109 changes: 101 additions & 8 deletions Content.Server/Forensics/Systems/ForensicsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Content.Shared.Inventory;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Random;
using Content.Shared.Inventory.Events;

namespace Content.Server.Forensics
{
Expand All @@ -23,9 +24,11 @@ public sealed class ForensicsSystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
SubscribeLocalEvent<ScentComponent, DidEquipEvent>(OnEquip);
SubscribeLocalEvent<FiberComponent, MapInitEvent>(OnFiberInit);
SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit);
SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit);
SubscribeLocalEvent<ScentComponent, MapInitEvent>(OnScentInit);

SubscribeLocalEvent<DnaComponent, BeingGibbedEvent>(OnBeingGibbed);
SubscribeLocalEvent<ForensicsComponent, MeleeHitEvent>(OnMeleeHit);
Expand All @@ -40,6 +43,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);
}

private void OnFiberInit(EntityUid uid, FiberComponent component, MapInitEvent args)
{
component.Fiberprint = GenerateFingerprint(length: 7);
Expand All @@ -53,35 +61,49 @@ private void OnFingerprintInit(EntityUid uid, FingerprintComponent component, Ma
private void OnDNAInit(EntityUid uid, DnaComponent component, MapInitEvent args)
{
component.DNA = GenerateDNA();

}

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, DnaComponent component, BeingGibbedEvent args)
{
foreach(EntityUid part in args.GibbedParts)
foreach (EntityUid part in args.GibbedParts)
{
var partComp = EnsureComp<ForensicsComponent>(part);
partComp.DNAs.Add(component.DNA);
partComp.CanDnaBeCleaned = false;
Dirty(part, partComp);
}
}

private void OnMeleeHit(EntityUid uid, ForensicsComponent component, MeleeHitEvent args)
{
if((args.BaseDamage.DamageDict.TryGetValue("Blunt", out var bluntDamage) && bluntDamage.Value > 0) ||
if ((args.BaseDamage.DamageDict.TryGetValue("Blunt", out var bluntDamage) && bluntDamage.Value > 0) ||
(args.BaseDamage.DamageDict.TryGetValue("Slash", out var slashDamage) && slashDamage.Value > 0) ||
(args.BaseDamage.DamageDict.TryGetValue("Piercing", out var pierceDamage) && pierceDamage.Value > 0))
{
foreach(EntityUid hitEntity in args.HitEntities)
foreach (EntityUid hitEntity in args.HitEntities)
{
if(TryComp<DnaComponent>(hitEntity, out var hitEntityComp))
if (TryComp<DnaComponent>(hitEntity, out var hitEntityComp))
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 @@ -112,19 +134,43 @@ private void OnAfterInteract(EntityUid uid, CleansForensicsComponent component,
if (args.Handled)
return;

if (!TryComp<ForensicsComponent>(args.Target, out var forensicsComp))
if (TryComp<ForensicsComponent>(args.Target, out var forensicsComp)
&& forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned
&& forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count > 0
&& forensicsComp.Scent != string.Empty)
{
var cleanDelay = component.CleanDelay;
if (HasComp<ScentComponent>(args.Target))
cleanDelay += 30;

var doAfterArgs = new DoAfterArgs(EntityManager, args.User, cleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used)
{
BreakOnHandChange = true,
NeedHand = true,
BreakOnDamage = true,
BreakOnTargetMove = true,
MovementThreshold = 0.01f,
DistanceThreshold = forensicsComp.CleanDistance,
};

_doAfterSystem.TryStartDoAfter(doAfterArgs);
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", args.Target)), args.User, args.User);

args.Handled = true;
return;
}

if((forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned) || (forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count > 0))
if (TryComp<ScentComponent>(args.Target, out var scentComp))
{
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.CleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used)
var cleanDelay = component.CleanDelay + 30;
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, cleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used)
{
BreakOnHandChange = true,
NeedHand = true,
BreakOnDamage = true,
BreakOnTargetMove = true,
MovementThreshold = 0.01f,
DistanceThreshold = forensicsComp.CleanDistance,
DistanceThreshold = 1.5f,
};

_doAfterSystem.TryStartDoAfter(doAfterArgs);
Expand All @@ -144,6 +190,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 @@ -154,6 +201,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)
Expand Down Expand Up @@ -193,17 +264,37 @@ private void ApplyEvidence(EntityUid user, EntityUid target)
}

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 @@ -221,6 +312,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 cef7b20

Please sign in to comment.