return;
component.Whitelist = state.Whitelist;
+ component.Blacklist = state.Blacklist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
}
-using Content.Shared.Magic;
+using Content.Shared.Magic;
using Content.Shared.Magic.Events;
namespace Content.Client.Magic;
/// </summary>
public EntityWhitelist? Whitelist = null;
+ /// <summary>
+ /// Blacklist that the target must satisfy.
+ /// </summary>
+ public EntityWhitelist? Blacklist = null;
+
/// <summary>
/// Predicate the target must satisfy.
/// </summary>
RemoveHighlights();
}
- public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, CancellableEntityEventArgs? validationEvent)
+ public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, EntityWhitelist? blacklist, CancellableEntityEventArgs? validationEvent)
{
Range = range;
CheckObstruction = checkObstructions;
Predicate = predicate;
Whitelist = whitelist;
+ Blacklist = blacklist;
ValidationEvent = validationEvent;
- _enabled = Predicate != null || Whitelist != null || ValidationEvent != null;
+ _enabled = Predicate != null || Whitelist != null || Blacklist != null || ValidationEvent != null;
}
public override void Update(float frameTime)
var range = entityAction.CheckCanAccess ? action.Range : -1;
_interactionOutline?.SetEnabled(false);
- _targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, null);
+ _targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, entityAction.Blacklist, null);
}
/// <summary>
-using Content.Shared.Whitelist;
+using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
/// <remarks>No whitelist check when null.</remarks>
[DataField("whitelist")] public EntityWhitelist? Whitelist;
+ /// <summary>
+ /// Determines which entities are NOT valid targets for this action.
+ /// </summary>
+ /// <remarks>No blacklist check when null.</remarks>
+ [DataField] public EntityWhitelist? Blacklist;
+
/// <summary>
/// Whether this action considers the user as a valid target entity when using this action.
/// </summary>
public sealed class EntityTargetActionComponentState : BaseActionComponentState
{
public EntityWhitelist? Whitelist;
+ public EntityWhitelist? Blacklist;
public bool CanTargetSelf;
public EntityTargetActionComponentState(EntityTargetActionComponent component, IEntityManager entManager) : base(component, entManager)
{
Whitelist = component.Whitelist;
+ Blacklist = component.Blacklist;
CanTargetSelf = component.CanTargetSelf;
}
}
if (!ValidateEntityTargetBase(user,
target,
comp.Whitelist,
+ comp.Blacklist,
comp.CheckCanInteract,
comp.CanTargetSelf,
comp.CheckCanAccess,
private bool ValidateEntityTargetBase(EntityUid user,
EntityUid? targetEntity,
EntityWhitelist? whitelist,
+ EntityWhitelist? blacklist,
bool checkCanInteract,
bool canTargetSelf,
bool checkCanAccess,
if (_whitelistSystem.IsWhitelistFail(whitelist, target))
return false;
+ if (_whitelistSystem.IsBlacklistPass(blacklist, target))
+ return false;
+
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
return false;
var entityValidated = ValidateEntityTargetBase(user,
entity,
comp.Whitelist,
+ null,
comp.CheckCanInteract,
comp.CanTargetSelf,
comp.CheckCanAccess,
public sealed class DamageableComponentState : ComponentState
{
public readonly Dictionary<string, FixedPoint2> DamageDict;
+ public readonly string? DamageContainerId;
public readonly string? ModifierSetId;
public readonly FixedPoint2? HealthBarThreshold;
public DamageableComponentState(
Dictionary<string, FixedPoint2> damageDict,
+ string? damageContainerId,
string? modifierSetId,
FixedPoint2? healthBarThreshold)
{
DamageDict = damageDict;
+ DamageContainerId = damageContainerId;
ModifierSetId = modifierSetId;
HealthBarThreshold = healthBarThreshold;
}
{
if (_netMan.IsServer)
{
- args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId, component.HealthBarThreshold);
+ args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageContainerID, component.DamageModifierSetId, component.HealthBarThreshold);
}
else
{
// avoid mispredicting damage on newly spawned entities.
- args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId, component.HealthBarThreshold);
+ args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageContainerID, component.DamageModifierSetId, component.HealthBarThreshold);
}
}
return;
}
+ component.DamageContainerID = state.DamageContainerId;
component.DamageModifierSetId = state.ModifierSetId;
component.HealthBarThreshold = state.HealthBarThreshold;
--- /dev/null
+
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Magic.Components;
+
+// Used on whitelist for animate spell/wand
+[RegisterComponent, NetworkedComponent]
+public sealed partial class AnimateableComponent : Component
+{
+
+}
--- /dev/null
+using Content.Shared.Actions;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Magic.Events;
+
+public sealed partial class AnimateSpellEvent : EntityTargetActionEvent, ISpeakSpell
+{
+ [DataField]
+ public string? Speech { get; private set; }
+
+ [DataField]
+ public ComponentRegistry AddComponents = new();
+
+ [DataField]
+ public HashSet<string> RemoveComponents = new();
+}
+using System.Linq;
using System.Numerics;
using Content.Shared.Actions;
using Content.Shared.Body.Components;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Spawners;
SubscribeLocalEvent<RandomGlobalSpawnSpellEvent>(OnRandomGlobalSpawnSpell);
SubscribeLocalEvent<MindSwapSpellEvent>(OnMindSwapSpell);
SubscribeLocalEvent<VoidApplauseSpellEvent>(OnVoidApplause);
+ SubscribeLocalEvent<AnimateSpellEvent>(OnAnimateSpell);
}
private void OnBeforeCastSpell(Entity<MagicComponent> ent, ref BeforeCastSpellEvent args)
ev.Handled = true;
Speak(ev);
- foreach (var toRemove in ev.ToRemove)
- {
- if (_compFact.TryGetRegistration(toRemove, out var registration))
- RemComp(ev.Target, registration.Type);
- }
-
- foreach (var (name, data) in ev.ToAdd)
- {
- if (HasComp(ev.Target, data.Component.GetType()))
- continue;
-
- var component = (Component)_compFact.GetComponent(name);
- var temp = (object)component;
- _seriMan.CopyTo(data.Component, ref temp);
- EntityManager.AddComponent(ev.Target, (Component)temp!);
- }
+ RemoveComponents(ev.Target, ev.ToRemove);
+ AddComponents(ev.Target, ev.ToAdd);
}
// End Change Component Spells
#endregion
comp.Uid = performer;
}
}
+
+ private void AddComponents(EntityUid target, ComponentRegistry comps)
+ {
+ foreach (var (name, data) in comps)
+ {
+ if (HasComp(target, data.Component.GetType()))
+ continue;
+
+ var component = (Component)_compFact.GetComponent(name);
+ var temp = (object)component;
+ _seriMan.CopyTo(data.Component, ref temp);
+ EntityManager.AddComponent(target, (Component)temp!);
+ }
+ }
+
+ private void RemoveComponents(EntityUid target, HashSet<string> comps)
+ {
+ foreach (var toRemove in comps)
+ {
+ if (_compFact.TryGetRegistration(toRemove, out var registration))
+ RemComp(target, registration.Type);
+ }
+ }
// End Spell Helpers
#endregion
#region Touch Spells
_stun.TryParalyze(ev.Performer, ev.PerformerStunDuration, true);
}
+ #endregion
+ #region Animation Spells
+
+ private void OnAnimateSpell(AnimateSpellEvent ev)
+ {
+ if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer) || !TryComp<FixturesComponent>(ev.Target, out var fixtures) ||
+ !TryComp<PhysicsComponent>(ev.Target, out var physics))
+ return;
+
+ ev.Handled = true;
+ //Speak(ev);
+
+ RemoveComponents(ev.Target, ev.RemoveComponents);
+ AddComponents(ev.Target, ev.AddComponents);
+
+ var xform = Transform(ev.Target);
+ var fixture = fixtures.Fixtures.First();
+
+ _transform.Unanchor(ev.Target);
+ _physics.SetCanCollide(ev.Target, true, true, false, fixtures, physics);
+ _physics.SetCollisionMask(ev.Target, fixture.Key, fixture.Value, (int)CollisionGroup.FlyingMobMask, fixtures, physics);
+ _physics.SetCollisionLayer(ev.Target, fixture.Key, fixture.Value, (int)CollisionGroup.FlyingMobLayer, fixtures, physics);
+ _physics.SetBodyType(ev.Target, BodyType.KinematicController, fixtures, physics, xform);
+ _physics.SetBodyStatus(ev.Target, physics, BodyStatus.InAir, true);
+ _physics.SetFixedRotation(ev.Target, false, true, fixtures, physics);
+ }
+
#endregion
// End Spells
#endregion
spellbook-mind-swap-name = Mind Swap
spellbook-mind-swap-description = Exchange bodies with another person!
+spellbook-animate-name = Animate
+spellbook-animate-description = Bring an inanimate object to life!
+
spellbook-smite-name = Smite
spellbook-smite-desc = Don't like them? EXPLODE them into giblets! Requires Wizard Robe & Hat.
spellbook-wand-locker-name = Wand of the Locker
spellbook-wand-locker-description = Shoot cursed lockers at your enemies and lock em away!
+spellbook-staff-animation-name = Staff of Animation
+spellbook-staff-animation-description = Bring inanimate objects to life!
+
# Events
spellbook-event-summon-ghosts-name = Summon Ghosts
- !type:ListingLimitedStockCondition
stock: 1
+- type: listing
+ id: SpellbookStaffAnimation
+ name: spellbook-staff-animation-name
+ description: spellbook-staff-animation-description
+ productEntity: AnimationStaff
+ cost:
+ WizCoin: 3
+ categories:
+ - SpellbookEquipment
+ conditions:
+ - !type:ListingLimitedStockCondition
+ stock: 1
+
# Event
- type: listing
id: SpellbookEventSummonGhosts
-# To be implemented: see #9072
+# To be implemented: see #9072
- type: entity
name: staff of healing
components:
- type: Item
size: Small
+ - type: Animateable
- type: Clickable
- type: InteractionOutline
- type: MovedByPressure
description: A square piece of metal standing on four metal legs.
abstract: true
components:
+ - type: Animateable
- type: Damageable
damageContainer: StructuralInorganic
damageModifierSet: Metallic
placement:
mode: PlaceFree
components:
+ - type: Animateable
- type: Clickable
- type: InteractionOutline
- type: Physics
placement:
mode: SnapgridCenter
components:
+ - type: Animateable
- type: MeleeSound
soundGroups:
Brute:
parent: BaseStructure
id: BaseMachine
components:
+ - type: Animateable
- type: InteractionOutline
- type: Anchorable
delay: 2
abstract: true
id: ConstructibleMachine
components:
+ - type: Animateable
- type: Machine
- type: ContainerContainer
containers:
name: gas canister
description: A canister that can contain any type of gas. It can be attached to connector ports using a wrench.
components:
+ - type: Animateable
- type: InteractionOutline
- type: Transform
noRot: true
description: A standard-issue Nanotrasen storage unit.
abstract: true
components:
+ - type: Animateable
- type: ResistLocker
- type: Transform
noRot: true
name: crate
description: A large container for items.
components:
+ - type: Animateable
- type: Transform
noRot: true
- type: Icon
-- type: entity
+- type: entity
id: StorageTank
parent: BaseStructureDynamic
name: storage tank
description: A liquids storage tank.
abstract: true
components:
+ - type: Animateable
- type: Sprite
noRot: true
- type: InteractionOutline
mask:
- MachineMask
layer:
- - WallLayer
\ No newline at end of file
+ - WallLayer
--- /dev/null
+- type: entity
+ id: ActionAnimateSpell
+ name: Animate
+ description: Bring an inanimate object to life!
+ components:
+ - type: EntityTargetAction
+ useDelay: 0
+ charges: 5
+ itemIconStyle: BigAction
+ whitelist:
+ components:
+ - Animateable # Currently on: SeatBase, TableBase, ClosetBase, BaseMachine, ConstructibleMachine, BaseComputer, BaseItem, CrateGeneric, StorageTank, GasCanister
+ blacklist:
+ components:
+ - MindContainer
+ - NukeDisk
+ - GravityGenerator
+ - AnomalyGenerator
+ canTargetSelf: false
+ interactOnMiss: false
+ sound: !type:SoundPathSpecifier
+ path: /Audio/Magic/staff_animation.ogg
+ icon:
+ sprite: Objects/Magic/magicactions.rsi
+ state: spell_default
+ event: !type:AnimateSpellEvent
+ addComponents:
+ - type: MindContainer
+ - type: InputMover
+ - type: MobMover
+ - type: MovementSpeedModifier
+ - type: HTN
+ rootTask:
+ task: SimpleHostileCompound
+ - type: CombatMode
+ - type: MeleeWeapon
+ animation: WeaponArcPunch
+ wideAnimation: WeaponArcPunch
+ altDisarm: false
+ soundHit: /Audio/Weapons/smash.ogg
+ range: 1.2
+ angle: 0.0
+ damage:
+ types:
+ Blunt: 10
+ - type: NpcFactionMember
+ factions:
+ - Wizard
+ - type: NoSlip
+ - type: MovementAlwaysTouching
+ - type: CanMoveInAir
+ - type: Damageable
+ damageContainer: ManifestedSpirit
+ damageModifierSet: ManifestedSpirit
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 100
+ behaviors:
+ - !type:DoActsBehavior
+ acts: ["Destruction"]
+ - !type:PlaySoundBehavior
+ sound:
+ collection: MetalBreak
+ - type: Hands
+ - type: CanEscapeInventory
+ removeComponents:
+ - RequireProjectileTarget
+ speech: action-speech-spell-animate
-# non-projectile / "gun" staves
+# non-projectile / "gun" staves
# wand that gives lights an RGB effect.
- type: entity
enabled: true
radius: 2
+- type: entity
+ id: AnimationStaff
+ parent: BaseItem
+ name: staff of animation
+ description: Brings inanimate objects to life!
+ components:
+ - type: Sprite
+ sprite: Objects/Weapons/Guns/Basic/staves.rsi
+ layers:
+ - state: animation
+ - type: ActionOnInteract
+ actions:
+ - ActionAnimateSpell
+ - type: Item
+ size: Normal
+ inhandVisuals:
+ left:
+ - state: animation-inhand-left
+ right:
+ - state: animation-inhand-right
+ - type: Tag
+ tags:
+ - WizardWand
+
- type: entity
id: ActionRgbLight
components:
- type: npcFaction
id: Dragon
hostile:
- - NanoTrasen
- - Syndicate
- - Xeno
- - PetsNT
- - Zombie
- - Revolutionary
- - AllHostile
+ - NanoTrasen
+ - Syndicate
+ - Xeno
+ - PetsNT
+ - Zombie
+ - Revolutionary
+ - AllHostile
+ - Wizard
- type: npcFaction
id: NanoTrasen
- Revolutionary
- Dragon
- AllHostile
+ - Wizard
- type: npcFaction
id: Mouse
hostile:
- - PetsNT
- - AllHostile
+ - PetsNT
+ - AllHostile
- type: npcFaction
id: Passive
- Zombie
- Revolutionary
- AllHostile
+ - Wizard
- type: npcFaction
id: SimpleNeutral
- Zombie
- Dragon
- AllHostile
+ - Wizard
- type: npcFaction
id: Xeno
- Zombie
- Revolutionary
- AllHostile
+ - Wizard
- type: npcFaction
id: Zombie
- PetsNT
- Revolutionary
- AllHostile
+ - Wizard
- type: npcFaction
id: Revolutionary
- SimpleHostile
- Dragon
- AllHostile
+ - Wizard
- type: npcFaction
id: AllHostile
- Xeno
- Zombie
- Revolutionary
+ - Wizard
+
+- type: npcFaction
+ id: Wizard
+ hostile:
+ - NanoTrasen
+ - Dragon
+ - SimpleHostile
+ - Syndicate
+ - Xeno
+ - Zombie
+ - Revolutionary
+ - AllHostile