Carp wave spawner and dragons as an actual event (#10254)

This commit is contained in:
metalgearsloth
2022-08-08 10:18:14 +10:00
committed by GitHub
parent 3d850c6592
commit a29d8b9fa2
60 changed files with 1264 additions and 569 deletions

View File

@@ -0,0 +1,9 @@
using Content.Shared.Dragon;
namespace Content.Client.Dragon;
[RegisterComponent]
public sealed class DragonRiftComponent : SharedDragonRiftComponent
{
}

View File

@@ -0,0 +1,51 @@
using Content.Shared.Dragon;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
namespace Content.Client.Dragon;
public sealed class DragonSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DragonRiftComponent, ComponentHandleState>(OnRiftHandleState);
}
private void OnRiftHandleState(EntityUid uid, DragonRiftComponent component, ref ComponentHandleState args)
{
if (args.Current is not DragonRiftComponentState state)
return;
if (component.State == state.State) return;
component.State = state.State;
TryComp<SpriteComponent>(uid, out var sprite);
TryComp<PointLightComponent>(uid, out var light);
if (sprite == null && light == null)
return;
switch (state.State)
{
case DragonRiftState.Charging:
sprite?.LayerSetColor(0, Color.FromHex("#569fff"));
if (light != null)
light.Color = Color.FromHex("#366db5");
break;
case DragonRiftState.AlmostFinished:
sprite?.LayerSetColor(0, Color.FromHex("#cf4cff"));
if (light != null)
light.Color = Color.FromHex("#9e2fc1");
break;
case DragonRiftState.Finished:
sprite?.LayerSetColor(0, Color.FromHex("#edbc36"));
if (light != null)
light.Color = Color.FromHex("#cbaf20");
break;
}
}
}

View File

@@ -0,0 +1,58 @@
using Content.Shared.Sprite;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Reflection;
namespace Content.Client.Sprite;
public sealed class RandomSpriteSystem : SharedRandomSpriteSystem
{
[Dependency] private readonly IReflectionManager _reflection = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RandomSpriteComponent, ComponentHandleState>(OnHandleState);
}
private void OnHandleState(EntityUid uid, RandomSpriteComponent component, ref ComponentHandleState args)
{
if (args.Current is not RandomSpriteColorComponentState state)
return;
if (state.Selected.Equals(component.Selected))
return;
component.Selected.Clear();
component.Selected.EnsureCapacity(state.Selected.Count);
foreach (var layer in state.Selected)
{
component.Selected.Add(layer.Key, layer.Value);
}
UpdateAppearance(uid, component);
}
private void UpdateAppearance(EntityUid uid, RandomSpriteComponent component, SpriteComponent? sprite = null)
{
if (!Resolve(uid, ref sprite, false))
return;
foreach (var layer in component.Selected)
{
object key;
if (_reflection.TryParseEnumReference(layer.Key, out var @enum))
{
key = @enum;
}
else
{
key = layer.Key;
}
sprite.LayerSetState(key, layer.Value.State);
sprite.LayerSetColor(key, layer.Value.Color ?? Color.White);
}
}
}

View File

@@ -32,11 +32,42 @@ namespace Content.Server.Dragon
[DataField("devourAction")]
public EntityTargetAction? DevourAction;
[DataField("spawnActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
public string SpawnActionId = "DragonSpawn";
/// <summary>
/// If we have active rifts.
/// </summary>
[ViewVariables, DataField("rifts")]
public List<EntityUid> Rifts = new();
[DataField("spawnAction")]
public InstantAction? SpawnAction;
public bool Weakened => WeakenedAccumulator > 0f;
/// <summary>
/// When any rift is destroyed how long is the dragon weakened for
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("weakenedDuration")]
public float WeakenedDuration = 120f;
/// <summary>
/// Has a rift been destroyed and the dragon in a temporary weakened state?
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("weakenedAccumulator")]
public float WeakenedAccumulator = 0f;
[ViewVariables(VVAccess.ReadWrite), DataField("riftAccumulator")]
public float RiftAccumulator = 0f;
/// <summary>
/// Maximum time the dragon can go without spawning a rift before they die.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("maxAccumulator")] public float RiftMaxAccumulator = 300f;
/// <summary>
/// Spawns a rift which can summon more mobs.
/// </summary>
[ViewVariables, DataField("spawnRiftAction")]
public InstantAction? SpawnRiftAction;
[ViewVariables(VVAccess.ReadWrite), DataField("riftPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string RiftPrototype = "CarpRift";
/// <summary>
/// The amount of time it takes to devour something
@@ -48,14 +79,7 @@ namespace Content.Server.Dragon
public float StructureDevourTime = 10f;
[DataField("devourTime")]
public float DevourTime = 2f;
[DataField("spawnCount")] public int SpawnsLeft = 2;
[DataField("maxSpawnCount")] public int MaxSpawns = 2;
[DataField("spawnProto", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? SpawnPrototype = "MobCarpDragon";
public float DevourTime = 3f;
[ViewVariables(VVAccess.ReadWrite), DataField("soundDeath")]
public SoundSpecifier? SoundDeath = new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg");
@@ -76,7 +100,7 @@ namespace Content.Server.Dragon
public SoundSpecifier? SoundRoar =
new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg")
{
Params = AudioParams.Default.WithVolume(-3f),
Params = AudioParams.Default.WithVolume(3f),
};
public CancellationTokenSource? CancelToken;
@@ -103,5 +127,5 @@ namespace Content.Server.Dragon
public sealed class DragonDevourActionEvent : EntityTargetActionEvent {}
public sealed class DragonSpawnActionEvent : InstantActionEvent {}
public sealed class DragonSpawnRiftActionEvent : InstantActionEvent {}
}

View File

@@ -0,0 +1,40 @@
using Content.Shared.Dragon;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Dragon;
[RegisterComponent]
public sealed class DragonRiftComponent : SharedDragonRiftComponent
{
/// <summary>
/// Dragon that spawned this rift.
/// </summary>
[ViewVariables, DataField("dragon")] public EntityUid Dragon;
/// <summary>
/// How long the rift has been active.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("accumulator")]
public float Accumulator = 0f;
/// <summary>
/// The maximum amount we can accumulate before becoming impervious.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("maxAccumuluator")] public float MaxAccumulator = 300f;
/// <summary>
/// Accumulation of the spawn timer.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("spawnAccumulator")]
public float SpawnAccumulator = 60f;
/// <summary>
/// How long it takes for a new spawn to be added.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("spawnCooldown")]
public float SpawnCooldown = 60f;
[ViewVariables(VVAccess.ReadWrite), DataField("spawn", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string SpawnPrototype = "MobCarpDragon";
}

View File

@@ -0,0 +1,67 @@
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.StationEvents.Components;
using Content.Shared.Dragon;
using Robust.Server.GameObjects;
using Robust.Shared.Random;
namespace Content.Server.Dragon;
public sealed partial class DragonSystem
{
public override string Prototype => "Dragon";
private int RiftsMet(DragonComponent component)
{
var finished = 0;
foreach (var rift in component.Rifts)
{
if (!TryComp<DragonRiftComponent>(rift, out var drift) ||
drift.State != DragonRiftState.Finished)
continue;
finished++;
}
return finished;
}
public override void Started()
{
var spawnLocations = EntityManager.EntityQuery<IMapGridComponent, TransformComponent>().ToList();
if (spawnLocations.Count == 0)
return;
var location = _random.Pick(spawnLocations);
Spawn("MobDragon", location.Item2.MapPosition);
}
public override void Ended()
{
return;
}
private void OnRiftRoundEnd(RoundEndTextAppendEvent args)
{
if (!RuleAdded)
return;
args.AddLine(Loc.GetString("dragon-round-end-summary"));
foreach (var dragon in EntityQuery<DragonComponent>(true))
{
var met = RiftsMet(dragon);
if (TryComp<ActorComponent>(dragon.Owner, out var actor))
{
args.AddLine(Loc.GetString("dragon-round-end-dragon-player", ("name", dragon.Owner), ("rifts", met), ("player", actor.PlayerSession)));
}
else
{
args.AddLine(Loc.GetString("dragon-round-end-dragon", ("name", dragon.Owner), ("rifts", met)));
}
}
}
}

View File

@@ -9,15 +9,27 @@ using Content.Shared.MobState.Components;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using System.Threading;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Shared.Damage;
using Content.Shared.Dragon;
using Content.Shared.Examine;
using Content.Shared.Movement.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Random;
namespace Content.Server.Dragon
{
public sealed class DragonSystem : EntitySystem
public sealed partial class DragonSystem : GameRuleSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
@@ -26,13 +38,200 @@ namespace Content.Server.Dragon
base.Initialize();
SubscribeLocalEvent<DragonComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<DragonComponent, DragonDevourComplete>(OnDragonDevourComplete);
SubscribeLocalEvent<DragonComponent, DragonDevourActionEvent>(OnDevourAction);
SubscribeLocalEvent<DragonComponent, DragonSpawnActionEvent>(OnDragonSpawnAction);
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
SubscribeLocalEvent<DragonComponent, DragonStructureDevourComplete>(OnDragonStructureDevourComplete);
SubscribeLocalEvent<DragonComponent, DragonDevourCancelledEvent>(OnDragonDevourCancelled);
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnRiftShutdown);
SubscribeLocalEvent<DragonRiftComponent, ComponentGetState>(OnRiftGetState);
SubscribeLocalEvent<DragonRiftComponent, AnchorStateChangedEvent>(OnAnchorChange);
SubscribeLocalEvent<DragonRiftComponent, ExaminedEvent>(OnRiftExamined);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var comp in EntityQuery<DragonComponent>())
{
if (comp.WeakenedAccumulator > 0f)
{
comp.WeakenedAccumulator -= frameTime;
// No longer weakened.
if (comp.WeakenedAccumulator < 0f)
{
comp.WeakenedAccumulator = 0f;
_movement.RefreshMovementSpeedModifiers(comp.Owner);
}
}
// At max rifts
if (comp.Rifts.Count >= 3)
{
continue;
}
// If there's an active rift don't accumulate.
if (comp.Rifts.Count > 0)
{
var lastRift = comp.Rifts[^1];
if (TryComp<DragonRiftComponent>(lastRift, out var rift) && rift.State != DragonRiftState.Finished)
{
comp.RiftAccumulator = 0f;
continue;
}
}
comp.RiftAccumulator += frameTime;
// Delete it, naughty dragon!
if (comp.RiftAccumulator >= comp.RiftMaxAccumulator)
{
Roar(comp);
QueueDel(comp.Owner);
}
}
foreach (var comp in EntityQuery<DragonRiftComponent>())
{
if (comp.State != DragonRiftState.Finished && comp.Accumulator >= comp.MaxAccumulator)
{
// TODO: When we get autocall you can buff if the rift finishes / 3 rifts are up
// for now they just keep 3 rifts up.
comp.Accumulator = comp.MaxAccumulator;
RemComp<DamageableComponent>(comp.Owner);
comp.State = DragonRiftState.Finished;
Dirty(comp);
}
else
{
comp.Accumulator += frameTime;
}
comp.SpawnAccumulator += frameTime;
if (comp.State < DragonRiftState.AlmostFinished && comp.SpawnAccumulator > comp.MaxAccumulator / 2f)
{
comp.State = DragonRiftState.AlmostFinished;
Dirty(comp);
var location = Transform(comp.Owner).LocalPosition;
_chat.DispatchGlobalAnnouncement(Loc.GetString("carp-rift-warning", ("location", location)), playSound: false, colorOverride: Color.Red);
_audioSystem.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast());
}
if (comp.SpawnAccumulator > comp.SpawnCooldown)
{
comp.SpawnAccumulator -= comp.SpawnCooldown;
Spawn(comp.SpawnPrototype, Transform(comp.Owner).MapPosition);
// TODO: When NPC refactor make it guard the rift.
}
}
}
#region Rift
private void OnRiftExamined(EntityUid uid, DragonRiftComponent component, ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("carp-rift-examine", ("percentage", MathF.Round(component.Accumulator / component.MaxAccumulator * 100))));
}
private void OnAnchorChange(EntityUid uid, DragonRiftComponent component, ref AnchorStateChangedEvent args)
{
if (!args.Anchored && component.State == DragonRiftState.Charging)
{
QueueDel(uid);
}
}
private void OnRiftShutdown(EntityUid uid, DragonRiftComponent component, ComponentShutdown args)
{
if (TryComp<DragonComponent>(component.Dragon, out var dragon) && !dragon.Weakened)
{
foreach (var rift in dragon.Rifts)
{
QueueDel(rift);
}
dragon.Rifts.Clear();
// We can't predict the rift being destroyed anyway so no point adding weakened to shared.
dragon.WeakenedAccumulator = dragon.WeakenedDuration;
_movement.RefreshMovementSpeedModifiers(component.Dragon);
_popupSystem.PopupEntity(Loc.GetString("carp-rift-destroyed"), component.Dragon, Filter.Entities(component.Dragon));
}
}
private void OnRiftGetState(EntityUid uid, DragonRiftComponent component, ref ComponentGetState args)
{
args.State = new DragonRiftComponentState()
{
State = component.State
};
}
private void OnDragonMove(EntityUid uid, DragonComponent component, RefreshMovementSpeedModifiersEvent args)
{
if (component.Weakened)
{
args.ModifySpeed(0.5f, 0.5f);
}
}
private void OnDragonRift(EntityUid uid, DragonComponent component, DragonSpawnRiftActionEvent args)
{
if (component.Weakened)
{
_popupSystem.PopupEntity(Loc.GetString("carp-rift-weakened"), uid, Filter.Entities(uid));
return;
}
if (component.Rifts.Count >= 3)
{
_popupSystem.PopupEntity(Loc.GetString("carp-rift-max"), uid, Filter.Entities(uid));
return;
}
if (component.Rifts.Count > 0 && TryComp<DragonRiftComponent>(component.Rifts[^1], out var rift) && rift.State != DragonRiftState.Finished)
{
_popupSystem.PopupEntity(Loc.GetString("carp-rift-duplicate"), uid, Filter.Entities(uid));
return;
}
var xform = Transform(uid);
// Have to be on a grid fam
if (xform.GridUid == null)
{
_popupSystem.PopupEntity(Loc.GetString("carp-rift-anchor"), uid, Filter.Entities(uid));
return;
}
var carpUid = Spawn(component.RiftPrototype, xform.MapPosition);
component.Rifts.Add(carpUid);
Comp<DragonRiftComponent>(carpUid).Dragon = uid;
_audioSystem.Play("/Audio/Weapons/Guns/Gunshots/rocket_launcher.ogg", Filter.Pvs(carpUid, entityManager: EntityManager), carpUid);
}
#endregion
private void OnShutdown(EntityUid uid, DragonComponent component, ComponentShutdown args)
{
foreach (var rift in component.Rifts)
{
QueueDel(rift);
}
}
private void OnMobStateChanged(EntityUid uid, DragonComponent component, MobStateChangedEvent args)
@@ -59,13 +258,7 @@ namespace Content.Server.Dragon
var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
//Humanoid devours allow dragon to get eggs, corpses included
if (EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
{
// Add a spawn for a consumed humanoid
component.SpawnsLeft = Math.Min(component.SpawnsLeft + 1, component.MaxSpawns);
}
//Non-humanoid mobs can only heal dragon for half the normal amount, with no additional spawn tickets
else
if (!EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
{
ichorInjection.ScaleSolution(0.5f);
}
@@ -87,10 +280,14 @@ namespace Content.Server.Dragon
_audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
}
private void Roar(DragonComponent component)
{
if (component.SoundRoar != null)
_audioSystem.Play(component.SoundRoar, Filter.Pvs(component.Owner, 4f, EntityManager), component.Owner, component.SoundRoar.Params);
}
private void OnStartup(EntityUid uid, DragonComponent component, ComponentStartup args)
{
component.SpawnsLeft = Math.Min(component.SpawnsLeft, component.MaxSpawns);
//Dragon doesn't actually chew, since he sends targets right into his stomach.
//I did it mom, I added ERP content into upstream. Legally!
component.DragonStomach = _containerSystem.EnsureContainer<Container>(uid, "dragon_stomach");
@@ -98,11 +295,10 @@ namespace Content.Server.Dragon
if (component.DevourAction != null)
_actionsSystem.AddAction(uid, component.DevourAction, null);
if (component.SpawnAction != null)
_actionsSystem.AddAction(uid, component.SpawnAction, null);
if (component.SpawnRiftAction != null)
_actionsSystem.AddAction(uid, component.SpawnRiftAction, null);
if (component.SoundRoar != null)
_audioSystem.Play(component.SoundRoar, Filter.Pvs(uid, 4f, EntityManager), uid, component.SoundRoar.Params);
Roar(component);
}
/// <summary>
@@ -117,7 +313,6 @@ namespace Content.Server.Dragon
return;
}
args.Handled = true;
var target = args.Target;
@@ -164,22 +359,6 @@ namespace Content.Server.Dragon
});
}
private void OnDragonSpawnAction(EntityUid dragonuid, DragonComponent component, DragonSpawnActionEvent args)
{
if (component.SpawnPrototype == null)
return;
// If dragon has spawns then add one.
if (component.SpawnsLeft > 0)
{
Spawn(component.SpawnPrototype, Transform(dragonuid).Coordinates);
component.SpawnsLeft--;
return;
}
_popupSystem.PopupEntity(Loc.GetString("dragon-spawn-action-popup-message-fail-no-eggs"), dragonuid, Filter.Entities(dragonuid));
}
private sealed class DragonDevourComplete : EntityEventArgs
{
public EntityUid User { get; }

View File

@@ -15,8 +15,6 @@ namespace Content.Server.RatKing
{
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly DiseaseSystem _disease = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly TransformSystem _xform = default!;

View File

@@ -1,13 +0,0 @@
namespace Content.Server.Sprite.Components
{
[RegisterComponent]
public sealed class RandomSpriteColorComponent : Component
{
// This should handle random states + colors for layers.
// Saame with RandomSpriteState
[DataField("selected")] public string? SelectedColor;
[DataField("state")] public string BaseState = "error";
[DataField("colors")] public readonly Dictionary<string, Color> Colors = new();
}
}

View File

@@ -1,10 +0,0 @@
namespace Content.Server.Sprite.Components
{
[RegisterComponent]
public sealed class RandomSpriteStateComponent : Component
{
[DataField("spriteStates")] public List<string>? SpriteStates;
[DataField("spriteLayer")] public int SpriteLayer;
}
}

View File

@@ -1,46 +1,53 @@
using Content.Server.Sprite.Components;
using Content.Shared.Random.Helpers;
using Robust.Server.GameObjects;
using System.Linq;
using Content.Shared.Decals;
using Content.Shared.Sprite;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Sprite;
public sealed class RandomSpriteSystem: EntitySystem
public sealed class RandomSpriteSystem: SharedRandomSpriteSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RandomSpriteColorComponent, ComponentStartup>(OnSpriteColorStartup);
SubscribeLocalEvent<RandomSpriteColorComponent, MapInitEvent>(OnSpriteColorMapInit);
SubscribeLocalEvent<RandomSpriteStateComponent, MapInitEvent>(OnSpriteStateMapInit);
SubscribeLocalEvent<RandomSpriteComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<RandomSpriteComponent, MapInitEvent>(OnMapInit);
}
private void OnSpriteColorStartup(EntityUid uid, RandomSpriteColorComponent component, ComponentStartup args)
private void OnMapInit(EntityUid uid, RandomSpriteComponent component, MapInitEvent args)
{
UpdateColor(component);
if (component.Selected.Count > 0)
return;
if (component.Available.Count == 0)
return;
var group = _random.Pick(component.Available);
component.Selected.EnsureCapacity(group.Count);
foreach (var layer in group)
{
Color? color = null;
if (!string.IsNullOrEmpty(layer.Value.Color))
color = _random.Pick(_prototype.Index<ColorPalettePrototype>(layer.Value.Color).Colors.Values);
component.Selected.Add(layer.Key, (layer.Value.State, color));
}
Dirty(component);
}
private void OnSpriteColorMapInit(EntityUid uid, RandomSpriteColorComponent component, MapInitEvent args)
private void OnGetState(EntityUid uid, RandomSpriteComponent component, ref ComponentGetState args)
{
component.SelectedColor = _random.Pick(component.Colors.Keys);
UpdateColor(component);
}
private void OnSpriteStateMapInit(EntityUid uid, RandomSpriteStateComponent component, MapInitEvent args)
{
if (component.SpriteStates == null) return;
if (!TryComp<SpriteComponent>(uid, out var spriteComponent)) return;
spriteComponent.LayerSetState(component.SpriteLayer, _random.Pick(component.SpriteStates));
}
private void UpdateColor(RandomSpriteColorComponent component)
{
if (!TryComp<SpriteComponent>(component.Owner, out var spriteComponent) || component.SelectedColor == null) return;
spriteComponent.LayerSetState(0, component.BaseState);
spriteComponent.LayerSetColor(0, component.Colors[component.SelectedColor]);
args.State = new RandomSpriteColorComponentState()
{
Selected = component.Selected,
};
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Dragon;
[Serializable, NetSerializable]
public sealed class DragonRiftComponentState : ComponentState
{
public DragonRiftState State;
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Dragon;
[NetworkedComponent]
public abstract class SharedDragonRiftComponent : Component
{
[ViewVariables, DataField("state")]
public DragonRiftState State = DragonRiftState.Charging;
}
[Serializable, NetSerializable]
public enum DragonRiftState : byte
{
Charging,
AlmostFinished,
Finished,
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Movement.Components;
/// <summary>
/// Is this entity always considered to be touching a wall?
/// i.e. when weightless they're floaty but still have free movement.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class MovementAlwaysTouchingComponent : Component
{
}

View File

@@ -8,6 +8,9 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Movement.Components
{
/// <summary>
/// Ignores gravity entirely.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class MovementIgnoreGravityComponent : Component
{
@@ -17,7 +20,6 @@ namespace Content.Shared.Movement.Components
[DataField("gravityState")] public bool Weightless = false;
}
[NetSerializable, Serializable]
public sealed class MovementIgnoreGravityComponentState : ComponentState
{

View File

@@ -56,55 +56,55 @@ namespace Content.Shared.Movement.Components
/// <summary>
/// Minimum speed a mob has to be moving before applying movement friction.
/// </summary>
[DataField("minimumFrictionSpeed")]
[ViewVariables(VVAccess.ReadWrite), DataField("minimumFrictionSpeed")]
public float MinimumFrictionSpeed = DefaultMinimumFrictionSpeed;
/// <summary>
/// The negative velocity applied for friction when weightless and providing inputs.
/// </summary>
[DataField("weightlessFriction")]
[ViewVariables(VVAccess.ReadWrite), DataField("weightlessFriction")]
public float WeightlessFriction = DefaultWeightlessFriction;
/// <summary>
/// The negative velocity applied for friction when weightless and not providing inputs.
/// This is essentially how much their speed decreases per second.
/// </summary>
[DataField("weightlessFrictionNoInput")]
[ViewVariables(VVAccess.ReadWrite), DataField("weightlessFrictionNoInput")]
public float WeightlessFrictionNoInput = DefaultWeightlessFrictionNoInput;
/// <summary>
/// The movement speed modifier applied to a mob's total input velocity when weightless.
/// </summary>
[DataField("weightlessModifier")]
[ViewVariables(VVAccess.ReadWrite), DataField("weightlessModifier")]
public float WeightlessModifier = DefaultWeightlessModifier;
/// <summary>
/// The acceleration applied to mobs when moving and weightless.
/// </summary>
[DataField("weightlessAcceleration")]
[ViewVariables(VVAccess.ReadWrite), DataField("weightlessAcceleration")]
public float WeightlessAcceleration = DefaultWeightlessAcceleration;
/// <summary>
/// The acceleration applied to mobs when moving.
/// </summary>
[DataField("acceleration")]
[ViewVariables(VVAccess.ReadWrite), DataField("acceleration")]
public float Acceleration = DefaultAcceleration;
/// <summary>
/// The negative velocity applied for friction.
/// </summary>
[DataField("friction")]
[ViewVariables(VVAccess.ReadWrite), DataField("friction")]
public float Friction = DefaultFriction;
/// <summary>
/// The negative velocity applied for friction.
/// </summary>
[DataField("frictionNoInput")] public float? FrictionNoInput = null;
[ViewVariables(VVAccess.ReadWrite), DataField("frictionNoInput")] public float? FrictionNoInput = null;
[DataField("baseWalkSpeed")]
[ViewVariables(VVAccess.ReadWrite), DataField("baseWalkSpeed")]
public float BaseWalkSpeed { get; set; } = DefaultBaseWalkSpeed;
[DataField("baseSprintSpeed")]
[ViewVariables(VVAccess.ReadWrite), DataField("baseSprintSpeed")]
public float BaseSprintSpeed { get; set; } = DefaultBaseSprintSpeed;
[ViewVariables]

View File

@@ -1,4 +1,5 @@
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Robust.Shared.GameStates;
namespace Content.Shared.Movement.Systems;
@@ -9,6 +10,12 @@ public sealed class MovementIgnoreGravitySystem : EntitySystem
{
SubscribeLocalEvent<MovementIgnoreGravityComponent, ComponentGetState>(GetState);
SubscribeLocalEvent<MovementIgnoreGravityComponent, ComponentHandleState>(HandleState);
SubscribeLocalEvent<MovementAlwaysTouchingComponent, CanWeightlessMoveEvent>(OnWeightless);
}
private void OnWeightless(EntityUid uid, MovementAlwaysTouchingComponent component, ref CanWeightlessMoveEvent args)
{
args.CanMove = true;
}
private void HandleState(EntityUid uid, MovementIgnoreGravityComponent component, ref ComponentHandleState args)

View File

@@ -0,0 +1,20 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Sprite;
[RegisterComponent, NetworkedComponent]
public sealed class RandomSpriteComponent : Component
{
/// <summary>
/// Available colors based on group, parsed layer enum, state, and color.
/// Stored as a list so we can have groups of random sprites (e.g. tech_base + tech_flare for holoparasite)
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("available")]
public List<Dictionary<string, (string State, string? Color)>> Available = new();
/// <summary>
/// Selected colors
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("selected")]
public Dictionary<string, (string State, Color? Color)> Selected = new();
}

View File

@@ -0,0 +1,11 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Sprite;
public abstract class SharedRandomSpriteSystem : EntitySystem {}
[Serializable, NetSerializable]
public sealed class RandomSpriteColorComponentState : ComponentState
{
public Dictionary<string, (string State, Color? Color)> Selected = default!;
}

View File

@@ -1,5 +1,5 @@
atreides.ogg, c-20r.ogg, flaregun.ogg, mateba.ogg, minigun.ogg, mk58.ogg were taken from https://gitlab.com/cmdevs/colonial-warfare/-/tree/32cb5892413243cc74bb2d11df8e3085f8ef1164/sound/weapons
They are licensed under CC-BY-SA 3.0
taser2.ogg and kinetic_accel.ogg were taken from https://github.com/tgstation/tgstation/tree/88d7dbfc105fbf40284d7b7c4587f8d23c0ac3ac
rocket_launcher.ogg and taser2.ogg and kinetic_accel.ogg were taken from https://github.com/tgstation/tgstation/tree/88d7dbfc105fbf40284d7b7c4587f8d23c0ac3ac
It is licensed under CC-BY-SA 3.0

View File

@@ -2,7 +2,7 @@ devour-action-popup-message-structure = Your jaws dig into thick material..
devour-action-popup-message-fail-target-not-valid = That doesn't look particularly edible.
devour-action-popup-message-fail-target-alive = You can't consume creatures that are alive!
dragon-spawn-action-popup-message-fail-no-eggs = You don't have the stamina to create a carp!
dragon-spawn-action-popup-message-fail-no-eggs = You don't have the stamina to do that!
action-name-devour = [color=red]Devour[/color]
@@ -10,3 +10,17 @@ action-description-devour = Attempt to break a structure with your jaws or swall
action-name-carp-summon = Summon carp
action-description-carp-summon = Summon a carp to aid you at seizing the station!
# Rifts
carp-rift-warning = A rift is causing an unnaturally large energy flux at {$location}. Stop it at all costs!
carp-rift-duplicate = Cannot have 2 charging rifts at the same time!
carp-rift-examine = It is [color=yellow]{$percentage}%[/color] charged!
carp-rift-max = You have reached your maximum amount of rifts
carp-rift-anchor = Rifts require a stable surface to spawn.
carp-rift-weakened = You are unable to summon more rifts in your weakened state.
carp-rift-destroyed = A rift has been destroyed! You are now weakened temporarily.
# Round end
dragon-round-end-summary = The dragons were:
dragon-round-end-dragon = {$name} with {$count} rifts
dragon-round-end-dragon-player = {$name} ({$player}) with {$count} rifts

View File

@@ -307,6 +307,7 @@
baseSprintSpeed : 6
- type: Sprite
drawdepth: Mobs
netsync: false
layers:
- map: ["enum.DamageStateVisualLayers.Base"]
state: butterfly
@@ -327,16 +328,10 @@
0: Alive
5: Critical
10: Dead
- type: RandomSpriteColor
state: butterfly
colors:
blue: "#1861d5"
red: "#951710"
pink: "#d5188d"
brown: "#a05212"
green: "#0e7f1b"
cyan: "#18a2d5"
yellow: "#d58c18"
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
butterfly: Rainbow
- type: Appearance
- type: DamageStateVisuals
states:

View File

@@ -29,7 +29,7 @@
- MobMask
layer:
- MobLayer
- type: MovementIgnoreGravity
- type: MovementAlwaysTouching
- type: MobState
thresholds:
0: Alive

View File

@@ -1,77 +1,95 @@
- type: entity
name: space carp
id: MobCarp
id: BaseMobCarp
parent: SimpleSpaceMobBase
description: It's a space carp.
abstract: true
components:
- type: InputMover
- type: MobMover
- type: UtilityNPC
behaviorSets:
- Idle
- UnarmedAttackHostiles
- type: AiFactionTag
factions:
- SimpleHostile
- type: Sprite
drawdepth: Mobs
layers:
- map: ["enum.DamageStateVisualLayers.Base"]
state: alive
sprite: Mobs/Aliens/Carps/space.rsi
- type: CombatMode
disarmAction:
enabled: false
autoPopulate: false
name: action-name-disarm
- type: Physics
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.40
mass: 40
mask:
- MobMask
layer:
- MobLayer
- type: MobState
thresholds:
0: Alive
50: Critical
100: Dead
- type: MovementIgnoreGravity
- type: Appearance
- type: DamageStateVisuals
states:
Alive:
Base: alive
Critical:
Base: crit
Dead:
Base: dead
- type: Butcherable
spawned:
- id: FoodMeatFish
amount: 2
- type: MeleeWeapon
range: 1.5
arcwidth: 0
arc: bite
hitSound:
path: /Audio/Effects/bite.ogg
damage:
types:
Piercing: 5
Slash: 10
- type: ReplacementAccent
accent: genericAggressive
- type: TypingIndicator
proto: alien
- type: NoSlip
- type: Tag
tags:
- Carp
- type: InputMover
- type: MobMover
- type: UtilityNPC
behaviorSets:
- Idle
- UnarmedAttackHostiles
- type: AiFactionTag
factions:
- SimpleHostile
- type: Sprite
drawdepth: Mobs
netsync: false
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: base
sprite: Mobs/Aliens/Carps/space.rsi
- map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
state: mouth
sprite: Mobs/Aliens/Carps/space.rsi
shader: unshaded
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
base: Rainbow
enum.DamageStateVisualLayers.BaseUnshaded:
mouth: ""
- type: CombatMode
disarmAction:
enabled: false
autoPopulate: false
name: action-name-disarm
- type: Physics
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.40
mass: 40
mask:
- MobMask
layer:
- MobLayer
- type: MobState
thresholds:
0: Alive
50: Critical
100: Dead
- type: MovementAlwaysTouching
- type: Appearance
- type: DamageStateVisuals
states:
Alive:
Base: base
BaseUnshaded: mouth
Dead:
Base: base_dead
BaseUnshaded: dead_mouth
- type: Butcherable
spawned:
- id: FoodMeatFish
amount: 2
- type: MeleeWeapon
range: 1.5
arcwidth: 0
arc: bite
hitSound:
path: /Audio/Effects/bite.ogg
damage:
types:
Piercing: 5
Slash: 10
- type: TypingIndicator
proto: alien
- type: NoSlip
- type: Tag
tags:
- Carp
- DoorBumpOpener
- type: entity
parent: BaseMobCarp
id: MobCarp
components:
- type: ReplacementAccent
accent: genericAggressive
- type: entity
name: magicarp
@@ -79,14 +97,14 @@
id: MobCarpMagic
description: Looks like some kind of fish. Might be magical.
components:
- type: Sprite
drawdepth: Mobs
layers:
- map: ["enum.DamageStateVisualLayers.Base"]
state: alive
sprite: Mobs/Aliens/Carps/magic.rsi
- type: TypingIndicator
proto: guardian
- type: Sprite
drawdepth: Mobs
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: base
sprite: Mobs/Aliens/Carps/magic.rsi
- type: TypingIndicator
proto: guardian
- type: entity
name: holocarp
@@ -94,37 +112,37 @@
id: MobCarpHolo
description: Carp made out of holographic energies. Sadly for you, it is very much real.
components:
- type: Sprite
drawdepth: Mobs
layers:
- map: ["enum.DamageStateVisualLayers.Base"]
state: alive
sprite: Mobs/Aliens/Carps/holo.rsi
- type: Physics
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.40
mass: 5
mask:
- MobMask
layer:
- Opaque
- type: TypingIndicator
proto: robot
- type: Sprite
drawdepth: Mobs
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: base
sprite: Mobs/Aliens/Carps/holo.rsi
- type: Physics
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.40
mass: 5
mask:
- MobMask
layer:
- Opaque
- type: TypingIndicator
proto: robot
- type: entity
id: MobCarpSalvage
parent: MobCarp
suffix: "Salvage Ruleset"
components:
- type: GhostTakeoverAvailable
prob: 0.33
name: space carp on salvage wreck
description: |
Defend the loot inside the salvage wreck!
- type: SalvageMobRestrictions
- type: GhostTakeoverAvailable
prob: 0.33
name: space carp on salvage wreck
description: |
Defend the loot inside the salvage wreck!
- type: SalvageMobRestrictions
- type: entity
name: space carp
@@ -132,8 +150,7 @@
suffix: DragonBrood
parent: MobCarp
components:
- type: GhostTakeoverAvailable
makeSentient: true
name: Sentient Carp
description: Help the dragon flood the station with carps!
- type: GhostTakeoverAvailable
makeSentient: true
name: Sentient Carp
description: Help the dragon flood the station with carps!

View File

@@ -38,7 +38,7 @@
thresholds:
0: Alive
15: Dead
- type: MovementIgnoreGravity
- type: MovementAlwaysTouching
- type: Appearance
- type: DamageStateVisuals
states:

View File

@@ -5,106 +5,112 @@
suffix:
description: A flying leviathan, loosely related to space carps.
components:
- type: GhostTakeoverAvailable
makeSentient: true
name: Space dragon!
description: Crash, roast, flood the station with carps!
- type: Speech
- type: CombatMode
disarmAction:
enabled: false
autoPopulate: false
name: action-name-disarm
- type: MobMover
- type: InputMover
- type: MovementSpeedModifier
baseWalkSpeed : 5
baseSprintSpeed : 5
- type: Sprite
sprite: Mobs/Aliens/Carps/dragon.rsi
noRot: true
# TODO: Randomise colors when RandomSpriteColor isn't poopoo
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: alive
- map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
state: alive-unshaded
shader: unshaded
- type: Appearance
- type: DamageStateVisuals
states:
Alive:
Base: alive
BaseUnshaded: alive-unshaded
Critical:
Base: crit
Dead:
Base: dead
BaseUnshaded: dead-unshaded
- type: Physics
bodyType: KinematicController
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.40
mass: 50
mask:
- FlyingMobMask
layer:
- FlyingMobLayer
- type: MobState
thresholds:
0: Alive
450: Critical
500: Dead
- type: Metabolizer
solutionOnBody: false
updateFrequency: 0.25
metabolizerTypes: [Dragon]
groups:
- id: Medicine
- id: Poison
- type: MovementIgnoreGravity
- type: NoSlip
- type: Butcherable
spawned:
- id: FoodMeatDragon
amount: 2
- type: InteractionPopup
successChance: 0.25 # It's no goose, but you better smell like carp.
interactSuccessString: petting-success-dragon
interactFailureString: petting-failure-dragon
interactFailureSound:
path: /Audio/Animals/space_dragon_roar.ogg
soundPerceivedByOthers: false # A 75% chance for a loud roar would get old fast.
- type: MeleeWeapon
hitSound:
path: /Audio/Effects/bite.ogg
damage:
types:
Piercing: 15
Slash: 15
- type: Dragon
spawnsLeft: 2
spawnsProto: MobCarpDragon
devourAction:
event: !type:DragonDevourActionEvent
icon: Interface/Actions/devour.png
name: action-name-devour
description: action-description-devour
devourChemical: Ichor
devourHealRate: 15.0
whitelist:
components:
- MobState
- Door
tags:
- Wall
spawnAction:
event: !type:DragonSpawnActionEvent
icon: Interface/Actions/carp_summon.png
name: action-name-carp-summon
description: action-description-carp-summon
useDelay: 5
- type: GhostTakeoverAvailable
makeSentient: true
name: Space dragon
description: Call in 3 carp rifts and take over this quadrant! You have only 5 minutes in between each rift before you will disappear.
- type: Speech
- type: CombatMode
disarmAction:
enabled: false
autoPopulate: false
name: action-name-disarm
- type: MobMover
- type: InputMover
- type: MovementSpeedModifier
baseWalkSpeed: 3
baseSprintSpeed: 5
weightlessModifier: 1.5
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
alive: Rainbow
- type: Sprite
sprite: Mobs/Aliens/Carps/dragon.rsi
netsync: false
noRot: true
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: alive
- map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
state: alive-unshaded
shader: unshaded
- type: Appearance
- type: DamageStateVisuals
states:
Alive:
Base: alive
BaseUnshaded: alive-unshaded
Critical:
Base: crit
Dead:
Base: dead
BaseUnshaded: dead-unshaded
- type: Physics
bodyType: KinematicController
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.40
mass: 50
mask:
- FlyingMobMask
layer:
- FlyingMobLayer
- type: MobState
thresholds:
0: Alive
450: Critical
500: Dead
- type: Metabolizer
solutionOnBody: false
updateFrequency: 0.25
metabolizerTypes: [ Dragon ]
groups:
- id: Medicine
- id: Poison
- type: MovementAlwaysTouching
- type: NoSlip
- type: Butcherable
spawned:
- id: FoodMeatDragon
amount: 2
- type: InteractionPopup
successChance: 0.25 # It's no goose, but you better smell like carp.
interactSuccessString: petting-success-dragon
interactFailureString: petting-failure-dragon
interactFailureSound:
path: /Audio/Animals/space_dragon_roar.ogg
soundPerceivedByOthers: false # A 75% chance for a loud roar would get old fast.
- type: MeleeWeapon
hitSound:
path: /Audio/Effects/bite.ogg
damage:
types:
Piercing: 15
Slash: 15
- type: Dragon
spawnsLeft: 2
spawnsProto: MobCarpDragon
devourAction:
event: !type:DragonDevourActionEvent
icon: Interface/Actions/devour.png
name: action-name-devour
description: action-description-devour
devourChemical: Ichor
devourHealRate: 15.0
whitelist:
components:
- MobState
- Door
tags:
- Wall
spawnRiftAction:
event: !type:DragonSpawnRiftActionEvent
icon:
sprite: Interface/Actions/carp_rift.rsi
state: icon
name: action-name-carp-rift
description: action-description-carp-rift
useDelay: 1

View File

@@ -5,78 +5,94 @@
id: MobGuardianBase
description: guardian
components:
- type: GhostTakeoverAvailable
makeSentient: true
name: Guardian
description: Listen to your owner. Don't tank damage. Punch people hard.
- type: Input
context: "human"
- type: MobMover
- type: InputMover
- type: MovementSpeedModifier
baseWalkSpeed : 7
baseSprintSpeed : 7
- type: DamageOnHighSpeedImpact
damage:
types:
Blunt: 5
soundHit:
path: /Audio/Effects/hit_kick.ogg
# TODO: Randomise sprites and randomise the layer color
- type: Sprite
drawdepth: Mobs
sprite: Mobs/Aliens/Guardians/guardians.rsi
layers:
- state: tech_base
- state: tech_flare
color: "#40a7d7"
shader: unshaded
noRot: true
- type: Clickable
- type: InteractionOutline
- type: Physics
bodyType: KinematicController
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.35
mass: 10
mask:
- FlyingMobMask
layer:
- FlyingMobLayer
- type: Damageable
damageContainer: Biological
- type: MobState
thresholds:
0: Alive
- type: HeatResistance
- type: CombatMode
- type: Internals
- type: Examiner
- type: Speech
- type: TypingIndicator
proto: guardian
- type: Pullable
- type: MeleeWeapon
range: 2
arcwidth: 30
arc: fist
cooldownTime: 0.7
arcCooldownTime: 0.7
damage:
types:
Blunt: 22
- type: Actions
- type: Guardian
- type: InteractionPopup
interactSuccessString: petting-success-holo
interactFailureString: petting-failure-holo
successChance: 0.7
- type: Tag
tags:
- CannotSuicide
- type: GhostTakeoverAvailable
makeSentient: true
name: Guardian
description: Listen to your owner. Don't tank damage. Punch people hard.
- type: Input
context: "human"
- type: MobMover
- type: InputMover
- type: MovementSpeedModifier
baseWalkSpeed: 4
baseSprintSpeed: 5.5
- type: DamageOnHighSpeedImpact
damage:
types:
Blunt: 5
soundHit:
path: /Audio/Effects/hit_kick.ogg
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
magic_base: ""
enum.DamageStateVisualLayers.BaseUnshaded:
magic_flare: Sixteen
- enum.DamageStateVisualLayers.Base:
miner_base: ""
enum.DamageStateVisualLayers.BaseUnshaded:
miner_flare: Sixteen
- enum.DamageStateVisualLayers.Base:
tech_base: ""
enum.DamageStateVisualLayers.BaseUnshaded:
tech_flare: Sixteen
- type: Sprite
drawdepth: Mobs
sprite: Mobs/Aliens/Guardians/guardians.rsi
netsync: false
layers:
- state: tech_base
map: [ "enum.DamageStateVisualLayers.Base" ]
- state: tech_flare
map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
color: "#40a7d7"
shader: unshaded
noRot: true
- type: Clickable
- type: InteractionOutline
- type: Physics
bodyType: KinematicController
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.35
mass: 10
mask:
- FlyingMobMask
layer:
- FlyingMobLayer
- type: Damageable
damageContainer: Biological
- type: MobState
thresholds:
0: Alive
- type: HeatResistance
- type: CombatMode
- type: Internals
- type: Examiner
- type: Speech
- type: TypingIndicator
proto: guardian
- type: Pullable
- type: MeleeWeapon
range: 2
arcwidth: 30
arc: fist
cooldownTime: 0.7
arcCooldownTime: 0.7
damage:
types:
Blunt: 22
- type: Actions
- type: Guardian
- type: InteractionPopup
interactSuccessString: petting-success-holo
interactFailureString: petting-failure-holo
successChance: 0.7
- type: Tag
tags:
- CannotSuicide
# From the uplink injector
- type: entity
@@ -85,20 +101,22 @@
parent: MobGuardianBase
description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself.
components:
- type: GhostTakeoverAvailable
makeSentient: true
name: Holoparasite
description: Listen to your owner. Don't tank damage. Punch people hard.
- type: NameIdentifier
group: Holoparasite
- type: TypingIndicator
proto: holo
- type: Sprite
layers:
- state: tech_base
- state: tech_flare
color: "#40a7d7"
shader: unshaded
- type: GhostTakeoverAvailable
makeSentient: true
name: Holoparasite
description: Listen to your owner. Don't tank damage. Punch people hard.
- type: NameIdentifier
group: Holoparasite
- type: TypingIndicator
proto: holo
- type: Sprite
layers:
- state: tech_base
map: [ "enum.DamageStateVisualLayers.Base" ]
- state: tech_flare
map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
color: "#40a7d7"
shader: unshaded
# From Wizard deck of cards
- type: entity
@@ -107,13 +125,19 @@
id: MobIfritGuardian
description: A corrupted jinn, ripped from fitra to serve the wizard's petty needs. It stands wicked, tuning into it's owner's life to sustain itself.
components:
- type: GhostTakeoverAvailable
makeSentient: true
name: Ifrit
description: Listen to your owner. Don't tank damage. Punch people hard.
- type: Sprite
layers:
- state: magic_base
- state: magic_flare
color: "#d14730"
shader: unshaded
- type: GhostTakeoverAvailable
makeSentient: true
name: Ifrit
description: Listen to your owner. Don't tank damage. Punch people hard.
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.BaseUnshaded:
magic_flare: Sixteen
- type: Sprite
layers:
- state: magic_base
map: [ "enum.DamageStateVisualLayers.Base" ]
- state: magic_flare
map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
color: "#40a7d7"
shader: unshaded

View File

@@ -176,14 +176,20 @@
- Nugget
- type: Sprite
sprite: Objects/Consumable/Food/Baked/nuggets.rsi
state: tendie
netsync: true
- type: RandomSpriteState
spriteStates:
- tendie
- lizard
- star
- corgi
layers:
- state: tendie
map: [ "enum.DamageStateVisualLayers.Base" ]
netsync: false
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
tendie: ""
- enum.DamageStateVisualLayers.Base:
lizard: ""
- enum.DamageStateVisualLayers.Base:
star: ""
- enum.DamageStateVisualLayers.Base:
corgi: ""
- type: SolutionContainerManager
solutions:
food:

View File

@@ -126,8 +126,13 @@
components:
- type: Sprite
sprite: Objects/Consumable/Food/egg.rsi
state: icon
- type: RandomSpriteState
spriteStates:
- icon
- white
layers:
- state: icon
map: [ "enum.DamageStateVisualLayers.Base" ]
netsync: false
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
icon: ""
- enum.DamageStateVisualLayers.Base:
white: ""

View File

@@ -237,11 +237,16 @@
components:
- type: Sprite
sprite: Objects/Consumable/Food/frozen.rsi
state: stick
- type: RandomSpriteState
spriteStates:
- stick
- stick2
netsync: false
layers:
- state: stick
map: [ "enum.DamageStateVisualLayers.Base" ]
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
stick: ""
- enum.DamageStateVisualLayers.Base:
stick2: ""
- type: Tag
tags:
- Trash

View File

@@ -87,12 +87,14 @@
tags:
- Raw
- type: Sprite
netsync: true
netsync: false
state: bacon
- type: RandomSpriteState
spriteStates:
- bacon
- bacon2
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
bacon: ""
- enum.DamageStateVisualLayers.Base:
bacon2: ""
- type: SolutionContainerManager
solutions:
food:
@@ -501,11 +503,13 @@
layers:
- state: plate-meat
- state: bacon-cooked
- type: RandomSpriteState
spriteLayer: 1
spriteStates:
- bacon-cooked
- bacon2-cooked
map: [ "enum.DamageStateVisualLayers.Base" ]
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
bacon-cooked: ""
- enum.DamageStateVisualLayers.Base:
bacon2-cooked: ""
- type: SolutionContainerManager
solutions:
food:
@@ -608,12 +612,16 @@
- type: Food
trash: FoodPlateSmall
- type: Sprite
netsync: true
state: chicken-fried
- type: RandomSpriteState
spriteStates:
- chicken-fried
- chicken2-fried
netsync: false
layers:
- state: chicken-fried
map: [ "enum.DamageStateVisualLayers.Base" ]
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
chicken-fried: ""
- enum.DamageStateVisualLayers.Base:
chicken2-fried: ""
- type: SolutionContainerManager
solutions:
food:

View File

@@ -5,13 +5,19 @@
description: It's a shard of some unknown material.
components:
- type: Sprite
sprite: Objects/Materials/Shards/shard.rsi
state: shard1
- type: RandomSpriteState
spriteStates:
- shard1
- shard2
- shard3
layers:
- sprite: Objects/Materials/Shards/shard.rsi
state: shard1
map: [ "enum.DamageStateVisualLayers.Base" ]
netsync: false
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
shard1: ""
- enum.DamageStateVisualLayers.Base:
shard2: ""
- enum.DamageStateVisualLayers.Base:
shard3: ""
- type: ItemCooldown
- type: MeleeWeapon
damage:

View File

@@ -6,8 +6,10 @@
components:
- type: Sprite
sprite: Objects/Misc/books.rsi
netsync: false
layers:
- state: book0
map: [ "enum.DamageStateVisualLayers.Base" ]
- type: Paper
- type: ActivatableUI
key: enum.PaperUiKey.Key
@@ -21,15 +23,23 @@
id: BookRandom
suffix: random
components:
- type: RandomSpriteState
spriteLayer: 0
spriteStates:
- book0
- book1
- book2
- book3
- book4
- book5
- book6
- book7
- book8
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
book0: ""
- enum.DamageStateVisualLayers.Base:
book1: ""
- enum.DamageStateVisualLayers.Base:
book2: ""
- enum.DamageStateVisualLayers.Base:
book3: ""
- enum.DamageStateVisualLayers.Base:
book4: ""
- enum.DamageStateVisualLayers.Base:
book5: ""
- enum.DamageStateVisualLayers.Base:
book6: ""
- enum.DamageStateVisualLayers.Base:
book7: ""
- enum.DamageStateVisualLayers.Base:
book8: ""

View File

@@ -4,21 +4,34 @@
id: Lighter
description: "A simple plastic cigarette lighter."
components:
- type: RandomSpriteState #this has to be before sprite component for the flame to toggle right because weldercomponent behaves weird (and i dont trust myself to fix it right)
spriteStates:
- basic_icon_base-1
- basic_icon_base-2
- basic_icon_base-3
- basic_icon_base-4
- basic_icon_base-5
- basic_icon_base-6
- basic_icon_base-7
- basic_icon_base-8
- basic_icon_base-9
- basic_icon_base-10
- basic_icon_base-11
# Sloth: What is this comment ?????????
- type: RandomSprite #this has to be before sprite component for the flame to toggle right because weldercomponent behaves weird (and i dont trust myself to fix it right)
available:
- enum.WelderLayers.Base:
basic_icon_base-1: ""
- enum.WelderLayers.Base:
basic_icon_base-2: ""
- enum.WelderLayers.Base:
basic_icon_base-3: ""
- enum.WelderLayers.Base:
basic_icon_base-4: ""
- enum.WelderLayers.Base:
basic_icon_base-5: ""
- enum.WelderLayers.Base:
basic_icon_base-6: ""
- enum.WelderLayers.Base:
basic_icon_base-7: ""
- enum.WelderLayers.Base:
basic_icon_base-8: ""
- enum.WelderLayers.Base:
basic_icon_base-9: ""
- enum.WelderLayers.Base:
basic_icon_base-10: ""
- enum.WelderLayers.Base:
basic_icon_base-11: ""
- type: Sprite
sprite: Objects/Tools/lighters.rsi
netsync: false
layers:
- state: icon_map
map: ["enum.WelderLayers.Base"]
@@ -69,16 +82,10 @@
id: CheapLighter
description: "A dangerously inexpensive plastic lighter, don't burn your thumb!"
components:
- type: RandomSpriteColor
state: cheap_icon_base
colors:
blue: "#1861d5"
red: "#951710"
pink: "#d5188d"
brown: "#a05212"
green: "#0e7f1b"
cyan: "#18a2d5"
yellow: "#d58c18" #all colours proudly stolen from wirecutters
- type: RandomSprite
available:
- enum.WelderLayers.Base:
cheap_icon_base: Rainbow
- type: Sprite
sprite: Objects/Tools/lighters.rsi
layers:

View File

@@ -13,8 +13,10 @@
- Wirecutter
- type: Sprite
sprite: Objects/Tools/wirecutters.rsi
netsync: false
layers:
- state: cutters-map
- state: cutters
map: [ "enum.DamageStateVisualLayers.Base" ]
- state: cutters-cutty-thingy
- type: ItemCooldown
- type: MeleeWeapon
@@ -26,16 +28,10 @@
- Cutting
useSound:
path: /Audio/Items/wirecutter.ogg
- type: RandomSpriteColor
state: cutters
colors:
blue: "#1861d5"
red: "#951710"
pink: "#d5188d"
brown: "#a05212"
green: "#0e7f1b"
cyan: "#18a2d5"
yellow: "#d58c18"
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
cutters: Rainbow
- type: Item
sprite: Objects/Tools/wirecutters.rsi
@@ -53,8 +49,10 @@
- Screwdriver
- type: Sprite
sprite: Objects/Tools/screwdriver.rsi
netsync: false
layers:
- state: screwdriver-map
- state: screwdriver
map: [ "enum.DamageStateVisualLayers.Base" ]
- state: screwdriver-screwybits
- type: Item
sprite: Objects/Tools/screwdriver.rsi
@@ -68,16 +66,10 @@
- Screwing
useSound:
collection: Screwdriver
- type: RandomSpriteColor
state: screwdriver
colors:
blue: "#1861d5"
red: "#ff0000"
pink: "#d5188d"
brown: "#a05212"
green: "#0e7f1b"
cyan: "#18a2d5"
yellow: "#ffa500"
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
screwdriver: Rainbow
- type: entity
name: wrench

View File

@@ -9,15 +9,21 @@
layers:
- state: base
- state: book-0
- type: RandomSpriteState
spriteLayer: 1
spriteStates:
- book-0
- book-1
- book-2
- book-3
- book-4
- book-5
map: [ "enum.DamageStateVisualLayers.Base" ]
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
book-0: ""
- enum.DamageStateVisualLayers.Base:
book-1: ""
- enum.DamageStateVisualLayers.Base:
book-2: ""
- enum.DamageStateVisualLayers.Base:
book-3: ""
- enum.DamageStateVisualLayers.Base:
book-4: ""
- enum.DamageStateVisualLayers.Base:
book-5: ""
- type: Damageable
damageModifierSet: Wood
damageContainer: Inorganic

View File

@@ -0,0 +1,39 @@
- type: entity
id: CarpRift
name: carp rift
description: A rift akin to the ones space carp use to travel long distances.
placement:
mode: SnapgridCenter
components:
- type: DragonRift
- type: Transform
anchored: true
- type: Physics
bodyType: Static
- type: Fixtures
- type: Sprite
netsync: false
layers:
- sprite: Structures/Specific/carp_rift.rsi
state: icon
color: "#569fff"
shader: unshaded
- type: InteractionOutline
- type: Clickable
- type: PointLight
enabled: true
color: "#366db5"
radius: 10.0
energy: 5.0
netsync: false
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 300
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]

View File

@@ -32,6 +32,17 @@
earliestStart: 15
maxOccurrences: 3
- type: gameRule
id: Dragon
config:
!type:StationEventRuleConfiguration
id: Dragon
weight: 10
endAfter: 2
earliestStart: 15
maxOccurrences: 7
minimumPlayers: 15
- type: gameRule
id: FalseAlarm
config:

View File

@@ -1,4 +1,16 @@
- type: palette
id: Rainbow
name: Rainbow
colors:
blue: "#1861d5"
red: "#951710"
pink: "#d5188d"
brown: "#a05212"
green: "#0e7f1b"
cyan: "#18a2d5"
yellow: "#d58c18"
- type: palette
id: Sixteen
name: Sixteen
colors:

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,21 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/tgstation/tgstation/blob/19da0cee1869bad0186d54d6bcd8a55ed30b9db6/icons/obj/carp_rift.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"delays": [
[
0.2,
0.2,
0.2
]
]
}
]
}

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -33,29 +33,7 @@
]
},
{
"name": "crit",
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "alive",
"name": "base",
"directions": 4,
"delays": [
[

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 B

View File

@@ -14,10 +14,7 @@
"name": "dead"
},
{
"name": "crit"
},
{
"name": "alive",
"name": "base",
"directions": 4,
"delays": [
[

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

View File

@@ -11,13 +11,39 @@
"name": "icon"
},
{
"name": "dead"
"name": "mouth",
"directions": 4,
"delays": [
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
]
]
},
{
"name": "crit"
"name": "dead_mouth"
},
{
"name": "alive",
"name": "base_dead"
},
{
"name": "base",
"directions": 4,
"delays": [
[

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

View File

@@ -1 +0,0 @@
Eventually we'll have some kind of unifying "RandomSpriteStateComponent" but til now i'm just leaving these here in protest.

View File

@@ -1 +0,0 @@
Eventually we'll have some kind of unifying "RandomSpriteStateComponent" but til now i'm just leaving these here in protest.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -0,0 +1,21 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/tgstation/tgstation/blob/19da0cee1869bad0186d54d6bcd8a55ed30b9db6/icons/obj/carp_rift.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"delays": [
[
0.2,
0.2,
0.2
]
]
}
]
}