mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-24 00:58:01 +03:00
* Automatic changelog update * Make NPC names proper nouns & fix some genders (#20534) * Proper names & genders * Uppercase proper names * Make Smile female * fix bingus wrinkly head (#20531) * rouny meat and steak (#20526) * lost friendship flavor * add rouny steak * rouny special meat * rouny meat textures --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Wearable Wet Floor Sign and 'Janitorial Suicide Vest' (#20311) * Explosive wet floor sign & janitorial suicide vest * fix attributions * Remove name & desc from explosive wet floor sign * Make wet floor sign chameleonable * Automatic changelog update * Minor slippery stuff (#20535) * Update submodule to 162.2.0 (#20570) * Predicted armor (#20560) * clean up some lines in smile the slime prototype (#20552) * Revert "Use full file path for temp replays (#19002)" (#20545) * Add EyesGlasses into ClothesMate (#20523) * Automatic changelog update * Fix Punpun crew monitor sensor (#20484) * Automatic changelog update * EasyPry airlocks for arrivals. Now also prying refactor I guess (#19394) Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Make arcades hackable again (#20555) * Automatic changelog update * Health alert tweaks (#20557) * Automatic changelog update * Add active and ended game rule components, generic TryRoundStartAttempt and minPlayers field (#20564) * Improve active game rule querying, add generic try round start attempt method, move minPlayers to GameRuleComponent * Nukeops todo and cleanup * Remove Active field * Add EndedGameRuleComponent * bartender suit (#20521) * give me a drink bartender * guh * aARG * Necropolis and mine walls (#20578) * Automatic changelog update * archaic accent tweaks (#20567) * Automatic changelog update * Add confirmation to kick and respawn in the admin player actions panel (#20542) * Remove windows-latest CI runs, remove unused build-test-release.yml (#20540) * Change .editorconfig to keep existing attribute arrangement (#20538) * Automatic changelog update * Catch replay start and end errors on round restarts (#20565) * Update trivial components to use auto comp states (#20539) * Fix role unbans not applying in real time (#20547) * Disable AHelp buttons when no player is selected, update button styling (#20568) * Automatic changelog update * Added a toggle fullscreen button (default F11) (#20272) * Added a toggle fullscreen button (default F11) * Removed un-needed comments * Review Requested Changes * Fixed Acidental Spacing Change * bwoink, removed extraneous code * nothing, litterally * Automatic changelog update * Fix not networking markings (#20588) * Automatic changelog update * Update RobustToolbox to v162.2.1 (#20590) * Automatic changelog update * Glorfcode (force say on damage/stun/crit) (#20562) * Automatic changelog update * Fix AHelp progressively showing more AHelp panels (#20591) * Automatic changelog update * caninsert entitystorage tweaks (#20589) * Automatic changelog update * Fix mapping actions crashing on load (#20592) * Fix loading a map with a ThirstComponent crashing the game (#20594) * Fix electrocution displaying real name of disguised players (#20603) * Organ fixes (#20488) * Automatic changelog update * Kettle medical rework (#20435) * Initial try at medical rework for kettle * Re-add decal decorations to Kettle's medical * Add more lockers to kettle's med * Actually upload kettle's map, not its proto * Automatic changelog update * Added blocked visuals to volumetric pump (#20610) * Automatic changelog update * Move TimedDespawn to engine (#20515) * Update submodule to 163.0.0 (#20616) * Remove v0.1 version number from local main menu screen (#20617) * Remove cloneData parameter from AutoNetworkedField (#20596) * Update submodule to 164.0.0 (#20618) * fix cognizine ghost role (#20632) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Move ID layer one pixel to correct alignment (#20630) * Update nukie hardsuit descriptions (#20529) * Make holofans destructable (#20445) * Automatic changelog update * bowl is open (#20453) Co-authored-by: deltanedas <@deltanedas:kde.org> * Add TestPair.WaitCommand() (#20615) * Rename ThreatPrototype and mark fields as required (#20611) * Adjust hard bomb shape (#20608) * dragon refactor, objectives and use GenericAntag (#20201) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Added generic empty liquids tank (#20563) * Move view variables verb to the top of the list with no category and localize it (#20546) * saltern update (#20325) Co-authored-by: deltanedas <@deltanedas:kde.org> * Fix followers leaking (#20643) * Add Winter Boots (#20622) * add * fix prototype * Add QM mantle (#20621) * add * fix prototype * Automatic changelog update * Slime mobs breathe nitrogen and resprite their organs (#20577) * Slimes breathe nitrogen and resprite their organs * ups * mmm * Automatic changelog update * Fix the new lizard horn's consistency + issue (#20620) * fix * add * Revert "add" This reverts commit a054a3204a8f185a94ceb80b1bd3bc9f30423711. * Add RandomHumanoidAppearance component to for space ninjas (#20605) * Automatic changelog update * Space cat breathes space (#20550) * Space cat breathes space Made Space Cat lungs ROBUST * Made Space Cat's lungs ROBUST 2.0 * Automatic changelog update * Wide anomaly locator (#20581) * Add files via upload * Add files via upload * Add files via upload * Add files via upload * add textures * fix encoding * fix 2 * Automatic changelog update * Fixed Telescopic Shield Lighting (#20650) * Fixed Telescopic Shield Lighting Bug fix #20199 * no need for these at all --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> * Automatic changelog update * Add a special hardsuit for nukie medic (#20471) * Automatic changelog update * Carry over other mutations when doing species mutation (#20551) * Automatic changelog update * Add Spanish accent to poncho and sombrero (#20377) --------- Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: Psychpsyo <60073468+Psychpsyo@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: ravage <142820619+ravage123321@users.noreply.github.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: LEVELcat <68501903+LEVELcat@users.noreply.github.com> Co-authored-by: Repo <47093363+Titian3@users.noreply.github.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> Co-authored-by: Doru991 <75124791+Doru991@users.noreply.github.com> Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com> Co-authored-by: Skarletto <122584947+Skarletto@users.noreply.github.com> Co-authored-by: NULL882 <104377798+NULL882@users.noreply.github.com> Co-authored-by: lunarcomets <140772713+lunarcomets@users.noreply.github.com> Co-authored-by: Miro Kavaliou <miraslauk@gmail.com> Co-authored-by: Kara <lunarautomaton6@gmail.com> Co-authored-by: Kacper Urbańczyk <kacperjaroslawurbanczyk@gmail.com> Co-authored-by: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Co-authored-by: daerSeebaer <61566539+daerSeebaer@users.noreply.github.com> Co-authored-by: Fluffiest Floofers <thebluewulf@gmail.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Kevin Zheng <kevinz5000@gmail.com> Co-authored-by: drteaspoon420 <87363733+drteaspoon420@users.noreply.github.com> Co-authored-by: Ubaser <134914314+UbaserB@users.noreply.github.com> Co-authored-by: Nim <128169402+Nimfar11@users.noreply.github.com> Co-authored-by: Kacper Urbańczyk <mikrel071204@gmail.com> Co-authored-by: Tox Cruize <141375638+TexCruize@users.noreply.github.com> Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> Co-authored-by: Michael Cu <43478115+michaelcu@users.noreply.github.com>
458 lines
19 KiB
C#
458 lines
19 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Server.Administration.Logs;
|
|
using Content.Server.Cargo.Systems;
|
|
using Content.Server.Interaction;
|
|
using Content.Server.Power.EntitySystems;
|
|
using Content.Server.Stunnable;
|
|
using Content.Server.Weapons.Ranged.Components;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.Damage.Systems;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Effects;
|
|
using Content.Shared.FixedPoint;
|
|
using Content.Shared.Interaction.Components;
|
|
using Content.Shared.Projectiles;
|
|
using Content.Shared.Weapons.Melee;
|
|
using Content.Shared.Weapons.Ranged;
|
|
using Content.Shared.Weapons.Ranged.Components;
|
|
using Content.Shared.Weapons.Ranged.Events;
|
|
using Content.Shared.Weapons.Reflect;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Physics;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Utility;
|
|
using SharedGunSystem = Content.Shared.Weapons.Ranged.Systems.SharedGunSystem;
|
|
|
|
namespace Content.Server.Weapons.Ranged.Systems;
|
|
|
|
public sealed partial class GunSystem : SharedGunSystem
|
|
{
|
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
|
[Dependency] private readonly IComponentFactory _factory = default!;
|
|
[Dependency] private readonly BatterySystem _battery = default!;
|
|
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
|
|
[Dependency] private readonly InteractionSystem _interaction = default!;
|
|
[Dependency] private readonly PricingSystem _pricing = default!;
|
|
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
[Dependency] private readonly StaminaSystem _stamina = default!;
|
|
[Dependency] private readonly StunSystem _stun = default!;
|
|
|
|
public const float DamagePitchVariation = SharedMeleeWeaponSystem.DamagePitchVariation;
|
|
public const float GunClumsyChance = 0.5f;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<BallisticAmmoProviderComponent, PriceCalculationEvent>(OnBallisticPrice);
|
|
}
|
|
|
|
private void OnBallisticPrice(EntityUid uid, BallisticAmmoProviderComponent component, ref PriceCalculationEvent args)
|
|
{
|
|
if (string.IsNullOrEmpty(component.Proto) || component.UnspawnedCount == 0)
|
|
return;
|
|
|
|
if (!ProtoManager.TryIndex<EntityPrototype>(component.Proto, out var proto))
|
|
{
|
|
Log.Error($"Unable to find fill prototype for price on {component.Proto} on {ToPrettyString(uid)}");
|
|
return;
|
|
}
|
|
|
|
// Probably good enough for most.
|
|
var price = _pricing.GetEstimatedPrice(proto);
|
|
args.Price += price * component.UnspawnedCount;
|
|
}
|
|
|
|
public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo,
|
|
EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, out bool userImpulse, EntityUid? user = null, bool throwItems = false)
|
|
{
|
|
userImpulse = true;
|
|
|
|
// Try a clumsy roll
|
|
// TODO: Who put this here
|
|
if (TryComp<ClumsyComponent>(user, out var clumsy) && gun.ClumsyProof == false)
|
|
{
|
|
for (var i = 0; i < ammo.Count; i++)
|
|
{
|
|
if (_interaction.TryRollClumsy(user.Value, GunClumsyChance, clumsy))
|
|
{
|
|
// Wound them
|
|
Damageable.TryChangeDamage(user, clumsy.ClumsyDamage, origin: user);
|
|
_stun.TryParalyze(user.Value, TimeSpan.FromSeconds(3f), true);
|
|
|
|
// Apply salt to the wound ("Honk!")
|
|
Audio.PlayPvs(new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/bang.ogg"), gunUid);
|
|
Audio.PlayPvs(clumsy.ClumsySound, gunUid);
|
|
|
|
PopupSystem.PopupEntity(Loc.GetString("gun-clumsy"), user.Value);
|
|
_adminLogger.Add(LogType.EntityDelete, LogImpact.Medium, $"Clumsy fire by {ToPrettyString(user.Value)} deleted {ToPrettyString(gunUid)}");
|
|
Del(gunUid);
|
|
userImpulse = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem);
|
|
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);
|
|
var mapDirection = toMap - fromMap.Position;
|
|
var mapAngle = mapDirection.ToAngle();
|
|
var angle = GetRecoilAngle(Timing.CurTime, gun, mapDirection.ToAngle());
|
|
|
|
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
|
|
var fromEnt = MapManager.TryFindGridAt(fromMap, out var gridUid, out var grid)
|
|
? fromCoordinates.WithEntityId(gridUid, EntityManager)
|
|
: new EntityCoordinates(MapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
|
|
|
|
// Update shot based on the recoil
|
|
toMap = fromMap.Position + angle.ToVec() * mapDirection.Length();
|
|
mapDirection = toMap - fromMap.Position;
|
|
var gunVelocity = Physics.GetMapLinearVelocity(gunUid);
|
|
|
|
// I must be high because this was getting tripped even when true.
|
|
// DebugTools.Assert(direction != Vector2.Zero);
|
|
var shotProjectiles = new List<EntityUid>(ammo.Count);
|
|
|
|
foreach (var (ent, shootable) in ammo)
|
|
{
|
|
// pneumatic cannon doesn't shoot bullets it just throws them, ignore ammo handling
|
|
if (throwItems && ent != null)
|
|
{
|
|
ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user);
|
|
continue;
|
|
}
|
|
|
|
switch (shootable)
|
|
{
|
|
// Cartridge shoots something else
|
|
case CartridgeAmmoComponent cartridge:
|
|
if (!cartridge.Spent)
|
|
{
|
|
if (cartridge.Count > 1)
|
|
{
|
|
var angles = LinearSpread(mapAngle - cartridge.Spread / 2,
|
|
mapAngle + cartridge.Spread / 2, cartridge.Count);
|
|
|
|
for (var i = 0; i < cartridge.Count; i++)
|
|
{
|
|
var uid = Spawn(cartridge.Prototype, fromEnt);
|
|
ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user);
|
|
shotProjectiles.Add(uid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var uid = Spawn(cartridge.Prototype, fromEnt);
|
|
ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user);
|
|
shotProjectiles.Add(uid);
|
|
}
|
|
|
|
RaiseLocalEvent(ent!.Value, new AmmoShotEvent()
|
|
{
|
|
FiredProjectiles = shotProjectiles,
|
|
});
|
|
|
|
SetCartridgeSpent(ent.Value, cartridge, true);
|
|
MuzzleFlash(gunUid, cartridge, user);
|
|
Audio.PlayPredicted(gun.SoundGunshot, gunUid, user);
|
|
|
|
if (cartridge.DeleteOnSpawn)
|
|
Del(ent.Value);
|
|
}
|
|
else
|
|
{
|
|
userImpulse = false;
|
|
Audio.PlayPredicted(gun.SoundEmpty, gunUid, user);
|
|
}
|
|
|
|
// Something like ballistic might want to leave it in the container still
|
|
if (!cartridge.DeleteOnSpawn && !Containers.IsEntityInContainer(ent!.Value))
|
|
EjectCartridge(ent.Value, angle);
|
|
|
|
Dirty(ent!.Value, cartridge);
|
|
break;
|
|
// Ammo shoots itself
|
|
case AmmoComponent newAmmo:
|
|
shotProjectiles.Add(ent!.Value);
|
|
MuzzleFlash(gunUid, newAmmo, user);
|
|
Audio.PlayPredicted(gun.SoundGunshot, gunUid, user);
|
|
ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user);
|
|
break;
|
|
case HitscanPrototype hitscan:
|
|
|
|
EntityUid? lastHit = null;
|
|
|
|
var from = fromMap;
|
|
// can't use map coords above because funny FireEffects
|
|
var fromEffect = fromCoordinates;
|
|
var dir = mapDirection.Normalized();
|
|
var lastUser = user;
|
|
|
|
if (hitscan.Reflective != ReflectType.None)
|
|
{
|
|
for (var reflectAttempt = 0; reflectAttempt < 3; reflectAttempt++)
|
|
{
|
|
var ray = new CollisionRay(from.Position, dir, hitscan.CollisionMask);
|
|
var rayCastResults =
|
|
Physics.IntersectRay(from.MapId, ray, hitscan.MaxLength, lastUser, false).ToList();
|
|
if (!rayCastResults.Any())
|
|
break;
|
|
|
|
var result = rayCastResults[0];
|
|
var hit = result.HitEntity;
|
|
lastHit = hit;
|
|
|
|
FireEffects(fromEffect, result.Distance, dir.Normalized().ToAngle(), hitscan, hit);
|
|
|
|
var ev = new HitScanReflectAttemptEvent(user, gunUid, hitscan.Reflective, dir, false);
|
|
RaiseLocalEvent(hit, ref ev);
|
|
|
|
if (!ev.Reflected)
|
|
break;
|
|
|
|
fromEffect = Transform(hit).Coordinates;
|
|
from = fromEffect.ToMap(EntityManager, _transform);
|
|
dir = ev.Direction;
|
|
lastUser = hit;
|
|
}
|
|
}
|
|
|
|
if (lastHit != null)
|
|
{
|
|
var hitEntity = lastHit.Value;
|
|
if (hitscan.StaminaDamage > 0f)
|
|
_stamina.TakeStaminaDamage(hitEntity, hitscan.StaminaDamage, source: user);
|
|
|
|
var dmg = hitscan.Damage;
|
|
|
|
var hitName = ToPrettyString(hitEntity);
|
|
if (dmg != null)
|
|
dmg = Damageable.TryChangeDamage(hitEntity, dmg, origin: user);
|
|
|
|
// check null again, as TryChangeDamage returns modified damage values
|
|
if (dmg != null)
|
|
{
|
|
if (!Deleted(hitEntity))
|
|
{
|
|
if (dmg.Total > FixedPoint2.Zero)
|
|
{
|
|
_color.RaiseEffect(Color.Red, new List<EntityUid>() { hitEntity }, Filter.Pvs(hitEntity, entityManager: EntityManager));
|
|
}
|
|
|
|
// TODO get fallback position for playing hit sound.
|
|
PlayImpactSound(hitEntity, dmg, hitscan.Sound, hitscan.ForceSound);
|
|
}
|
|
|
|
if (user != null)
|
|
{
|
|
Logs.Add(LogType.HitScanHit,
|
|
$"{ToPrettyString(user.Value):user} hit {hitName:target} using hitscan and dealt {dmg.Total:damage} damage");
|
|
}
|
|
else
|
|
{
|
|
Logs.Add(LogType.HitScanHit,
|
|
$"{hitName:target} hit by hitscan dealing {dmg.Total:damage} damage");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FireEffects(fromEffect, hitscan.MaxLength, dir.ToAngle(), hitscan);
|
|
}
|
|
|
|
Audio.PlayPredicted(gun.SoundGunshot, gunUid, user);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
RaiseLocalEvent(gunUid, new AmmoShotEvent()
|
|
{
|
|
FiredProjectiles = shotProjectiles,
|
|
});
|
|
}
|
|
|
|
private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid gunUid, EntityUid? user)
|
|
{
|
|
// Do a throw
|
|
if (!HasComp<ProjectileComponent>(uid))
|
|
{
|
|
RemoveShootable(uid);
|
|
// TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack.
|
|
ThrowingSystem.TryThrow(uid, mapDirection, gun.ProjectileSpeed, user);
|
|
return;
|
|
}
|
|
|
|
ShootProjectile(uid, mapDirection, gunVelocity, gunUid, user, gun.ProjectileSpeed);
|
|
}
|
|
|
|
public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocity, EntityUid gunUid, EntityUid? user = null, float speed = 20f)
|
|
{
|
|
var physics = EnsureComp<PhysicsComponent>(uid);
|
|
Physics.SetBodyStatus(physics, BodyStatus.InAir);
|
|
|
|
var targetMapVelocity = gunVelocity + direction.Normalized() * speed;
|
|
var currentMapVelocity = Physics.GetMapLinearVelocity(uid, physics);
|
|
var finalLinear = physics.LinearVelocity + targetMapVelocity - currentMapVelocity;
|
|
Physics.SetLinearVelocity(uid, finalLinear, body: physics);
|
|
|
|
if (user != null)
|
|
{
|
|
var projectile = EnsureComp<ProjectileComponent>(uid);
|
|
Projectiles.SetShooter(projectile, user.Value);
|
|
projectile.Weapon = gunUid;
|
|
}
|
|
|
|
TransformSystem.SetWorldRotation(uid, direction.ToWorldAngle());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a linear spread of angles between start and end.
|
|
/// </summary>
|
|
/// <param name="start">Start angle in degrees</param>
|
|
/// <param name="end">End angle in degrees</param>
|
|
/// <param name="intervals">How many shots there are</param>
|
|
private Angle[] LinearSpread(Angle start, Angle end, int intervals)
|
|
{
|
|
var angles = new Angle[intervals];
|
|
DebugTools.Assert(intervals > 1);
|
|
|
|
for (var i = 0; i <= intervals - 1; i++)
|
|
{
|
|
angles[i] = new Angle(start + (end - start) * i / (intervals - 1));
|
|
}
|
|
|
|
return angles;
|
|
}
|
|
|
|
private Angle GetRecoilAngle(TimeSpan curTime, GunComponent component, Angle direction)
|
|
{
|
|
var timeSinceLastFire = (curTime - component.LastFire).TotalSeconds;
|
|
var newTheta = MathHelper.Clamp(component.CurrentAngle.Theta + component.AngleIncrease.Theta - component.AngleDecay.Theta * timeSinceLastFire, component.MinAngle.Theta, component.MaxAngle.Theta);
|
|
component.CurrentAngle = new Angle(newTheta);
|
|
component.LastFire = component.NextFire;
|
|
|
|
// Convert it so angle can go either side.
|
|
var random = Random.NextFloat(-0.5f, 0.5f);
|
|
var spread = component.CurrentAngle.Theta * random;
|
|
var angle = new Angle(direction.Theta + component.CurrentAngle.Theta * random);
|
|
DebugTools.Assert(spread <= component.MaxAngle.Theta);
|
|
return angle;
|
|
}
|
|
|
|
protected override void Popup(string message, EntityUid? uid, EntityUid? user) { }
|
|
|
|
protected override void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null)
|
|
{
|
|
var filter = Filter.Pvs(uid, entityManager: EntityManager);
|
|
|
|
if (TryComp<ActorComponent>(user, out var actor))
|
|
filter.RemovePlayer(actor.PlayerSession);
|
|
|
|
RaiseNetworkEvent(message, filter);
|
|
}
|
|
|
|
public void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound)
|
|
{
|
|
DebugTools.Assert(!Deleted(otherEntity), "Impact sound entity was deleted");
|
|
|
|
// Like projectiles and melee,
|
|
// 1. Entity specific sound
|
|
// 2. Ammo's sound
|
|
// 3. Nothing
|
|
var playedSound = false;
|
|
|
|
if (!forceWeaponSound && modifiedDamage != null && modifiedDamage.Total > 0 && TryComp<RangedDamageSoundComponent>(otherEntity, out var rangedSound))
|
|
{
|
|
var type = SharedMeleeWeaponSystem.GetHighestDamageSound(modifiedDamage, ProtoManager);
|
|
|
|
if (type != null && rangedSound.SoundTypes?.TryGetValue(type, out var damageSoundType) == true)
|
|
{
|
|
Audio.PlayPvs(damageSoundType, otherEntity, AudioParams.Default.WithVariation(DamagePitchVariation));
|
|
playedSound = true;
|
|
}
|
|
else if (type != null && rangedSound.SoundGroups?.TryGetValue(type, out var damageSoundGroup) == true)
|
|
{
|
|
Audio.PlayPvs(damageSoundGroup, otherEntity, AudioParams.Default.WithVariation(DamagePitchVariation));
|
|
playedSound = true;
|
|
}
|
|
}
|
|
|
|
if (!playedSound && weaponSound != null)
|
|
{
|
|
Audio.PlayPvs(weaponSound, otherEntity);
|
|
}
|
|
}
|
|
|
|
// TODO: Pseudo RNG so the client can predict these.
|
|
#region Hitscan effects
|
|
|
|
private void FireEffects(EntityCoordinates fromCoordinates, float distance, Angle mapDirection, HitscanPrototype hitscan, EntityUid? hitEntity = null)
|
|
{
|
|
// Lord
|
|
// Forgive me for the shitcode I am about to do
|
|
// Effects tempt me not
|
|
var sprites = new List<(NetCoordinates coordinates, Angle angle, SpriteSpecifier sprite, float scale)>();
|
|
var gridUid = fromCoordinates.GetGridUid(EntityManager);
|
|
var angle = mapDirection;
|
|
|
|
// We'll get the effects relative to the grid / map of the firer
|
|
// Look you could probably optimise this a bit with redundant transforms at this point.
|
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
|
|
if (xformQuery.TryGetComponent(gridUid, out var gridXform))
|
|
{
|
|
var (_, gridRot, gridInvMatrix) = TransformSystem.GetWorldPositionRotationInvMatrix(gridXform, xformQuery);
|
|
|
|
fromCoordinates = new EntityCoordinates(gridUid.Value,
|
|
gridInvMatrix.Transform(fromCoordinates.ToMapPos(EntityManager, TransformSystem)));
|
|
|
|
// Use the fallback angle I guess?
|
|
angle -= gridRot;
|
|
}
|
|
|
|
if (distance >= 1f)
|
|
{
|
|
if (hitscan.MuzzleFlash != null)
|
|
{
|
|
var coords = fromCoordinates.Offset(angle.ToVec().Normalized() / 2);
|
|
var netCoords = GetNetCoordinates(coords);
|
|
|
|
sprites.Add((netCoords, angle, hitscan.MuzzleFlash, 1f));
|
|
}
|
|
|
|
if (hitscan.TravelFlash != null)
|
|
{
|
|
var coords = fromCoordinates.Offset(angle.ToVec() * (distance + 0.5f) / 2);
|
|
var netCoords = GetNetCoordinates(coords);
|
|
|
|
sprites.Add((netCoords, angle, hitscan.TravelFlash, distance - 1.5f));
|
|
}
|
|
}
|
|
|
|
if (hitscan.ImpactFlash != null)
|
|
{
|
|
var coords = fromCoordinates.Offset(angle.ToVec() * distance);
|
|
var netCoords = GetNetCoordinates(coords);
|
|
|
|
sprites.Add((netCoords, angle.FlipPositive(), hitscan.ImpactFlash, 1f));
|
|
}
|
|
|
|
if (sprites.Count > 0)
|
|
{
|
|
RaiseNetworkEvent(new HitscanEvent
|
|
{
|
|
Sprites = sprites,
|
|
}, Filter.Pvs(fromCoordinates, entityMan: EntityManager));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|