using Content.Shared.Tabletop.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
+using Robust.Server.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
+using Robust.Shared.Audio;
using Timer = Robust.Shared.Timing.Timer;
+using Content.Shared.Cluwne;
namespace Content.Server.Administration.Systems;
};
args.Verbs.Add(killSign);
- // TODO: Port cluwne outfit.
- Verb clown = new()
+ Verb cluwne = new()
{
- Text = "Clown",
+ Text = "Cluwne",
Category = VerbCategory.Smite,
- Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Fun/bikehorn.rsi"), "icon"),
+
+ Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Clothing/Mask/cluwne.rsi"), "icon"),
+
Act = () =>
{
- SetOutfitCommand.SetOutfit(args.Target, "ClownGear", EntityManager, (_, clothing) =>
- {
- if (HasComp<ClothingComponent>(clothing))
- EnsureComp<UnremoveableComponent>(clothing);
- EnsureComp<ClumsyComponent>(args.Target);
- });
+ EnsureComp<CluwneComponent>(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-clown-description")
+ Message = Loc.GetString("admin-smite-cluwne-description")
};
- args.Verbs.Add(clown);
+ args.Verbs.Add(cluwne);
Verb maiden = new()
{
--- /dev/null
+using Content.Server.Administration.Commands;
+using Content.Server.Popups;
+using Content.Shared.Popups;
+using Content.Shared.Mobs;
+using Content.Server.Chat;
+using Content.Server.Chat.Systems;
+using Content.Shared.Chat.Prototypes;
+using Robust.Shared.Random;
+using Content.Shared.Stunnable;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.Damage;
+using Robust.Shared.Prototypes;
+using Content.Server.Emoting.Systems;
+using Content.Server.Speech.EntitySystems;
+using Content.Shared.Cluwne;
+using Content.Shared.Interaction.Components;
+
+namespace Content.Server.Cluwne;
+
+public sealed class CluwneSystem : EntitySystem
+{
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly IRobustRandom _robustRandom = default!;
+ [Dependency] private readonly SharedStunSystem _stunSystem = default!;
+ [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<CluwneComponent, ComponentStartup>(OnComponentStartup);
+ SubscribeLocalEvent<CluwneComponent, MobStateChangedEvent>(OnMobState);
+ SubscribeLocalEvent<CluwneComponent, EmoteEvent>(OnEmote, before:
+ new[] { typeof(VocalSystem), typeof(BodyEmotesSystem) });
+ }
+
+ /// <summary>
+ /// On death removes active comps and gives genetic damage to prevent cloning, reduce this to allow cloning.
+ /// </summary>
+ private void OnMobState(EntityUid uid, CluwneComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState == MobState.Dead)
+ {
+ RemComp<CluwneComponent>(uid);
+ RemComp<ClumsyComponent>(uid);
+ RemComp<AutoEmoteComponent>(uid);
+ var damageSpec = new DamageSpecifier(_prototypeManager.Index<DamageGroupPrototype>("Genetic"), 300);
+ _damageableSystem.TryChangeDamage(uid, damageSpec);
+ }
+ }
+
+ public EmoteSoundsPrototype? EmoteSounds;
+
+ /// <summary>
+ /// OnStartup gives the cluwne outfit, ensures clumsy, gives name prefix and makes sure emote sounds are laugh.
+ /// </summary>
+ private void OnComponentStartup(EntityUid uid, CluwneComponent component, ComponentStartup args)
+ {
+ if (component.EmoteSoundsId == null)
+ return;
+ _prototypeManager.TryIndex(component.EmoteSoundsId, out EmoteSounds);
+
+ var meta = MetaData(uid);
+ var name = meta.EntityName;
+
+ EnsureComp<AutoEmoteComponent>(uid);
+ _autoEmote.AddEmote(uid, "CluwneGiggle");
+ EnsureComp<ClumsyComponent>(uid);
+
+ _popupSystem.PopupEntity(Loc.GetString("cluwne-transform", ("target", uid)), uid, PopupType.LargeCaution);
+ _audio.PlayPvs(component.SpawnSound, uid);
+
+ meta.EntityName = Loc.GetString("cluwne-name-prefix", ("target", name));
+
+ SetOutfitCommand.SetOutfit(uid, "CluwneGear", EntityManager);
+ }
+
+ /// <summary>
+ /// Handles the timing on autoemote as well as falling over and honking.
+ /// </summary>
+ private void OnEmote(EntityUid uid, CluwneComponent component, ref EmoteEvent args)
+ {
+ if (args.Handled)
+ return;
+ args.Handled = _chat.TryPlayEmoteSound(uid, EmoteSounds, args.Emote);
+
+ if (_robustRandom.Prob(component.GiggleRandomChance))
+ {
+ _audio.PlayPvs(component.SpawnSound, uid);
+ _chat.TrySendInGameICMessage(uid, "honks", InGameICChatType.Emote, false, false);
+ }
+
+ else if (_robustRandom.Prob(component.KnockChance))
+ {
+ _audio.PlayPvs(component.KnockSound, uid);
+ _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(component.ParalyzeTime), true);
+ _chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, false, false);
+ }
+ }
+}
--- /dev/null
+using Robust.Shared.Audio;
+using Content.Shared.Chat.Prototypes;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Cluwne;
+
+[RegisterComponent]
+[NetworkedComponent]
+public sealed class CluwneComponent : Component
+{
+ /// <summary>
+ /// timings for giggles and knocks.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan DamageGiggleCooldown = TimeSpan.FromSeconds(2);
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float KnockChance = 0.05f;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float GiggleRandomChance = 0.1f;
+
+ [DataField("emoteId", customTypeSerializer: typeof(PrototypeIdSerializer<EmoteSoundsPrototype>))]
+ public string? EmoteSoundsId = "Cluwne";
+
+ /// <summary>
+ /// Amount of time cluwne is paralyzed for when falling over.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float ParalyzeTime = 2f;
+
+ /// <summary>
+ /// Sound specifiers for honk and knock.
+ /// </summary>
+ [DataField("spawnsound")]
+ public SoundSpecifier SpawnSound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg");
+
+ [DataField("knocksound")]
+ public SoundSpecifier KnockSound = new SoundPathSpecifier("/Audio/Items/airhorn.ogg");
+}
license: "CC-BY-SA-3.0"
copyright: "Time immemorial"
source: "https://github.com/tgstation/tgstation/blob/172b533d0257fcc1f8a05406f1c9fad514c14d88/sound/items/rped.ogg"
+
+- files: ["scary_horn.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Made by theOperand (github) for tgstation, modified by brainfood1183 (github) for ss14"
+ source: "https://github.com/tgstation/tgstation/blob/9a378933d2cfcb2c6692f5c60fbce979ac4e47c5/sound/spookoween/scary_horn.ogg"
+
+- files: ["airhorn.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Taken from tgstation, modified by brainfood1183 (github) for ss14"
+ source: "https://github.com/tgstation/tgstation/blob/529d97cb1c105bcd548e95a9c9070bbf5253dd81/sound/items/AirHorn.ogg"
--- /dev/null
+- files:
+ - cluwnelaugh1.ogg
+ - cluwnelaugh2.ogg
+ - cluwnelaugh3.ogg
+ license: "CC-BY-4.0"
+ copyright: "Evil Laughs 3 Pack by Nanakisan. Modified by brainfood1183 (Github), shortened, converted from WAV to OGG and downmixed to 1 channel."
+ source: "https://freesound.org/people/Nanakisan/packs/15544/"
admin-smite-ghostkick-description = Silently kicks the user, dropping their connection.
admin-smite-nyanify-description = Forcibly add cat ears, there is no escape.
admin-smite-kill-sign-description = Marks a player for death by their fellows.
-admin-smite-clown-description = Clowns them. The suit cannot be removed.
+admin-smite-cluwne-description = Cluwnes them. The suit cannot be removed and the station's crew may murder them freely.
admin-smite-anger-pointing-arrows-description = Angers the pointing arrows, causing them to assault this entity explosively.
admin-smite-dust-description = Reduces the target to a small pile of ash.
admin-smite-buffering-description = Causes the target to randomly start buffering, freezing them in place for a short timespan while they load.
--- /dev/null
+cluwne-transform = {CAPITALIZE(THE($target))} turned into a cluwne!
+cluwne-name-prefix = Cluwnified {$target}
equippedPrefix: holding
- type: Storage
capacity: 9999
+
+- type: entity
+ parent: ClothingBackpackClown
+ id: ClothingBackpackCluwne
+ name: cursed giggles von honkerton
+ suffix: Unremoveable
+ description: Cursed giggles von honkerton backpack.
+ components:
+ - type: Sprite
+ sprite: Clothing/Back/Backpacks/cluwne.rsi
+ - type: Unremoveable
fiberMaterial: fibers-synthetic
fiberColor: fibers-black
- type: FingerprintMask
+
+- type: entity
+ parent: ClothingHandsGlovesColorWhite
+ id: ClothingHandsGlovesCluwne
+ name: cluwne hands
+ suffix: Unremoveable
+ description: A cursed pair of cluwne hands.
+ components:
+ - type: Unremoveable
- type: DiseaseProtection
protection: 0.1
- type: IdentityBlocker
+
+- type: entity
+ parent: ClothingMaskClown
+ id: ClothingMaskCluwne
+ name: cluwne face and hair
+ suffix: Unremoveable
+ description: Cursed cluwne face and hair.
+ components:
+ - type: Sprite
+ sprite: Clothing/Mask/cluwne.rsi
+ - type: Clothing
+ sprite: Clothing/Mask/cluwne.rsi
+ - type: Unremoveable
+ - type: AddAccentClothing
+ accent: StutteringAccent
sprite: Clothing/Shoes/Specific/jester.rsi
- type: Clothing
sprite: Clothing/Shoes/Specific/jester.rsi
+
+- type: entity
+ parent: ClothingShoesClown
+ id: ClothingShoesCluwne
+ name: cluwne shoes
+ suffix: Unremoveable
+ description: "Cursed pair of cluwne shoes."
+ components:
+ - type: Sprite
+ sprite: Clothing/Shoes/Specific/cluwne.rsi
+ - type: Clothing
+ sprite: Clothing/Shoes/Specific/cluwne.rsi
+ - type: Unremoveable
Slash: 0.8
Piercing: 0.8
+- type: entity
+ parent: ClothingUniformJumpsuitClown
+ id: ClothingUniformJumpsuitCluwne
+ name: cluwne suit
+ suffix: Unremoveable
+ description: Cursed cluwne suit.
+ components:
+ - type: Sprite
+ sprite: Clothing/Uniforms/Jumpsuit/cluwne.rsi
+ - type: Clothing
+ sprite: Clothing/Uniforms/Jumpsuit/cluwne.rsi
+ - type: Unremoveable
+
- type: entity
parent: ClothingUniformBase
id: ClothingUniformJumpsuitDameDane
- ClothingNeckNonBinaryPin
- ClothingNeckPansexualPin
- ClothingNeckTransPin
+ - CluwneHorn
rareChance: 0.01
prototypes:
- Lighter
types:
Blunt: 200
+- type: entity
+ parent: MobHuman
+ id: MobCluwne
+ name: person
+ description: A polymorphed unfortunate.
+ components:
+ - type: Cluwne
id: NukeOp
components:
- type: NukeOperative
+
+- type: entity
+ id: RandomHumanoidSpawnerCluwne
+ name: Cluwne
+ suffix: spawns a cluwne
+ components:
+ - type: Sprite
+ netsync: false
+ sprite: Markers/jobs.rsi
+ state: cluwne
+ - type: RandomHumanoidSpawner
+ settings: Cluwne
+ - type: RandomMetadata
+ nameSegments:
+ - names_first
+ - names_last
+
+
+- type: randomHumanoidSettings
+ id: Cluwne
+ randomizeName: false
+ components:
+ - type: GhostTakeoverAvailable
+ name: Cluwne
+ description: Become a pitiful cluwne, your only goal in life is to find a sweet release from your suffering (usually by being beaten to death). A cluwne is not an antagonist but may defend itself. Crewmembers may murder cluwnes freely.
+ - type: Cluwne
- type: Item
sprite: Objects/Storage/happyhonk/cluwne.rsi
heldPrefix: box
+ - type: StorageFill
+ contents:
+ - id: CluwneHorn
- type: entity
id: FoodMealHappyHonkClown
borderColor: "#774705"
- type: Icon
state: pda-detective
+
+- type: entity
+ parent: ClownPDA
+ id: CluwnePDA
+ name: cluwne PDA
+ suffix: Unremoveable
+ description: Cursed cluwne PDA.
+ components:
+ - type: PDA
+ id: CluwneIDCard
+ state: pda-cluwne
+ - type: PDABorderColor
+ borderColor: "#1c8f4d"
+ - type: Icon
+ state: pda-cluwne
+ penSlot:
+ startingItem: CrayonGreen
+ ejectSound: /Audio/Items/bikehorn.ogg
+ priority: -1
+ whitelist:
+ tags:
+ - Write
+ - type: Unremoveable
+
types:
Blunt: 0
+- type: entity
+ parent: BikeHorn
+ id: CluwneHorn
+ name: broken bike horn
+ description: A broken horn off of a bicycle.
+ components:
+ - type: Sprite
+ sprite: Objects/Fun/cluwnehorn.rsi
+ state: icon
+ - type: Item
+ sprite: Objects/Fun/cluwnehorn.rsi
+ size: 5
+ - type: Clothing
+ sprite: Objects/Fun/cluwnehorn.rsi
+ slots: [Belt]
+ quickEquip: false
+ - type: EmitSoundOnUse
+ sound:
+ collection: CluwneHorn
+ params:
+ variation: 0.246
+
- type: entity
parent: BikeHorn
id: GoldenBikeHorn
slots: [Belt]
- type: Prayable
- type: StaticPrice
- price: 1000
\ No newline at end of file
+ price: 1000
- type: Item
heldPrefix: gold
+
+- type: entity
+ parent: IDCardStandard
+ id: CluwneIDCard
+ name: cluwne ID card
+ suffix: Unremoveable
+ components:
+ - type: Sprite
+ layers:
+ - state: default
+ - state: idcluwne
+ - type: IdCard
+ jobTitle: Cluwne
+ - type: Unremoveable
proto: ProjectilePolyboltDoor
capacity: 10
count: 10
+
+- type: entity
+ name: wand of cluwning
+ parent: WeaponWandPolymorphBase
+ id: WeaponWandCluwne
+ description: Make their situation worse by turning them into a cluwne.
+ components:
+ - type: BasicEntityAmmoProvider
+ proto: ProjectilePolyboltCluwne
+ capacity: 3
+ count: 3
-- type: entity
+- type: entity
id: ProjectileFireball
name: fireball
description: You better GITTAH WEIGH.
types:
Piercing: 300
ignoreResistances: true
+
+- type: entity
+ id: ProjectilePolyboltCluwne
+ parent: ProjectilePolyboltBase
+ name: cluwne polybolt
+ description: knoH KnoH!
+ noSpawn: true
+ components:
+ - type: PolymorphOnCollide
+ polymorph: WizardForcedCluwne
+ whitelist:
+ components:
+ - Body
revertOnCrit: false
revertOnDeath: false
+- type: polymorph
+ id: WizardForcedCluwne
+ entity: MobCluwne
+ forced: true
+ transferName: true
+ transferHumanoidAppearance: true
+ inventory: Transfer
+ revertOnDeath: true
+
# this is a test for transferring some visual appearance stuff
- type: polymorph
id: TestHumanMorph
--- /dev/null
+- type: startingGear
+ id: CluwneGear
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitCluwne
+ back: ClothingBackpackCluwne
+ shoes: ClothingShoesCluwne
+ mask: ClothingMaskCluwne
+ id: CluwnePDA
+ gloves: ClothingHandsGlovesCluwne
+ pocket1: CluwneHorn
id: BikeHorn
files:
- /Audio/Items/bikehorn.ogg
+
+- type: soundCollection
+ id: CluwneHorn
+ files:
+ - /Audio/Items/brokenbikehorn.ogg
+
files:
- /Audio/Voice/Zombie/zombie-1.ogg
- /Audio/Voice/Zombie/zombie-2.ogg
- - /Audio/Voice/Zombie/zombie-3.ogg
\ No newline at end of file
+ - /Audio/Voice/Zombie/zombie-3.ogg
+
+- type: soundCollection
+ id: CluwneScreams
+ files:
+ - /Audio/Voice/Cluwne/cluwnelaugh1.ogg
+ - /Audio/Voice/Cluwne/cluwnelaugh2.ogg
+ - /Audio/Voice/Cluwne/cluwnelaugh3.ogg
interval: 5.0
chance: 0.1
withChat: false
+# Cluwne
+- type: autoEmote
+ id: CluwneGiggle
+ emote: Scream
+ interval: 5.0
+ chance: 0.5
+ withChat: false
path: /Audio/Animals/mouse_squeak.ogg
params:
variation: 0.125
+
+- type: emoteSounds
+ id: Cluwne
+ sound:
+ collection: CluwneScreams
+ params:
+ variation: 0.125
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by brainfood1183 (github) for ss14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-BACKPACK",
+ "directions": 4
+ }
+ ]
+}
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by brainfood1183 (github) for ss14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-MASK",
+ "directions": 4
+ }
+ ]
+}
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by brainfood1183 (github) for ss14.",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-FEET",
+ "directions": 4
+ }
+ ]
+}
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by brainfood1183 (github) for ss14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-INNERCLOTHING",
+ "directions": 4
+ }
+ ]
+}
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi, librarian by Peptide. cburn made by brainfood1183 (github)",
+ "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi, librarian by Peptide. cburn and cluwne made by brainfood1183 (github)",
"size": {
"x": 32,
"y": 32
},
{
"name": "warden"
+ },
+ {
+ "name": "cluwne"
},
{
"name": "musician"
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/59f2a4e10e5ba36033c9734ddebfbbdc6157472d",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/59f2a4e10e5ba36033c9734ddebfbbdc6157472d, pda-cluwne made by brainfood1183 (github) ss14",
"size": {
"x": 32,
"y": 32
},
{
"name": "pda-ert"
+ },
+ {
+ "name": "pda-cluwne"
}
]
}
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by brainfood1183 (github) for ss14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-BELT",
+ "directions": 4
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ }
+ ]
+}
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/d917f4c2a088419d5c3aec7656b7ff8cebd1822e",
+ "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/d917f4c2a088419d5c3aec7656b7ff8cebd1822e idcluwne made by brainfood1183 (github) for ss14",
"size": {
"x": 32,
"y": 32
},
{
"name": "syndie"
+ },
+ {
+ "name": "idcluwne"
},
{
"name": "gold-inhand-left",