Files
wwdpublic/Content.Server/Flash/FlashSystem.cs
FoxxoTrystan d9a04690a4 Vulpkanin Update (#715)
![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:
https://github.com/Simple-Station/Einstein-Engines/issues/711

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

For Vulpkanin Stats changed please check this PR:
https://github.com/Simple-Station/Einstein-Engines/pull/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 `🆑` to change the name that appears
in the changelog (ex: `🆑 Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

🆑 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 <45297731+FoxxoTrystan@users.noreply.github.com>
Co-authored-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com>
2024-08-15 03:11:05 -04:00

250 lines
9.4 KiB
C#

using System.Linq;
using Content.Server.Flash.Components;
using Content.Shared.Flash.Components;
using Content.Server.Light.EntitySystems;
using Content.Server.Popups;
using Content.Server.Stunnable;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Flash;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Physics;
using Content.Shared.Tag;
using Content.Shared.Weapons.Melee.Events;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
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
{
internal sealed class FlashSystem : SharedFlashSystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[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()
{
base.Initialize();
SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
// ran before toggling light for extra-bright lantern
SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new []{ typeof(HandheldLightSystem) });
SubscribeLocalEvent<InventoryComponent, FlashAttemptEvent>(OnInventoryFlashAttempt);
SubscribeLocalEvent<FlashImmunityComponent, FlashAttemptEvent>(OnFlashImmunityFlashAttempt);
SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
SubscribeLocalEvent<TemporaryBlindnessComponent, FlashAttemptEvent>(OnTemporaryBlindnessFlashAttempt);
}
private void OnFlashMeleeHit(EntityUid uid, FlashComponent comp, MeleeHitEvent args)
{
if (!args.IsHit ||
!args.HitEntities.Any() ||
!UseFlash(uid, comp, args.User))
{
return;
}
args.Handled = true;
foreach (var e in args.HitEntities)
{
Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true);
}
}
private void OnFlashUseInHand(EntityUid uid, FlashComponent comp, UseInHandEvent args)
{
if (args.Handled || !UseFlash(uid, comp, args.User))
return;
args.Handled = true;
FlashArea(uid, args.User, comp.Range, comp.AoeFlashDuration, comp.SlowTo, true);
}
private bool UseFlash(EntityUid uid, FlashComponent comp, EntityUid user)
{
if (comp.Flashing)
return false;
TryComp<LimitedChargesComponent>(uid, out var charges);
if (_charges.IsEmpty(uid, charges))
return false;
_charges.UseCharge(uid, charges);
_audio.PlayPvs(comp.Sound, uid);
comp.Flashing = true;
_appearance.SetData(uid, FlashVisuals.Flashing, true);
if (_charges.IsEmpty(uid, charges))
{
_appearance.SetData(uid, FlashVisuals.Burnt, true);
_tag.AddTag(uid, "Trash");
_popup.PopupEntity(Loc.GetString("flash-component-becomes-empty"), user);
}
uid.SpawnTimer(400, () =>
{
_appearance.SetData(uid, FlashVisuals.Flashing, false);
comp.Flashing = false;
});
return true;
}
public void Flash(EntityUid target,
EntityUid? user,
EntityUid? used,
float flashDuration,
float slowTo,
bool displayPopup = true,
FlashableComponent? flashable = null,
bool melee = false)
{
if (!Resolve(target, ref flashable, false))
return;
var attempt = new FlashAttemptEvent(target, user, used);
RaiseLocalEvent(target, attempt, true);
if (attempt.Cancelled)
return;
if (melee)
{
var ev = new AfterFlashedEvent(target, user, used);
if (user != null)
RaiseLocalEvent(user.Value, ref ev);
if (used != null)
RaiseLocalEvent(used.Value, ref ev);
}
flashDuration *= flashable.DurationMultiplier;
flashable.LastFlash = _timing.CurTime;
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);
if (displayPopup && user != null && target != user && Exists(user.Value))
{
_popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you",
("user", Identity.Entity(user.Value, EntityManager))), target, target);
}
}
public void FlashArea(EntityUid source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, SoundSpecifier? sound = null)
{
var transform = EntityManager.GetComponent<TransformComponent>(source);
var mapPosition = _transform.GetMapCoordinates(transform);
var flashableQuery = GetEntityQuery<FlashableComponent>();
foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range))
{
if (!flashableQuery.TryGetComponent(entity, out var flashable))
continue;
// Check for unobstructed entities while ignoring the mobs with flashable components.
if (!_interaction.InRangeUnobstructed(entity, mapPosition, range, flashable.CollisionGroup, (e) => e == source))
continue;
// They shouldn't have flash removed in between right?
Flash(entity, user, source, duration, slowTo, displayPopup, flashableQuery.GetComponent(entity));
}
if (sound != null)
{
_audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f));
}
}
private void OnInventoryFlashAttempt(EntityUid uid, InventoryComponent component, FlashAttemptEvent args)
{
foreach (var slot in new[] { "head", "eyes", "mask" })
{
if (args.Cancelled)
break;
if (_inventory.TryGetSlotEntity(uid, slot, out var item, component))
RaiseLocalEvent(item.Value, args, true);
}
}
private void OnFlashImmunityFlashAttempt(EntityUid uid, FlashImmunityComponent component, FlashAttemptEvent args)
{
if(component.Enabled)
args.Cancel();
}
private void OnPermanentBlindnessFlashAttempt(EntityUid uid, PermanentBlindnessComponent component, FlashAttemptEvent args)
{
args.Cancel();
}
private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args)
{
args.Cancel();
}
}
public sealed class FlashAttemptEvent : CancellableEntityEventArgs
{
public readonly EntityUid Target;
public readonly EntityUid? User;
public readonly EntityUid? Used;
public FlashAttemptEvent(EntityUid target, EntityUid? user, EntityUid? used)
{
Target = target;
User = user;
Used = used;
}
}
/// <summary>
/// Called after a flash is used via melee on another person to check for rev conversion.
/// Raised on the user of the flash, the target hit by the flash, and the flash used.
/// </summary>
[ByRefEvent]
public readonly struct AfterFlashedEvent
{
public readonly EntityUid Target;
public readonly EntityUid? User;
public readonly EntityUid? Used;
public AfterFlashedEvent(EntityUid target, EntityUid? user, EntityUid? used)
{
Target = target;
User = user;
Used = used;
}
}
}