From 22e3d533d3c38597423588b9f21446d434cc3221 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:54:20 +0200 Subject: [PATCH] Catchable items, playable basketball (#37702) * catching * fix * improve * fix linter * cleanup * fix prediction * do the same here * fix comment --- .../Body/Systems/SharedBloodstreamSystem.cs | 3 +- Content.Shared/Clumsy/ClumsyComponent.cs | 32 +++++-- Content.Shared/Clumsy/ClumsySystem.cs | 65 +++++++++++--- .../Random/Helpers/SharedRandomExtensions.cs | 20 +++++ Content.Shared/Throwing/CatchAttemptEvent.cs | 7 ++ Content.Shared/Throwing/CatchableComponent.cs | 39 +++++++++ Content.Shared/Throwing/CatchableSystem.cs | 84 +++++++++++++++++++ .../bonk/components/bonkable-component.ftl | 4 - .../components/hypospray-component.ftl | 1 - .../clown/components/clumsy-component.ftl | 10 +++ Resources/Locale/en-US/throwing/catchable.ftl | 4 + Resources/Locale/en-US/weapons/ranged/gun.ftl | 1 - .../Prototypes/Entities/Mobs/NPCs/animals.yml | 6 ++ .../Entities/Mobs/Player/guardian.yml | 3 + .../Prototypes/Entities/Objects/Fun/toys.yml | 46 ++++++++++ .../Prototypes/Roles/Jobs/Civilian/clown.yml | 3 + 16 files changed, 304 insertions(+), 24 deletions(-) create mode 100644 Content.Shared/Throwing/CatchAttemptEvent.cs create mode 100644 Content.Shared/Throwing/CatchableComponent.cs create mode 100644 Content.Shared/Throwing/CatchableSystem.cs delete mode 100644 Resources/Locale/en-US/bonk/components/bonkable-component.ftl create mode 100644 Resources/Locale/en-US/clown/components/clumsy-component.ftl create mode 100644 Resources/Locale/en-US/throwing/catchable.ftl diff --git a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs index b5d5100189..ac385040a9 100644 --- a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs +++ b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs @@ -14,6 +14,7 @@ using Content.Shared.Forensics.Components; using Content.Shared.HealthExaminable; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; +using Content.Shared.Random.Helpers; using Content.Shared.Rejuvenate; using Content.Shared.Speech.EntitySystems; using Robust.Shared.Audio.Systems; @@ -222,7 +223,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem // TODO: Replace with RandomPredicted once the engine PR is merged // Use both the receiver and the damage causing entity for the seed so that we have different results for multiple attacks in the same tick - var seed = HashCode.Combine((int)_timing.CurTick.Value, GetNetEntity(ent).Id, GetNetEntity(args.Origin)?.Id ?? 0); + var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id, GetNetEntity(args.Origin)?.Id ?? 0 }); var rand = new System.Random(seed); var prob = Math.Clamp(totalFloat / 25, 0, 1); if (totalFloat > 0 && rand.Prob(prob)) diff --git a/Content.Shared/Clumsy/ClumsyComponent.cs b/Content.Shared/Clumsy/ClumsyComponent.cs index 6b013a5c2f..9e6e851c8c 100644 --- a/Content.Shared/Clumsy/ClumsyComponent.cs +++ b/Content.Shared/Clumsy/ClumsyComponent.cs @@ -5,7 +5,7 @@ using Robust.Shared.GameStates; namespace Content.Shared.Clumsy; /// -/// A simple clumsy tag-component. +/// Makes the entity clumsy, randomly failing some interactions and hurting themselves. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class ClumsyComponent : Component @@ -48,11 +48,17 @@ public sealed partial class ClumsyComponent : Component public TimeSpan GunShootFailStunTime = TimeSpan.FromSeconds(3); /// - /// Stun time after failing to shoot a gun. + /// Damage taken after failing to shoot a gun. /// [DataField, AutoNetworkedField] public DamageSpecifier? GunShootFailDamage; + /// + /// Damage taken after failing to catch an item. + /// + [DataField, AutoNetworkedField] + public DamageSpecifier? CatchingFailDamage; + /// /// Noise to play after failing to shoot a gun. Boom! /// @@ -77,6 +83,12 @@ public sealed partial class ClumsyComponent : Component [DataField, AutoNetworkedField] public bool ClumsyGuns = true; + /// + /// Whether or not to apply Clumsy to catching items. + /// + [DataField, AutoNetworkedField] + public bool ClumsyCatching = true; + /// /// Whether or not to apply Clumsy to vaulting. /// @@ -87,17 +99,23 @@ public sealed partial class ClumsyComponent : Component /// Lets you define a new "failed" message for each event. /// [DataField] - public LocId HypoFailedMessage = "hypospray-component-inject-self-clumsy-message"; + public LocId HypoFailedMessage = "clumsy-hypospray-fail-message"; + + [DataField] + public LocId GunFailedMessage = "clumsy-gun-fail-message"; + + [DataField] + public LocId CatchingFailedMessageSelf = "clumsy-catch-fail-message-user"; [DataField] - public LocId GunFailedMessage = "gun-clumsy"; + public LocId CatchingFailedMessageOthers = "clumsy-catch-fail-message-others"; [DataField] - public LocId VaulingFailedMessageSelf = "bonkable-success-message-user"; + public LocId VaulingFailedMessageSelf = "clumsy-vaulting-fail-message-user"; [DataField] - public LocId VaulingFailedMessageOthers = "bonkable-success-message-others"; + public LocId VaulingFailedMessageOthers = "clumsy-vaulting-fail-message-others"; [DataField] - public LocId VaulingFailedMessageForced = "forced-bonkable-success-message"; + public LocId VaulingFailedMessageForced = "clumsy-vaulting-fail-forced-message"; } diff --git a/Content.Shared/Clumsy/ClumsySystem.cs b/Content.Shared/Clumsy/ClumsySystem.cs index 348d99182a..9e0e82364f 100644 --- a/Content.Shared/Clumsy/ClumsySystem.cs +++ b/Content.Shared/Clumsy/ClumsySystem.cs @@ -6,10 +6,14 @@ using Content.Shared.Damage; using Content.Shared.IdentityManagement; using Content.Shared.Medical; using Content.Shared.Popups; +using Content.Shared.Random.Helpers; using Content.Shared.Stunnable; +using Content.Shared.Throwing; using Content.Shared.Weapons.Ranged.Events; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -17,19 +21,20 @@ namespace Content.Shared.Clumsy; public sealed class ClumsySystem : EntitySystem { - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedStunSystem _stun = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly INetManager _net = default!; public override void Initialize() { SubscribeLocalEvent(BeforeHyposprayEvent); SubscribeLocalEvent(BeforeDefibrillatorZapsEvent); SubscribeLocalEvent(BeforeGunShotEvent); + SubscribeLocalEvent(OnCatchAttempt); SubscribeLocalEvent(OnBeforeClimbEvent); } @@ -43,12 +48,15 @@ public sealed class ClumsySystem : EntitySystem if (!ent.Comp.ClumsyHypo) return; - if (!_random.Prob(ent.Comp.ClumsyDefaultCheck)) + // TODO: Replace with RandomPredicted once the engine PR is merged + var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id }); + var rand = new System.Random(seed); + if (!rand.Prob(ent.Comp.ClumsyDefaultCheck)) return; args.TargetGettingInjected = args.EntityUsingHypospray; - args.InjectMessageOverride = "hypospray-component-inject-self-clumsy-message"; - _audio.PlayPvs(ent.Comp.ClumsySound, ent); + args.InjectMessageOverride = Loc.GetString(ent.Comp.HypoFailedMessage); + _audio.PlayPredicted(ent.Comp.ClumsySound, ent, args.EntityUsingHypospray); } private void BeforeDefibrillatorZapsEvent(Entity ent, ref SelfBeforeDefibrillatorZapsEvent args) @@ -59,7 +67,10 @@ public sealed class ClumsySystem : EntitySystem if (!ent.Comp.ClumsyDefib) return; - if (!_random.Prob(ent.Comp.ClumsyDefaultCheck)) + // TODO: Replace with RandomPredicted once the engine PR is merged + var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id }); + var rand = new System.Random(seed); + if (!rand.Prob(ent.Comp.ClumsyDefaultCheck)) return; args.DefibTarget = args.EntityUsingDefib; @@ -67,6 +78,37 @@ public sealed class ClumsySystem : EntitySystem } + private void OnCatchAttempt(Entity ent, ref CatchAttemptEvent args) + { + // Clumsy people sometimes fail to catch items! + + // checks if ClumsyCatching is false, if so, skips. + if (!ent.Comp.ClumsyCatching) + return; + + // TODO: Replace with RandomPredicted once the engine PR is merged + var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(args.Item).Id }); + var rand = new System.Random(seed); + if (!rand.Prob(ent.Comp.ClumsyDefaultCheck)) + return; + + args.Cancelled = true; // fail to catch + + if (ent.Comp.CatchingFailDamage != null) + _damageable.TryChangeDamage(ent, ent.Comp.CatchingFailDamage, origin: args.Item); + + // Collisions don't work properly with PopupPredicted or PlayPredicted. + // So we make this server only. + if (_net.IsClient) + return; + + var selfMessage = Loc.GetString(ent.Comp.CatchingFailedMessageSelf, ("item", ent.Owner), ("catcher", Identity.Entity(ent.Owner, EntityManager))); + var othersMessage = Loc.GetString(ent.Comp.CatchingFailedMessageOthers, ("item", ent.Owner), ("catcher", Identity.Entity(ent.Owner, EntityManager))); + _popup.PopupEntity(selfMessage, ent.Owner, ent.Owner); + _popup.PopupEntity(othersMessage, ent.Owner, Filter.PvsExcept(ent.Owner), true); + _audio.PlayPvs(ent.Comp.ClumsySound, ent); + } + private void BeforeGunShotEvent(Entity ent, ref SelfBeforeGunShotEvent args) { // Clumsy people sometimes can't shoot :( @@ -78,7 +120,10 @@ public sealed class ClumsySystem : EntitySystem if (args.Gun.Comp.ClumsyProof) return; - if (!_random.Prob(ent.Comp.ClumsyDefaultCheck)) + // TODO: Replace with RandomPredicted once the engine PR is merged + var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(args.Gun).Id }); + var rand = new System.Random(seed); + if (!rand.Prob(ent.Comp.ClumsyDefaultCheck)) return; if (ent.Comp.GunShootFailDamage != null) @@ -90,7 +135,7 @@ public sealed class ClumsySystem : EntitySystem _audio.PlayPvs(ent.Comp.GunShootFailSound, ent); _audio.PlayPvs(ent.Comp.ClumsySound, ent); - _popup.PopupEntity(Loc.GetString("gun-clumsy"), ent, ent); + _popup.PopupEntity(Loc.GetString(ent.Comp.GunFailedMessage), ent, ent); args.Cancel(); } @@ -100,9 +145,9 @@ public sealed class ClumsySystem : EntitySystem if (!ent.Comp.ClumsyVaulting) return; - // This event is called in shared, thats why it has all the extra prediction stuff. - var rand = new System.Random((int)_timing.CurTick.Value); - + // TODO: Replace with RandomPredicted once the engine PR is merged + var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id }); + var rand = new System.Random(seed); if (!_cfg.GetCVar(CCVars.GameTableBonk) && !rand.Prob(ent.Comp.ClumsyDefaultCheck)) return; diff --git a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs index 42d92a9065..87e839b56f 100644 --- a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs +++ b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs @@ -184,5 +184,25 @@ namespace Content.Shared.Random.Helpers // Shouldn't happen throw new InvalidOperationException($"Invalid weighted pick for {prototype.ID}!"); } + + /// + /// A very simple, deterministic djb2 hash function for generating a combined seed for the random number generator. + /// We can't use HashCode.Combine because that is initialized with a random value, creating different results on the server and client. + /// + /// + /// Combine the current game tick with a NetEntity Id in order to not get the same random result if this is called multiple times in the same tick. + /// + /// var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id }); + /// + /// + public static int HashCodeCombine(List values) + { + int hash = 5381; + foreach (var value in values) + { + hash = (hash << 5) + hash + value; + } + return hash; + } } } diff --git a/Content.Shared/Throwing/CatchAttemptEvent.cs b/Content.Shared/Throwing/CatchAttemptEvent.cs new file mode 100644 index 0000000000..03206bdcd9 --- /dev/null +++ b/Content.Shared/Throwing/CatchAttemptEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Throwing; + +/// +/// Raised on someone when they try to catch an item. +/// +[ByRefEvent] +public record struct CatchAttemptEvent(EntityUid Item, float CatchChance, bool Cancelled = false); diff --git a/Content.Shared/Throwing/CatchableComponent.cs b/Content.Shared/Throwing/CatchableComponent.cs new file mode 100644 index 0000000000..ce68374440 --- /dev/null +++ b/Content.Shared/Throwing/CatchableComponent.cs @@ -0,0 +1,39 @@ +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Throwing; + +/// +/// Allows this entity to be caught in your hands when someone else throws it at you. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class CatchableComponent : Component +{ + /// + /// If true this item can only be caught while in combat mode. + /// + [DataField, AutoNetworkedField] + public bool RequireCombatMode; + + /// + /// The chance of successfully catching. + /// + [DataField, AutoNetworkedField] + public float CatchChance = 1.0f; + + /// + /// Optional whitelist for who can catch this item. + /// + /// + /// Example usecase: Only someone who knows martial arts can catch grenades. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? CatcherWhitelist; + + /// + /// The sound to play when successfully catching. + /// + [DataField] + public SoundSpecifier? CatchSuccessSound; +} diff --git a/Content.Shared/Throwing/CatchableSystem.cs b/Content.Shared/Throwing/CatchableSystem.cs new file mode 100644 index 0000000000..8f2fd355ba --- /dev/null +++ b/Content.Shared/Throwing/CatchableSystem.cs @@ -0,0 +1,84 @@ +using Content.Shared.CombatMode; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; +using Content.Shared.Popups; +using Content.Shared.Whitelist; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Shared.Throwing; + +public sealed partial class CatchableSystem : EntitySystem +{ + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly ThrownItemSystem _thrown = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + + private EntityQuery _handsQuery; + private EntityQuery _combatModeQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoHit); + + _handsQuery = GetEntityQuery(); + _combatModeQuery = GetEntityQuery(); + } + + private void OnDoHit(Entity ent, ref ThrowDoHitEvent args) + { + if (!_handsQuery.TryGetComponent(args.Target, out var handsComp)) + return; // don't do anything for walls etc + + // Is the catcher in combat mode if required? + if (ent.Comp.RequireCombatMode && (!_combatModeQuery.TryComp(args.Target, out var combatModeComp) || !combatModeComp.IsInCombatMode)) + return; + + // Is the catcher able to catch this item? + if (!_whitelist.IsWhitelistPassOrNull(ent.Comp.CatcherWhitelist, args.Target)) + return; + + var attemptEv = new CatchAttemptEvent(ent.Owner, ent.Comp.CatchChance); + RaiseLocalEvent(args.Target, ref attemptEv); + + if (attemptEv.Cancelled) + return; + + // TODO: Replace with RandomPredicted once the engine PR is merged + var seed = HashCode.Combine((int)_timing.CurTick.Value, GetNetEntity(ent).Id); + var rand = new System.Random(seed); + if (!rand.Prob(ent.Comp.CatchChance)) + return; + + // Try to catch! + if (!_hands.TryPickupAnyHand(args.Target, ent.Owner, handsComp: handsComp, animate: false)) + return; // The hands are full! + + // Success! + + // We picked it up already but we still have to raise the throwing stop (but not the landing) events at the right time, + // otherwise it will raise the events for that later while still in your hand + _thrown.StopThrow(ent.Owner, args.Component); + + // Collisions don't work properly with PopupPredicted or PlayPredicted. + // So we make this server only. + if (_net.IsClient) + return; + + var selfMessage = Loc.GetString("catchable-component-success-self", ("item", ent.Owner), ("catcher", Identity.Entity(args.Target, EntityManager))); + var othersMessage = Loc.GetString("catchable-component-success-others", ("item", ent.Owner), ("catcher", Identity.Entity(args.Target, EntityManager))); + _popup.PopupEntity(selfMessage, args.Target, args.Target); + _popup.PopupEntity(othersMessage, args.Target, Filter.PvsExcept(args.Target), true); + _audio.PlayPvs(ent.Comp.CatchSuccessSound, args.Target); + } +} diff --git a/Resources/Locale/en-US/bonk/components/bonkable-component.ftl b/Resources/Locale/en-US/bonk/components/bonkable-component.ftl deleted file mode 100644 index 1a79da3509..0000000000 --- a/Resources/Locale/en-US/bonk/components/bonkable-component.ftl +++ /dev/null @@ -1,4 +0,0 @@ -forced-bonkable-success-message = { CAPITALIZE($bonker) } bonks {$victim}s head against { THE($bonkable) }! - -bonkable-success-message-user = You bonk your head against { THE($bonkable) }! -bonkable-success-message-others = {$victim} bonks their head against { THE($bonkable) }! diff --git a/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl b/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl index 52dbf9010e..96ebaa3ed0 100644 --- a/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl @@ -10,7 +10,6 @@ hypospray-volume-label = Volume: [color=white]{$currentVolume}/{$totalVolume}u[/ hypospray-component-inject-other-message = You inject {$other}. hypospray-component-inject-self-message = You inject yourself. -hypospray-component-inject-self-clumsy-message = Oops! You injected yourself. hypospray-component-empty-message = Nothing to inject. hypospray-component-feel-prick-message = You feel a tiny prick! hypospray-component-transfer-already-full-message = {$owner} is already full! diff --git a/Resources/Locale/en-US/clown/components/clumsy-component.ftl b/Resources/Locale/en-US/clown/components/clumsy-component.ftl new file mode 100644 index 0000000000..055b3b9c4e --- /dev/null +++ b/Resources/Locale/en-US/clown/components/clumsy-component.ftl @@ -0,0 +1,10 @@ +clumsy-vaulting-fail-forced-message = { CAPITALIZE($bonker) } bonks { $victim }s head against { THE($bonkable) }! +clumsy-vaulting-fail-message-user = You bonk your head against { THE($bonkable) }! +clumsy-vaulting-fail-message-others = { $victim } bonks their head against { THE($bonkable) }! + +clumsy-gun-fail-message = The gun blows up in your face! + +clumsy-hypospray-fail-message = Oops! You injected yourself. + +clumsy-catch-fail-message-user = { CAPITALIZE(THE($item)) } hits your head! +clumsy-catch-fail-message-others = { CAPITALIZE(THE($item)) } hits { THE($catcher) }'s head! diff --git a/Resources/Locale/en-US/throwing/catchable.ftl b/Resources/Locale/en-US/throwing/catchable.ftl new file mode 100644 index 0000000000..8db3befd68 --- /dev/null +++ b/Resources/Locale/en-US/throwing/catchable.ftl @@ -0,0 +1,4 @@ +catchable-component-success-self = You catch {THE($item)}! +catchable-component-success-others = {CAPITALIZE(THE($catcher))} catches {THE($item)}! +catchable-component-fail-self = You fail to catch {THE($item)}! +catchable-component-fail-others = {CAPITALIZE(THE($catcher))} fails to catch {THE($item)}! diff --git a/Resources/Locale/en-US/weapons/ranged/gun.ftl b/Resources/Locale/en-US/weapons/ranged/gun.ftl index 18e01e31c8..a364075be9 100644 --- a/Resources/Locale/en-US/weapons/ranged/gun.ftl +++ b/Resources/Locale/en-US/weapons/ranged/gun.ftl @@ -4,7 +4,6 @@ gun-fire-rate-examine = Fire rate is [color={$color}]{$fireRate}[/color] per sec gun-selector-verb = Change to {$mode} gun-selected-mode = Selected {$mode} gun-disabled = You can't use guns! -gun-clumsy = The gun blows up in your face! gun-set-fire-mode = Set to {$mode} gun-magazine-whitelist-fail = That won't fit into the gun! gun-magazine-fired-empty = No ammo left! diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 967dda00db..2a599d70be 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1464,6 +1464,9 @@ Piercing: 4 groups: Burn: 3 + catchingFailDamage: + types: + Blunt: 1 clumsySound: path: /Audio/Animals/monkey_scream.ogg @@ -1643,6 +1646,9 @@ Piercing: 7 groups: Burn: 3 + catchingFailDamage: + types: + Blunt: 1 clumsySound: path: /Audio/Voice/Reptilian/reptilian_scream.ogg diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index 0aee45e28e..1b81b0d099 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -239,6 +239,9 @@ Piercing: 4 groups: Burn: 3 + catchingFailDamage: + types: + Blunt: 1 - type: MeleeWeapon angle: 30 animation: WeaponArcFist diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 4daffe1c56..e9279193b7 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -396,6 +396,20 @@ - type: Sprite sprite: Objects/Fun/Balls/basketball.rsi state: icon + - type: Fixtures + fixtures: + fix1: + shape: !type:PhysShapeCircle + radius: 0.25 + density: 20 + mask: + - ItemMask + restitution: 0.8 # bouncy + friction: 0.2 + - type: Catchable + catchChance: 0.8 + catchSuccessSound: + path: /Audio/Effects/Footsteps/bounce.ogg - type: EmitSoundOnCollide sound: path: /Audio/Effects/Footsteps/bounce.ogg @@ -414,6 +428,23 @@ - type: Sprite sprite: Objects/Fun/Balls/football.rsi state: icon + - type: Fixtures + fixtures: + fix1: + shape: !type:PhysShapeCircle + radius: 0.25 + density: 20 + mask: + - ItemMask + restitution: 0.5 # a little bouncy + friction: 0.2 + - type: Catchable + catchChance: 0.8 + catchSuccessSound: + path: /Audio/Effects/Footsteps/bounce.ogg + - type: EmitSoundOnCollide + sound: + path: /Audio/Effects/Footsteps/bounce.ogg - type: Item size: Small sprite: Objects/Fun/Balls/football.rsi @@ -427,6 +458,21 @@ - type: Sprite sprite: Objects/Fun/Balls/beach_ball.rsi state: icon + - type: Fixtures + fixtures: + fix1: + shape: !type:PhysShapeCircle + radius: 0.3 + position: "0,-0.2" + density: 20 + mask: + - ItemMask + restitution: 0.1 # not bouncy + friction: 0.2 + - type: Catchable + catchChance: 0.8 + catchSuccessSound: + path: /Audio/Effects/Footsteps/bounce.ogg - type: EmitSoundOnCollide sound: path: /Audio/Effects/Footsteps/bounce.ogg diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml index 60821cfe76..c64d0550ce 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml @@ -19,6 +19,9 @@ Piercing: 4 groups: Burn: 3 + catchingFailDamage: + types: + Blunt: 1 - type: SleepEmitSound snore: /Audio/Voice/Misc/silly_snore.ogg interval: 10 -- 2.51.2