Add AutoEmote comp/system, updates to zombie code (#13932)
* Add AutoEmote comp/system * Reduce groan chance so it's the same as before Old code did 0.2 and then 0.5, now it's just one Prob(0.1) * Fix typo, curTime var, don't log Resolve * Maybe fix pausing? * Fix mistake * Update NextEmoteTime if an auto emote is removed * Fix stuff Get CurTime outside update loop Use MapInit instead of ComponentInit Fix a typo in a comment Debug assert prototype ID in RemoveEmote Do += PausedTime in OnUnpaused Add prototype as arg to ResetTimer to avoid an indexing
This commit is contained in:
32
Content.Server/Chat/AutoEmoteComponent.cs
Normal file
32
Content.Server/Chat/AutoEmoteComponent.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
namespace Content.Server.Chat;
|
||||||
|
|
||||||
|
using Content.Server.Chat.Systems;
|
||||||
|
using Content.Shared.Chat.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes an entity to automatically emote at a set interval.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(AutoEmoteSystem))]
|
||||||
|
public sealed class AutoEmoteComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A set of emotes that the entity will preform.
|
||||||
|
/// <see cref="AutoEmotePrototype"/>
|
||||||
|
/// </summary>
|
||||||
|
[DataField("emotes", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AutoEmotePrototype>)), ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public HashSet<string> Emotes = new HashSet<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary storing the time of the next emote attempt for each emote.
|
||||||
|
/// Uses AutoEmotePrototype IDs as keys.
|
||||||
|
/// <summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)] //TODO: make this a datafield and (de)serialize values as time offsets when https://github.com/space-wizards/RobustToolbox/issues/3768 is fixed
|
||||||
|
public Dictionary<string, TimeSpan> EmoteTimers = new Dictionary<string, TimeSpan>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time of the next emote. Redundant, but avoids having to iterate EmoteTimers each update.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public TimeSpan NextEmoteTime = TimeSpan.MaxValue;
|
||||||
|
}
|
||||||
134
Content.Server/Chat/Systems/AutoEmoteSystem.cs
Normal file
134
Content.Server/Chat/Systems/AutoEmoteSystem.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
namespace Content.Server.Chat.Systems;
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Chat.Prototypes;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
public sealed class AutoEmoteSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AutoEmoteComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<AutoEmoteComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
var curTime = _gameTiming.CurTime;
|
||||||
|
foreach (var autoEmote in EntityQuery<AutoEmoteComponent>())
|
||||||
|
{
|
||||||
|
var uid = autoEmote.Owner;
|
||||||
|
|
||||||
|
if (autoEmote.NextEmoteTime > curTime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach ((var key, var time) in autoEmote.EmoteTimers)
|
||||||
|
{
|
||||||
|
if (time > curTime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var autoEmotePrototype = _prototypeManager.Index<AutoEmotePrototype>(key);
|
||||||
|
ResetTimer(uid, key, autoEmote, autoEmotePrototype);
|
||||||
|
|
||||||
|
if (!_random.Prob(autoEmotePrototype.Chance))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (autoEmotePrototype.WithChat)
|
||||||
|
{
|
||||||
|
_chatSystem.TryEmoteWithChat(uid, autoEmotePrototype.EmoteId, autoEmotePrototype.HiddenFromChatWindow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatSystem.TryEmoteWithoutChat(uid, autoEmotePrototype.EmoteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(EntityUid uid, AutoEmoteComponent autoEmote, MapInitEvent args)
|
||||||
|
{
|
||||||
|
// Start timers
|
||||||
|
foreach (var autoEmotePrototypeId in autoEmote.Emotes)
|
||||||
|
{
|
||||||
|
ResetTimer(uid, autoEmotePrototypeId, autoEmote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnpaused(EntityUid uid, AutoEmoteComponent autoEmote, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
foreach (var key in autoEmote.EmoteTimers.Keys)
|
||||||
|
{
|
||||||
|
autoEmote.EmoteTimers[key] += args.PausedTime;
|
||||||
|
}
|
||||||
|
autoEmote.NextEmoteTime += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to add an emote to the entity, which will be performed at an interval.
|
||||||
|
/// </summary>
|
||||||
|
public bool AddEmote(EntityUid uid, string autoEmotePrototypeId, AutoEmoteComponent? autoEmote = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref autoEmote, logMissing: false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (autoEmote.Emotes.Contains(autoEmotePrototypeId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
autoEmote.Emotes.Add(autoEmotePrototypeId);
|
||||||
|
ResetTimer(uid, autoEmotePrototypeId, autoEmote);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop preforming an emote.
|
||||||
|
/// </summary>
|
||||||
|
public bool RemoveEmote(EntityUid uid, string autoEmotePrototypeId, AutoEmoteComponent? autoEmote = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref autoEmote, logMissing: false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DebugTools.Assert(_prototypeManager.HasIndex<AutoEmotePrototype>(autoEmotePrototypeId), "Prototype not found. Did you make a typo?");
|
||||||
|
|
||||||
|
if (!autoEmote.EmoteTimers.Remove(autoEmotePrototypeId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
autoEmote.NextEmoteTime = autoEmote.EmoteTimers.Values.Min();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset the timer for a specific emote, or return false if it doesn't exist.
|
||||||
|
/// </summary>
|
||||||
|
public bool ResetTimer(EntityUid uid, string autoEmotePrototypeId, AutoEmoteComponent? autoEmote = null, AutoEmotePrototype? autoEmotePrototype = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref autoEmote))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!autoEmote.Emotes.Contains(autoEmotePrototypeId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
autoEmotePrototype ??= _prototypeManager.Index<AutoEmotePrototype>(autoEmotePrototypeId);
|
||||||
|
|
||||||
|
var curTime = _gameTiming.CurTime;
|
||||||
|
var time = curTime + autoEmotePrototype.Interval;
|
||||||
|
autoEmote.EmoteTimers[autoEmotePrototypeId] = time;
|
||||||
|
|
||||||
|
if (autoEmote.NextEmoteTime > time || autoEmote.NextEmoteTime <= curTime)
|
||||||
|
autoEmote.NextEmoteTime = time;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,33 @@
|
|||||||
namespace Content.Server.Zombies;
|
namespace Content.Server.Zombies;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates a zombie that is "alive", i.e not crit/dead.
|
||||||
|
/// Causes it to emote when damaged.
|
||||||
|
/// TODO: move this to generic EmoteWhenDamaged comp/system.
|
||||||
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class ActiveZombieComponent : Component
|
public sealed class ActiveZombieComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chance that on a random attempt
|
/// What emote to preform.
|
||||||
/// that a zombie will do a groan
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float GroanChance = 0.2f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Minimum time between groans
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float GroanCooldown = 2;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The length of time between each zombie's random groan
|
|
||||||
/// attempt.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float RandomGroanAttempt = 5;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public string GroanEmoteId = "Scream";
|
public string GroanEmoteId = "Scream";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum time between groans.
|
||||||
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float LastDamageGroanCooldown = 0f;
|
public TimeSpan DamageGroanCooldown = TimeSpan.FromSeconds(2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chance to groan.
|
||||||
|
/// </summary>
|
||||||
|
public float DamageGroanChance = 0.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last time the zombie groaned from taking damage.
|
||||||
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float Accumulator = 0f;
|
public TimeSpan LastDamageGroan = TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Server.Chat;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.Cloning;
|
using Content.Server.Cloning;
|
||||||
using Content.Server.Disease;
|
using Content.Server.Disease;
|
||||||
@@ -7,14 +8,10 @@ using Content.Server.Disease.Components;
|
|||||||
using Content.Server.Drone.Components;
|
using Content.Server.Drone.Components;
|
||||||
using Content.Server.Humanoid;
|
using Content.Server.Humanoid;
|
||||||
using Content.Server.Inventory;
|
using Content.Server.Inventory;
|
||||||
using Content.Server.Speech;
|
|
||||||
using Content.Shared.Bed.Sleep;
|
using Content.Shared.Bed.Sleep;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Server.Chat.Systems;
|
|
||||||
using Content.Server.Emoting.Systems;
|
using Content.Server.Emoting.Systems;
|
||||||
using Content.Server.Speech.EntitySystems;
|
using Content.Server.Speech.EntitySystems;
|
||||||
using Content.Shared.Movement.Systems;
|
|
||||||
using Content.Shared.Bed.Sleep;
|
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Disease.Events;
|
using Content.Shared.Disease.Events;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
@@ -24,6 +21,7 @@ using Content.Shared.Weapons.Melee.Events;
|
|||||||
using Content.Shared.Zombies;
|
using Content.Shared.Zombies;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Zombies
|
namespace Content.Server.Zombies
|
||||||
{
|
{
|
||||||
@@ -34,6 +32,8 @@ namespace Content.Server.Zombies
|
|||||||
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
|
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
|
||||||
[Dependency] private readonly ServerInventorySystem _inv = default!;
|
[Dependency] private readonly ServerInventorySystem _inv = default!;
|
||||||
[Dependency] private readonly ChatSystem _chat = default!;
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
|
[Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||||
@@ -52,7 +52,6 @@ namespace Content.Server.Zombies
|
|||||||
SubscribeLocalEvent<ActiveZombieComponent, DamageChangedEvent>(OnDamage);
|
SubscribeLocalEvent<ActiveZombieComponent, DamageChangedEvent>(OnDamage);
|
||||||
SubscribeLocalEvent<ActiveZombieComponent, AttemptSneezeCoughEvent>(OnSneeze);
|
SubscribeLocalEvent<ActiveZombieComponent, AttemptSneezeCoughEvent>(OnSneeze);
|
||||||
SubscribeLocalEvent<ActiveZombieComponent, TryingToSleepEvent>(OnSleepAttempt);
|
SubscribeLocalEvent<ActiveZombieComponent, TryingToSleepEvent>(OnSleepAttempt);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSleepAttempt(EntityUid uid, ActiveZombieComponent component, ref TryingToSleepEvent args)
|
private void OnSleepAttempt(EntityUid uid, ActiveZombieComponent component, ref TryingToSleepEvent args)
|
||||||
@@ -77,16 +76,30 @@ namespace Content.Server.Zombies
|
|||||||
|
|
||||||
private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args)
|
private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args)
|
||||||
{
|
{
|
||||||
|
//BUG: this won't work when an entity becomes a zombie some other way, such as admin smite
|
||||||
if (args.NewMobState == MobState.Alive)
|
if (args.NewMobState == MobState.Alive)
|
||||||
|
{
|
||||||
|
// Groaning when damaged
|
||||||
EnsureComp<ActiveZombieComponent>(uid);
|
EnsureComp<ActiveZombieComponent>(uid);
|
||||||
|
|
||||||
|
// Random groaning
|
||||||
|
EnsureComp<AutoEmoteComponent>(uid);
|
||||||
|
_autoEmote.AddEmote(uid, "ZombieGroan");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
// Stop groaning when damaged
|
||||||
RemComp<ActiveZombieComponent>(uid);
|
RemComp<ActiveZombieComponent>(uid);
|
||||||
|
|
||||||
|
// Stop random groaning
|
||||||
|
_autoEmote.RemoveEmote(uid, "ZombieGroan");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDamage(EntityUid uid, ActiveZombieComponent component, DamageChangedEvent args)
|
private void OnDamage(EntityUid uid, ActiveZombieComponent component, DamageChangedEvent args)
|
||||||
{
|
{
|
||||||
if (args.DamageIncreased)
|
if (args.DamageIncreased)
|
||||||
DoGroan(uid, component);
|
AttemptDamageGroan(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSneeze(EntityUid uid, ActiveZombieComponent component, ref AttemptSneezeCoughEvent args)
|
private void OnSneeze(EntityUid uid, ActiveZombieComponent component, ref AttemptSneezeCoughEvent args)
|
||||||
@@ -166,40 +179,16 @@ namespace Content.Server.Zombies
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoGroan(EntityUid uid, ActiveZombieComponent component)
|
private void AttemptDamageGroan(EntityUid uid, ActiveZombieComponent component)
|
||||||
{
|
{
|
||||||
if (component.LastDamageGroanCooldown > 0)
|
if (component.LastDamageGroan + component.DamageGroanCooldown > _gameTiming.CurTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_robustRandom.Prob(component.DamageGroanChance))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_robustRandom.Prob(0.5f)) //this message is never seen by players so it just says this for admins
|
|
||||||
// What? Is this REALLY the best way we have of letting admins know there are zombies in a round?
|
|
||||||
// [automated maintainer groan]
|
|
||||||
_chat.TrySendInGameICMessage(uid, "[automated zombie groan]", InGameICChatType.Speak, false);
|
|
||||||
else
|
|
||||||
_chat.TryEmoteWithoutChat(uid, component.GroanEmoteId);
|
_chat.TryEmoteWithoutChat(uid, component.GroanEmoteId);
|
||||||
|
component.LastDamageGroan = _gameTiming.CurTime;
|
||||||
component.LastDamageGroanCooldown = component.GroanCooldown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
foreach (var zombiecomp in EntityQuery<ActiveZombieComponent>())
|
|
||||||
{
|
|
||||||
zombiecomp.Accumulator += frameTime;
|
|
||||||
zombiecomp.LastDamageGroanCooldown -= frameTime;
|
|
||||||
|
|
||||||
if (zombiecomp.Accumulator < zombiecomp.RandomGroanAttempt)
|
|
||||||
continue;
|
|
||||||
zombiecomp.Accumulator -= zombiecomp.RandomGroanAttempt;
|
|
||||||
|
|
||||||
if (!_robustRandom.Prob(zombiecomp.GroanChance))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//either do a random accent line or scream
|
|
||||||
DoGroan(zombiecomp.Owner, zombiecomp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Content.Server.Disease.Components;
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Speech.Components;
|
using Content.Server.Speech.Components;
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
@@ -29,7 +28,6 @@ using Content.Shared.Humanoid;
|
|||||||
using Content.Shared.Mobs;
|
using Content.Shared.Mobs;
|
||||||
using Content.Shared.Movement.Systems;
|
using Content.Shared.Movement.Systems;
|
||||||
using Content.Shared.Weapons.Melee;
|
using Content.Shared.Weapons.Melee;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
|
|
||||||
namespace Content.Server.Zombies
|
namespace Content.Server.Zombies
|
||||||
{
|
{
|
||||||
|
|||||||
43
Content.Shared/Chat/Prototypes/AutoEmotePrototype.cs
Normal file
43
Content.Shared/Chat/Prototypes/AutoEmotePrototype.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
namespace Content.Shared.Chat.Prototypes;
|
||||||
|
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
[Prototype("autoEmote")]
|
||||||
|
public sealed class AutoEmotePrototype : IPrototype
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[IdDataField]
|
||||||
|
public string ID { get; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the emote prototype.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||||
|
public string EmoteId = String.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How often an attempt at the emote will be made.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("interval", required: true)]
|
||||||
|
public TimeSpan Interval;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Probability of performing the emote each interval.
|
||||||
|
/// <summary>
|
||||||
|
[DataField("chance")]
|
||||||
|
public float Chance = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Also send the emote in chat.
|
||||||
|
/// <summary>
|
||||||
|
[DataField("withChat")]
|
||||||
|
public bool WithChat = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hide the chat message from the chat window, only showing the popup.
|
||||||
|
/// This does nothing if WithChat is false.
|
||||||
|
/// <summary>
|
||||||
|
[DataField("hiddenFromChatWindow")]
|
||||||
|
public bool HiddenFromChatWindow = false;
|
||||||
|
}
|
||||||
7
Resources/Prototypes/Voice/auto_emotes.yml
Normal file
7
Resources/Prototypes/Voice/auto_emotes.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Zombie
|
||||||
|
- type: autoEmote
|
||||||
|
id: ZombieGroan
|
||||||
|
emote: Scream
|
||||||
|
interval: 5.0
|
||||||
|
chance: 0.1
|
||||||
|
withChat: false
|
||||||
Reference in New Issue
Block a user