[DataField("deleteAfterExplosion")]
public bool? DeleteAfterExplosion;
+ /// <summary>
+ /// Whether to not set <see cref="Exploded"/> to true, allowing it to explode multiple times.
+ /// This should never be used if it is damageable.
+ /// </summary>
+ [DataField]
+ public bool Repeatable;
+
/// <summary>
/// Avoid somehow double-triggering this explosion (e.g. by damaging this entity from its own explosion.
/// </summary>
--- /dev/null
+using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Explosion.Components;
+
+/// <summary>
+/// Constantly triggers after being added to an entity.
+/// </summary>
+[RegisterComponent, Access(typeof(TriggerSystem))]
+[AutoGenerateComponentPause]
+public sealed partial class RepeatingTriggerComponent : Component
+{
+ /// <summary>
+ /// How long to wait between triggers.
+ /// The first trigger starts this long after the component is added.
+ /// </summary>
+ [DataField]
+ public TimeSpan Delay = TimeSpan.FromSeconds(1);
+
+ /// <summary>
+ /// When the next trigger will be.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+ public TimeSpan NextTrigger;
+}
if (explosive.Exploded)
return;
- explosive.Exploded = true;
+ explosive.Exploded = !explosive.Repeatable;
// Override the explosion intensity if optional arguments were provided.
if (radius != null)
SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered);
SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlipTriggered);
SubscribeLocalEvent<TriggerWhenEmptyComponent, OnEmptyGunShotEvent>(OnEmptyTriggered);
+ SubscribeLocalEvent<RepeatingTriggerComponent, MapInitEvent>(OnRepeatInit);
SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(OnSpawnTrigger);
SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteTrigger);
Trigger(uid, args.EmptyGun);
}
+ private void OnRepeatInit(Entity<RepeatingTriggerComponent> ent, ref MapInitEvent args)
+ {
+ ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay;
+ }
+
public bool Trigger(EntityUid trigger, EntityUid? user = null)
{
var triggerEvent = new TriggerEvent(trigger, user);
UpdateProximity();
UpdateTimer(frameTime);
UpdateTimedCollide(frameTime);
+ UpdateRepeat();
}
private void UpdateTimer(float frameTime)
_appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
}
}
+
+ private void UpdateRepeat()
+ {
+ var now = _timing.CurTime;
+ var query = EntityQueryEnumerator<RepeatingTriggerComponent>();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (comp.NextTrigger > now)
+ continue;
+
+ comp.NextTrigger = now + comp.Delay;
+ Trigger(uid);
+ }
+ }
}
}
--- /dev/null
+using Content.Shared.Actions;
+using Content.Shared.Weapons.Ranged.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Weapons.Ranged.Components;
+
+/// <summary>
+/// Lets you shoot a gun using an action.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(ActionGunSystem))]
+public sealed partial class ActionGunComponent : Component
+{
+ /// <summary>
+ /// Action to create, must use <see cref="ActionGunShootEvent"/>.
+ /// </summary>
+ [DataField(required: true)]
+ public EntProtoId Action = string.Empty;
+
+ [DataField]
+ public EntityUid? ActionEntity;
+
+ /// <summary>
+ /// Prototype of gun entity to spawn.
+ /// Deleted when this component is removed.
+ /// </summary>
+ [DataField(required: true)]
+ public EntProtoId GunProto = string.Empty;
+
+ [DataField]
+ public EntityUid? Gun;
+}
+
+/// <summary>
+/// Action event for <see cref="ActionGunComponent"/> to shoot at a position.
+/// </summary>
+public sealed partial class ActionGunShootEvent : WorldTargetActionEvent;
--- /dev/null
+using Content.Shared.Actions;
+using Content.Shared.Weapons.Ranged.Components;
+
+namespace Content.Shared.Weapons.Ranged.Systems;
+
+public sealed class ActionGunSystem : EntitySystem
+{
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
+ [Dependency] private readonly SharedGunSystem _gun = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ActionGunComponent, MapInitEvent>(OnMapInit);
+ SubscribeLocalEvent<ActionGunComponent, ComponentShutdown>(OnShutdown);
+ SubscribeLocalEvent<ActionGunComponent, ActionGunShootEvent>(OnShoot);
+ }
+
+ private void OnMapInit(Entity<ActionGunComponent> ent, ref MapInitEvent args)
+ {
+ if (string.IsNullOrEmpty(ent.Comp.Action))
+ return;
+
+ _actions.AddAction(ent, ref ent.Comp.ActionEntity, ent.Comp.Action);
+ ent.Comp.Gun = Spawn(ent.Comp.GunProto);
+ }
+
+ private void OnShutdown(Entity<ActionGunComponent> ent, ref ComponentShutdown args)
+ {
+ if (ent.Comp.Gun is {} gun)
+ QueueDel(gun);
+ }
+
+ private void OnShoot(Entity<ActionGunComponent> ent, ref ActionGunShootEvent args)
+ {
+ if (TryComp<GunComponent>(ent.Comp.Gun, out var gun))
+ _gun.AttemptShoot(ent, ent.Comp.Gun.Value, gun, args.Target);
+ }
+}
+
speedModifierThresholds:
250: 0.7
400: 0.5
+ # disable taking damage from fire, since its a fire breathing dragon
+ - type: Flammable
+ damage:
+ types: {}
+ - type: Temperature
+ heatDamageThreshold: 800
- type: Metabolizer
solutionOnBody: false
updateInterval: 0.25
spawnRiftAction: ActionSpawnRift
- type: GenericAntag
rule: Dragon
+ - type: ActionGun
+ action: ActionDragonsBreath
+ gunProto: DragonsBreathGun
- type: GuideHelp
guides:
- MinorAntagonists
+- type: entity
+ noSpawn: true
+ id: DragonsBreathGun
+ name: dragon's lung
+ description: For dragon's breathing
+ components:
+ - type: RechargeBasicEntityAmmo
+ rechargeCooldown: 5
+ rechargeSound:
+ path: /Audio/Animals/space_dragon_roar.ogg
+ - type: BasicEntityAmmoProvider
+ proto: ProjectileDragonsBreath
+ capacity: 1
+ count: 1
+ - type: Gun
+ soundGunshot:
+ path: /Audio/Animals/space_dragon_roar.ogg
+ soundEmpty: null
+ projectileSpeed: 5
+
- type: entity
parent: BaseMobDragon
id: MobDragonDungeon
state: icon
event: !type:DragonSpawnRiftActionEvent
useDelay: 1
+ priority: 3
- type: entity
id: ActionDevour
icon: { sprite : Interface/Actions/devour.rsi, state: icon }
iconOn: { sprite : Interface/Actions/devour.rsi, state: icon-on }
event: !type:DevourActionEvent
+ priority: 1
+
+- type: entity
+ noSpawn: true
+ id: ActionDragonsBreath
+ name: "[color=orange]Dragon's Breath[/color]"
+ description: Spew out flames at anyone foolish enough to attack you!
+ components:
+ - type: WorldTargetAction
+ # TODO: actual sprite
+ icon: { sprite : Objects/Weapons/Guns/Projectiles/magic.rsi, state: fireball }
+ event: !type:ActionGunShootEvent
+ priority: 2
+ checkCanAccess: false
+ range: 0
- type: IgniteOnCollide
fireStacks: 0.35
+- type: entity
+ noSpawn: true
+ parent: BaseBulletTrigger
+ id: ProjectileDragonsBreath
+ name: dragon's breath
+ description: Try not to get toasted.
+ components:
+ - type: PointLight
+ color: "#E25822"
+ radius: 3.0
+ energy: 5.0
+ - type: Sprite
+ sprite: Objects/Weapons/Guns/Projectiles/magic.rsi
+ layers:
+ - state: fireball
+ shader: unshaded
+ - type: IgnitionSource
+ temperature: 1000
+ ignited: true
+ - type: RepeatingTrigger
+ delay: 0.5 # line of fire as well as if it hits something
+ - type: ExplodeOnTrigger
+ - type: Explosive
+ explosionType: FireBomb
+ totalIntensity: 5 # low intensity, the point is to burn attackers not to break open walls, dragons can just eat them
+ intensitySlope: 1
+ maxIntensity: 3
+ canCreateVacuum: false
+ deleteAfterExplosion: false
+ repeatable: true
+ - type: TimedDespawn
+ lifetime: 5
+
- type: entity
id: ProjectileAnomalyFireball
name: fireball