make dragons breathe fire (#26746)

* add ActionGun system

* add RepeatingTrigger

* dragons breath projectile, repeatedly explodes

* give dragon fire breathing action, fireproof it

* oop

* oop 2

* prevent troll

* proper repeating thing

* pro

* webedit ops

* realops

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
deltanedas
2024-05-08 00:25:41 +00:00
committed by GitHub
parent bd06aa2365
commit d6d1c9ed8a
8 changed files with 210 additions and 1 deletions

View File

@@ -81,6 +81,13 @@ public sealed partial class ExplosiveComponent : Component
[DataField("deleteAfterExplosion")] [DataField("deleteAfterExplosion")]
public bool? 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> /// <summary>
/// Avoid somehow double-triggering this explosion (e.g. by damaging this entity from its own explosion. /// Avoid somehow double-triggering this explosion (e.g. by damaging this entity from its own explosion.
/// </summary> /// </summary>

View File

@@ -0,0 +1,25 @@
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;
}

View File

@@ -160,7 +160,7 @@ public sealed partial class ExplosionSystem : EntitySystem
if (explosive.Exploded) if (explosive.Exploded)
return; return;
explosive.Exploded = true; explosive.Exploded = !explosive.Repeatable;
// Override the explosion intensity if optional arguments were provided. // Override the explosion intensity if optional arguments were provided.
if (radius != null) if (radius != null)

View File

@@ -94,6 +94,7 @@ namespace Content.Server.Explosion.EntitySystems
SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered); SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered);
SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlipTriggered); SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlipTriggered);
SubscribeLocalEvent<TriggerWhenEmptyComponent, OnEmptyGunShotEvent>(OnEmptyTriggered); SubscribeLocalEvent<TriggerWhenEmptyComponent, OnEmptyGunShotEvent>(OnEmptyTriggered);
SubscribeLocalEvent<RepeatingTriggerComponent, MapInitEvent>(OnRepeatInit);
SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(OnSpawnTrigger); SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(OnSpawnTrigger);
SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteTrigger); SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteTrigger);
@@ -241,6 +242,11 @@ namespace Content.Server.Explosion.EntitySystems
Trigger(uid, args.EmptyGun); 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) public bool Trigger(EntityUid trigger, EntityUid? user = null)
{ {
var triggerEvent = new TriggerEvent(trigger, user); var triggerEvent = new TriggerEvent(trigger, user);
@@ -323,6 +329,7 @@ namespace Content.Server.Explosion.EntitySystems
UpdateProximity(); UpdateProximity();
UpdateTimer(frameTime); UpdateTimer(frameTime);
UpdateTimedCollide(frameTime); UpdateTimedCollide(frameTime);
UpdateRepeat();
} }
private void UpdateTimer(float frameTime) private void UpdateTimer(float frameTime)
@@ -357,5 +364,19 @@ namespace Content.Server.Explosion.EntitySystems
_appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance); _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);
}
}
} }
} }

View File

@@ -0,0 +1,37 @@
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;

View File

@@ -0,0 +1,41 @@
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);
}
}

View File

@@ -91,6 +91,12 @@
speedModifierThresholds: speedModifierThresholds:
250: 0.7 250: 0.7
400: 0.5 400: 0.5
# disable taking damage from fire, since its a fire breathing dragon
- type: Flammable
damage:
types: {}
- type: Temperature
heatDamageThreshold: 800
- type: Metabolizer - type: Metabolizer
solutionOnBody: false solutionOnBody: false
updateInterval: 0.25 updateInterval: 0.25
@@ -141,10 +147,33 @@
spawnRiftAction: ActionSpawnRift spawnRiftAction: ActionSpawnRift
- type: GenericAntag - type: GenericAntag
rule: Dragon rule: Dragon
- type: ActionGun
action: ActionDragonsBreath
gunProto: DragonsBreathGun
- type: GuideHelp - type: GuideHelp
guides: guides:
- MinorAntagonists - 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 - type: entity
parent: BaseMobDragon parent: BaseMobDragon
id: MobDragonDungeon id: MobDragonDungeon
@@ -183,6 +212,7 @@
state: icon state: icon
event: !type:DragonSpawnRiftActionEvent event: !type:DragonSpawnRiftActionEvent
useDelay: 1 useDelay: 1
priority: 3
- type: entity - type: entity
id: ActionDevour id: ActionDevour
@@ -194,3 +224,18 @@
icon: { sprite : Interface/Actions/devour.rsi, state: icon } icon: { sprite : Interface/Actions/devour.rsi, state: icon }
iconOn: { sprite : Interface/Actions/devour.rsi, state: icon-on } iconOn: { sprite : Interface/Actions/devour.rsi, state: icon-on }
event: !type:DevourActionEvent 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

View File

@@ -30,6 +30,39 @@
- type: IgniteOnCollide - type: IgniteOnCollide
fireStacks: 0.35 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 - type: entity
id: ProjectileAnomalyFireball id: ProjectileAnomalyFireball
name: fireball name: fireball