Залью спеллы потестить, мне похуй ПР НОМЕР 156

* refactor CheZaHuetaMagicSystem

* эщкере

* alt-spells system. lmb, rmb, alt-click

* fix

* ChargeSpellsIndicator + Visual(CheZaHueta)

* Custom charge effect for spell

* Custom MaxChargeLevel

* Finally. Alt spells seems to work!! Need to start do spells and gamerule

* fuckkk

* fix crash, actually burn scroll..

* some fixes blyat

* ArcSpell

* очередная CheZaHuetaSystem, ForceSpell

* ONI'SOMA!

* mraow

* prepare this LMAO

* Yebanyy rot etogo kazino blyat! - CardsSpell

* forcewall

* nig

* blink

* Ethereal Jaunt

* игра говно

* Блядина

* ну на еще спеллов

* blyadina

* да иди ты нахуй БЛЯДЬ

* кто прочитал, тот сдохнет. сделай 5 репостов чтобы выжить....

* icons

* та ваще поебать

* одежда
This commit is contained in:
rhailrake
2024-03-07 16:01:54 +00:00
committed by GitHub
parent d944178a7b
commit c08cdeb84d
99 changed files with 3461 additions and 406 deletions

View File

@@ -2,6 +2,7 @@ using System.Numerics;
using System.Threading;
using Content.Client.CombatMode;
using Content.Client.Gameplay;
using Content.Client.UserInterface.Systems.Actions;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
@@ -16,7 +17,7 @@ namespace Content.Client.ContextMenu.UI
/// <remarks>
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
/// </remarks>
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>, IOnSystemChanged<ChargeActionSystem>
{
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
@@ -216,6 +217,12 @@ namespace Content.Client.ContextMenu.UI
Close();
}
private void OnChargingUpdated(bool charging)
{
if (charging)
Close();
}
public void OnSystemLoaded(CombatModeSystem system)
{
system.LocalPlayerCombatModeUpdated += OnCombatModeUpdated;
@@ -225,5 +232,15 @@ namespace Content.Client.ContextMenu.UI
{
system.LocalPlayerCombatModeUpdated -= OnCombatModeUpdated;
}
public void OnSystemLoaded(ChargeActionSystem system)
{
system.ChargingUpdated += OnChargingUpdated;
}
public void OnSystemUnloaded(ChargeActionSystem system)
{
system.ChargingUpdated -= OnChargingUpdated;
}
}
}

View File

@@ -22,6 +22,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Content.Client.Actions.ActionsSystem;
@@ -29,8 +30,7 @@ using static Content.Client.UserInterface.Systems.Actions.Windows.ActionsWindow;
using static Robust.Client.UserInterface.Control;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.LineEdit;
using static Robust.Client.UserInterface.Controls.MultiselectOptionButton<
Content.Client.UserInterface.Systems.Actions.Windows.ActionsWindow.Filters>;
using static Robust.Client.UserInterface.Controls.MultiselectOptionButton<Content.Client.UserInterface.Systems.Actions.Windows.ActionsWindow.Filters>;
using static Robust.Client.UserInterface.Controls.TextureRect;
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
@@ -128,25 +128,45 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
}
builder
.Bind(ContentKeyFunctions.OpenActionsMenu,
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse, outsidePrediction: true),
typeof(ConstructionSystem), typeof(DragDropSystem))
.BindBefore(EngineKeyFunctions.UIRightClick, new PointerInputCmdHandler(TargetingCancel, outsidePrediction: true))
.Bind(ContentKeyFunctions.OpenActionsMenu, InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse, outsidePrediction: true), typeof(ConstructionSystem), typeof(DragDropSystem))
.BindBefore(ContentKeyFunctions.AltActivateItemInWorld, new PointerInputCmdHandler(AltUse, outsidePrediction: true))
.Register<ActionUIController>();
}
private bool TargetingCancel(in PointerInputCmdArgs args)
private bool AltUse(in PointerInputCmdArgs args)
{
if (!_timing.IsFirstTimePredicted)
if (!_timing.IsFirstTimePredicted || _actionsSystem == null || SelectingTargetFor is not { } actionId)
return false;
// only do something for actual target-based actions
if (SelectingTargetFor == null)
if (_playerManager.LocalEntity is not { } user)
return false;
StopTargeting();
return true;
if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
return false;
if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
baseAction is not BaseTargetActionComponent action || !action.IsAltEnabled)
{
return false;
}
// Is the action currently valid?
if (!action.Enabled
|| action is { Charges: 0, RenewCharges: false }
|| action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime)
{
// The user is targeting with this action, but it is not valid. Maybe mark this click as
// handled and prevent further interactions.
return !action.InteractOnMiss;
}
return action switch
{
WorldTargetActionComponent mapTarget => TryTargetWorld(args.Coordinates, actionId, mapTarget, user, comp,
ActionUseType.AltUse, target: args.EntityUid) || !mapTarget.InteractOnMiss,
_ => false
};
}
/// <summary>
@@ -179,28 +199,23 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
return !action.InteractOnMiss;
}
switch (action)
return action switch
{
case WorldTargetActionComponent mapTarget:
return TryTargetWorld(args, actionId, mapTarget, user, comp) || !mapTarget.InteractOnMiss;
case EntityTargetActionComponent entTarget:
return TryTargetEntity(args, actionId, entTarget, user, comp) || !entTarget.InteractOnMiss;
default:
Logger.Error($"Unknown targeting action: {actionId.GetType()}");
return false;
}
WorldTargetActionComponent mapTarget => TryTargetWorld(args.Coordinates, actionId, mapTarget, user, comp) ||
!mapTarget.InteractOnMiss,
EntityTargetActionComponent entTarget => TryTargetEntity(args.EntityUid, actionId, entTarget, user, comp) ||
!entTarget.InteractOnMiss,
_ => false
};
}
private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, WorldTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
public bool TryTargetWorld(EntityCoordinates coordinates, EntityUid actionId, WorldTargetActionComponent action,
EntityUid user, ActionsComponent actionComp, ActionUseType type = ActionUseType.Default, int chargeLevel = 0, EntityUid? target = default)
{
if (_actionsSystem == null)
return false;
var coords = args.Coordinates;
if (!_actionsSystem.ValidateWorldTarget(user, coords, action))
if (!_actionsSystem.ValidateWorldTarget(user, coordinates, action))
{
// Invalid target.
if (action.DeselectOnMiss)
@@ -213,14 +228,24 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
if (action.Event != null)
{
action.Event.Target = coords;
action.Event.Target = coordinates;
action.Event.Performer = user;
}
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
}
else
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetCoordinates(coords)));
{
var msg = new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetCoordinates(coordinates))
{
ActionUseType = type,
ChargeLevel = chargeLevel,
EntityTarget = EntityManager.GetNetEntity(target)
};
EntityManager.RaisePredictiveEvent(msg);
}
if (!action.Repeat)
StopTargeting();
@@ -228,14 +253,12 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
return true;
}
private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, EntityTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
public bool TryTargetEntity(EntityUid uid, EntityUid actionId, EntityTargetActionComponent action, EntityUid user, ActionsComponent actionComp, ActionUseType type = ActionUseType.Default)
{
if (_actionsSystem == null)
return false;
var entity = args.EntityUid;
if (!_actionsSystem.ValidateEntityTarget(user, entity, action))
if (!_actionsSystem.ValidateEntityTarget(user, uid, action))
{
if (action.DeselectOnMiss)
StopTargeting();
@@ -247,14 +270,21 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
if (action.Event != null)
{
action.Event.Target = entity;
action.Event.Target = uid;
action.Event.Performer = user;
}
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
}
else
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetEntity(args.EntityUid)));
{
var msg = new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetEntity(uid))
{
ActionUseType = type
};
EntityManager.RaisePredictiveEvent(msg);
}
if (!action.Repeat)
StopTargeting();
@@ -409,12 +439,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
};
}
private void ClearList()
{
if (_window?.Disposed == false)
_window.ResultsGrid.RemoveAllChildren();
}
private void PopulateActions(IEnumerable<(EntityUid Id, BaseActionComponent Comp)> actions)
{
if (_window is not { Disposed: false, IsOpen: true })
@@ -432,7 +456,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
existing.Add(button);
}
int i = 0;
var i = 0;
foreach (var action in actions)
{
if (i < existing.Count)

View File

@@ -0,0 +1,173 @@
using Content.Client.Actions;
using Content.Shared._White.Wizard;
using Content.Shared._White.Wizard.Charging;
using Content.Shared.Actions;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Client.UserInterface.Systems.Actions;
public sealed class ChargeActionSystem : SharedChargingSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
private ActionUIController? _controller;
public event Action<bool>? ChargingUpdated;
private bool _charging;
private bool _prevCharging;
private float _chargeTime;
private int _chargeLevel;
private int _prevChargeLevel;
private bool _isChargingPlaying;
private bool _isChargedPlaying;
private const float LevelChargeTime = 1.5f;
public override void Initialize()
{
base.Initialize();
_controller = _uiManager.GetUIController<ActionUIController>();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (_playerManager.LocalEntity is not { } user)
return;
if (!_timing.IsFirstTimePredicted || _controller == null || _controller.SelectingTargetFor is not { } actionId)
return;
if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
baseAction is not BaseTargetActionComponent action || !action.IsChargeEnabled)
return;
if (!action.Enabled
|| action is { Charges: 0, RenewCharges: false }
|| action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime)
{
return;
}
var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
switch (altDown)
{
case BoundKeyState.Down:
_prevCharging = _charging;
_charging = true;
_chargeTime += frameTime;
_chargeLevel = (int) (_chargeTime / LevelChargeTime) + 1;
_chargeLevel = Math.Clamp(_chargeLevel, 1, action.MaxChargeLevel);
break;
case BoundKeyState.Up when _charging:
_prevCharging = _charging;
_charging = false;
_chargeTime = 0f;
_isChargingPlaying = false;
_isChargedPlaying = false;
HandleAction(actionId, action, user, _chargeLevel);
_chargeLevel = 0;
RaiseNetworkEvent(new RequestAudioSpellStop());
RaiseNetworkEvent(new RemoveWizardChargeEvent());
break;
case BoundKeyState.Up:
_prevCharging = _charging;
_chargeLevel = 0;
_charging = false;
_chargeTime = 0f;
_isChargingPlaying = false;
_isChargedPlaying = false;
RaiseNetworkEvent(new RequestAudioSpellStop());
RaiseNetworkEvent(new RemoveWizardChargeEvent());
break;
}
if (_chargeLevel != _prevChargeLevel)
{
if (_chargeLevel > 0 && _charging)
{
RaiseNetworkEvent(new AddWizardChargeEvent(action.ChargeProto));
}
_prevChargeLevel = _chargeLevel;
}
if (_prevCharging != _charging)
{
ChargingUpdated?.Invoke(_charging);
}
if (_charging && !_isChargingPlaying)
{
_isChargingPlaying = true;
RaiseNetworkEvent(new RequestSpellChargingAudio(action.ChargingSound, action.LoopCharging));
}
if (_chargeLevel >= action.MaxChargeLevel && !_isChargedPlaying && _charging)
{
_isChargedPlaying = true;
RaiseNetworkEvent(new RequestSpellChargedAudio(action.MaxChargedSound, action.LoopMaxCharged));
}
}
private void HandleAction(EntityUid actionId, BaseTargetActionComponent action, EntityUid user, int chargeLevel)
{
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
if (mousePos.MapId == MapId.Nullspace)
return;
var coordinates = EntityCoordinates.FromMap(_mapManager.TryFindGridAt(mousePos, out var gridUid, out _)
? gridUid
: _mapManager.GetMapEntityId(mousePos.MapId), mousePos, _transformSystem, EntityManager);
if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
return;
switch (action)
{
case WorldTargetActionComponent mapTarget:
_controller?.TryTargetWorld(coordinates, actionId, mapTarget, user, comp, ActionUseType.Charge, chargeLevel);
break;
}
RaiseNetworkEvent(new RequestAudioSpellStop());
RaiseNetworkEvent(new RemoveWizardChargeEvent());
}
public override void Shutdown()
{
base.Shutdown();
_controller = null;
_charging = false;
_prevCharging = false;
_chargeTime = 0f;
_chargeLevel = 0;
_prevChargeLevel = 0;
_isChargingPlaying = false;
_isChargedPlaying = false;
}
}

View File

@@ -17,8 +17,6 @@ using Robust.Client.State;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Weapons.Melee;

View File

@@ -0,0 +1,7 @@
using Content.Shared._White.Wizard.ScrollSystem;
namespace Content.Client._White.Wizard.Scrolls;
public sealed class ScrollSystem : SharedScrollSystem
{
}

View File

@@ -0,0 +1,15 @@
namespace Content.Server.EnergyDome;
/// <summary>
/// marker component that allows linking the dome generator with the dome itself
/// </summary>
[RegisterComponent, Access(typeof(EnergyDomeSystem))]
public sealed partial class EnergyDomeComponent : Component
{
/// <summary>
/// A linked generator that uses energy
/// </summary>
[DataField]
public EntityUid? Generator;
}

View File

@@ -0,0 +1,85 @@
using Content.Shared.DeviceLinking;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Server.EnergyDome;
/// <summary>
/// component, allows an entity to generate a battery-powered energy dome of a specific type.
/// </summary>
[RegisterComponent, Access(typeof(EnergyDomeSystem))] //Access add
public sealed partial class EnergyDomeGeneratorComponent : Component
{
[DataField]
public bool Enabled = false;
/// <summary>
/// How much energy will be spent from the battery per unit of damage taken by the shield.
/// </summary>
[DataField]
public float DamageEnergyDraw = 10f;
/// <summary>
/// Whether or not the dome can be toggled via standard interactions
/// (alt verbs, using in hand, etc)
/// </summary>
[DataField]
public bool CanInteractUse = true;
/// <summary>
/// Can the NetworkDevice system activate and deactivate the barrier?
/// </summary>
[DataField]
public bool CanDeviceNetworkUse = false;
//Dome
[DataField, ViewVariables(VVAccess.ReadWrite)]
public EntProtoId DomePrototype = "EnergyDomeSmallRed";
[DataField]
public EntityUid? SpawnedDome;
/// <summary>
/// the entity on which the shield will be hung. This is either the container containing
/// the item or the item itself. Determined when the shield is activated,
/// it is stored in the component for changing the protected entity.
/// </summary>
[DataField]
public EntityUid? DomeParentEntity;
//Action
[DataField]
public EntProtoId ToggleAction = "ActionToggleDome";
[DataField]
public EntityUid? ToggleActionEntity;
//Sounds
[DataField]
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
[DataField]
public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Machines/anomaly_sync_connect.ogg");
[DataField]
public SoundSpecifier EnergyOutSound = new SoundPathSpecifier("/Audio/Machines/energyshield_down.ogg");
[DataField]
public SoundSpecifier TurnOffSound = new SoundPathSpecifier("/Audio/Machines/button.ogg");
[DataField]
public SoundSpecifier ParrySound = new SoundPathSpecifier("/Audio/Machines/energyshield_parry.ogg")
{
Params = AudioParams.Default.WithVariation(0.05f)
};
//Ports
[DataField]
public ProtoId<SinkPortPrototype> TogglePort = "Toggle";
[DataField]
public ProtoId<SinkPortPrototype> OnPort = "On";
[DataField]
public ProtoId<SinkPortPrototype> OffPort = "Off";
}

View File

@@ -0,0 +1,329 @@
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.PowerCell;
using Content.Shared.Actions;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
using Content.Shared.Timing;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
namespace Content.Server.EnergyDome;
public sealed partial class EnergyDomeSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
//Generator events
SubscribeLocalEvent<EnergyDomeGeneratorComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ActivateInWorldEvent>(OnActivatedInWorld);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ToggleActionEvent>(OnToggleAction);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, PowerCellChangedEvent>(OnPowerCellChanged);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ChargeChangedEvent>(OnChargeChanged);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, EntParentChangedMessage>(OnParentChanged);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, GetVerbsEvent<ActivationVerb>>(AddToggleDomeVerb);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ComponentRemove>(OnComponentRemove);
//Dome events
SubscribeLocalEvent<EnergyDomeComponent, DamageChangedEvent>(OnDomeDamaged);
}
private void OnInit(Entity<EnergyDomeGeneratorComponent> generator, ref MapInitEvent args)
{
if (generator.Comp.CanDeviceNetworkUse)
_signalSystem.EnsureSinkPorts(generator, generator.Comp.TogglePort, generator.Comp.OnPort, generator.Comp.OffPort);
}
//different ways of use
private void OnSignalReceived(Entity<EnergyDomeGeneratorComponent> generator, ref SignalReceivedEvent args)
{
if (!generator.Comp.CanDeviceNetworkUse)
return;
if (args.Port == generator.Comp.OnPort)
{
AttemptToggle(generator, true);
}
if (args.Port == generator.Comp.OffPort)
{
AttemptToggle(generator, false);
}
if (args.Port == generator.Comp.TogglePort)
{
AttemptToggle(generator, !generator.Comp.Enabled);
}
}
private void OnAfterInteract(Entity<EnergyDomeGeneratorComponent> generator, ref AfterInteractEvent args)
{
if (generator.Comp.CanInteractUse)
AttemptToggle(generator, !generator.Comp.Enabled);
}
private void OnActivatedInWorld(Entity<EnergyDomeGeneratorComponent> generator, ref ActivateInWorldEvent args)
{
if (generator.Comp.CanInteractUse)
AttemptToggle(generator, !generator.Comp.Enabled);
}
private void OnExamine(Entity<EnergyDomeGeneratorComponent> generator, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString(
(generator.Comp.Enabled)
? "energy-dome-on-examine-is-on-message"
: "energy-dome-on-examine-is-off-message"
));
}
private void AddToggleDomeVerb(Entity<EnergyDomeGeneratorComponent> generator, ref GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !generator.Comp.CanInteractUse)
return;
var @event = args;
ActivationVerb verb = new()
{
Text = Loc.GetString("energy-dome-verb-toggle"),
Act = () => AttemptToggle(generator, !generator.Comp.Enabled)
};
args.Verbs.Add(verb);
}
private void OnGetActions(Entity<EnergyDomeGeneratorComponent> generator, ref GetItemActionsEvent args)
{
if (generator.Comp.CanInteractUse)
args.AddAction(ref generator.Comp.ToggleActionEntity, generator.Comp.ToggleAction);
}
private void OnToggleAction(Entity<EnergyDomeGeneratorComponent> generator, ref ToggleActionEvent args)
{
if (args.Handled)
return;
AttemptToggle(generator, !generator.Comp.Enabled);
args.Handled = true;
}
// System interactions
private void OnPowerCellSlotEmpty(Entity<EnergyDomeGeneratorComponent> generator, ref PowerCellSlotEmptyEvent args)
{
TurnOff(generator, true);
}
private void OnPowerCellChanged(Entity<EnergyDomeGeneratorComponent> generator, ref PowerCellChangedEvent args)
{
if (args.Ejected || !_powerCell.HasDrawCharge(generator))
TurnOff(generator, true);
}
private void OnChargeChanged(Entity<EnergyDomeGeneratorComponent> generator, ref ChargeChangedEvent args)
{
if (args.Charge == 0)
TurnOff(generator, true);
}
private void OnDomeDamaged(Entity<EnergyDomeComponent> dome, ref DamageChangedEvent args)
{
if (dome.Comp.Generator == null)
return;
var generatorUid = dome.Comp.Generator.Value;
if (!TryComp<EnergyDomeGeneratorComponent>(generatorUid, out var generatorComp))
return;
if (args.DamageDelta == null)
return;
float totalDamage = args.DamageDelta.GetTotal().Float();
var energyLeak = totalDamage * generatorComp.DamageEnergyDraw;
_audio.PlayPvs(generatorComp.ParrySound, dome);
if (HasComp<PowerCellDrawComponent>(generatorUid))
{
_powerCell.TryGetBatteryFromSlot(generatorUid, out var cell);
if (cell != null)
{
_battery.UseCharge(cell.Owner, energyLeak);
if (cell.Charge == 0)
TurnOff((generatorUid, generatorComp), true);
}
}
//it seems to me it would not work well to hang both a powercell and an internal battery with wire charging on the object....
if (TryComp<BatteryComponent>(generatorUid, out var battery)) {
_battery.UseCharge(generatorUid, energyLeak);
if (battery.Charge == 0)
TurnOff((generatorUid, generatorComp), true);
}
}
private void OnParentChanged(Entity<EnergyDomeGeneratorComponent> generator, ref EntParentChangedMessage args)
{
//To do: taking the active barrier in hand for some reason does not manage to change the parent in this case,
//and the barrier is not turned off.
//
//Laying down works well (-_-)
if (GetProtectedEntity(generator) != generator.Comp.DomeParentEntity)
TurnOff(generator, false);
}
private void OnComponentRemove(Entity<EnergyDomeGeneratorComponent> generator, ref ComponentRemove args)
{
TurnOff(generator, false);
}
// Functional
public bool AttemptToggle(Entity<EnergyDomeGeneratorComponent> generator, bool status)
{
if (TryComp<UseDelayComponent>(generator, out var useDelay) && _useDelay.IsDelayed(new Entity<UseDelayComponent>(generator, useDelay)))
{
_audio.PlayPvs(generator.Comp.TurnOffSound, generator);
_popup.PopupEntity(
Loc.GetString("energy-dome-recharging"),
generator);
return false;
}
if (TryComp<PowerCellSlotComponent>(generator, out var powerCellSlot))
{
if (!_powerCell.TryGetBatteryFromSlot(generator, out var cell) && !TryComp(generator, out cell))
{
_audio.PlayPvs(generator.Comp.TurnOffSound, generator);
_popup.PopupEntity(
Loc.GetString("energy-dome-no-cell"),
generator);
return false;
}
if (!_powerCell.HasDrawCharge(generator))
{
_audio.PlayPvs(generator.Comp.TurnOffSound, generator);
_popup.PopupEntity(
Loc.GetString("energy-dome-no-power"),
generator);
return false;
}
}
if (TryComp<BatteryComponent>(generator, out var battery))
{
if (battery.Charge == 0)
{
_audio.PlayPvs(generator.Comp.TurnOffSound, generator);
_popup.PopupEntity(
Loc.GetString("energy-dome-no-power"),
generator);
return false;
}
}
Toggle(generator, status);
return true;
}
private void Toggle(Entity<EnergyDomeGeneratorComponent> generator, bool status)
{
if (status)
TurnOn(generator);
else
TurnOff(generator, false);
}
private void TurnOn(Entity<EnergyDomeGeneratorComponent> generator)
{
if (generator.Comp.Enabled)
return;
var protectedEntity = GetProtectedEntity(generator);
var newDome = Spawn(generator.Comp.DomePrototype, Transform(protectedEntity).Coordinates);
generator.Comp.DomeParentEntity = protectedEntity;
_transform.SetParent(newDome, protectedEntity);
if (TryComp<EnergyDomeComponent>(newDome, out var domeComp))
{
domeComp.Generator = generator;
}
_powerCell.SetPowerCellDrawEnabled(generator, true);
if (TryComp<BatterySelfRechargerComponent>(generator, out var recharger)) {
recharger.AutoRecharge = true;
}
generator.Comp.SpawnedDome = newDome;
_audio.PlayPvs(generator.Comp.TurnOnSound, generator);
generator.Comp.Enabled = true;
}
private void TurnOff(Entity<EnergyDomeGeneratorComponent> generator, bool startReloading)
{
if (!generator.Comp.Enabled)
return;
generator.Comp.Enabled = false;
QueueDel(generator.Comp.SpawnedDome);
_powerCell.SetPowerCellDrawEnabled(generator, false);
if (TryComp<BatterySelfRechargerComponent>(generator, out var recharger))
{
recharger.AutoRecharge = false;
}
_audio.PlayPvs(generator.Comp.TurnOffSound, generator);
if (startReloading)
{
_audio.PlayPvs(generator.Comp.EnergyOutSound, generator);
if (TryComp<UseDelayComponent>(generator, out var useDelay))
{
_useDelay.TryResetDelay(new Entity<UseDelayComponent>(generator, useDelay));
}
}
}
// Util
private EntityUid GetProtectedEntity(EntityUid entity)
{
return (_container.TryGetOuterContainer(entity, Transform(entity), out var container))
? container.Owner
: entity;
}
}

View File

@@ -57,7 +57,6 @@ public sealed class LightningSystem : SharedLightningSystem
}
}
/// <summary>
/// Looks for objects with a LightningTarget component in the radius, prioritizes them, and hits the highest priority targets with lightning.
/// </summary>
@@ -78,9 +77,9 @@ public sealed class LightningSystem : SharedLightningSystem
_random.Shuffle(targets);
targets.Sort((x, y) => y.Priority.CompareTo(x.Priority));
int shootedCount = 0;
int count = -1;
while(shootedCount < boltCount)
var shootCount = 0;
var count = -1;
while(shootCount < boltCount)
{
count++;
@@ -95,7 +94,7 @@ public sealed class LightningSystem : SharedLightningSystem
{
ShootRandomLightnings(targets[count].Owner, range, 1, lightningPrototype, arcDepth - targets[count].LightningResistance, triggerLightningEvents);
}
shootedCount++;
shootCount++;
}
}
}

View File

@@ -1,35 +0,0 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Server.Magic.Components;
/// <summary>
/// Spellbooks for having an entity learn spells as long as they've read the book and it's in their hand.
/// </summary>
[RegisterComponent]
public sealed partial class SpellbookComponent : Component
{
/// <summary>
/// List of spells that this book has. This is a combination of the WorldSpells, EntitySpells, and InstantSpells.
/// </summary>
[ViewVariables]
public readonly List<EntityUid> Spells = new();
/// <summary>
/// The three fields below is just used for initialization.
/// </summary>
[DataField("spells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, EntityPrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public Dictionary<string, int> SpellActions = new();
[DataField("learnTime")]
[ViewVariables(VVAccess.ReadWrite)]
public float LearnTime = .75f;
/// <summary>
/// If true, the spell action stays even after the book is removed
/// </summary>
[DataField("learnPermanently")]
[ViewVariables(VVAccess.ReadWrite)]
public bool LearnPermanently;
}

View File

@@ -1,17 +1,15 @@
using System.Linq;
using System.Numerics;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Doors.Systems;
using Content.Server.Magic.Components;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Actions;
using Content.Shared.Body.Components;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.DoAfter;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Interaction.Events;
using Content.Shared.Magic;
using Content.Shared.Magic.Events;
using Content.Shared.Maps;
@@ -21,6 +19,7 @@ using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
@@ -33,31 +32,25 @@ namespace Content.Server.Magic;
/// </summary>
public sealed class MagicSystem : EntitySystem
{
[Dependency] private readonly ISerializationManager _seriMan = default!;
[Dependency] private readonly ISerializationManager _serializationManager = default!;
[Dependency] private readonly IComponentFactory _compFact = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly DoorBoltSystem _boltsSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpellbookComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<SpellbookComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<SpellbookComponent, SpellbookDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<InstantSpawnSpellEvent>(OnInstantSpawn);
SubscribeLocalEvent<TeleportSpellEvent>(OnTeleportSpell);
SubscribeLocalEvent<KnockSpellEvent>(OnKnockSpell);
@@ -67,73 +60,8 @@ public sealed class MagicSystem : EntitySystem
SubscribeLocalEvent<ChangeComponentsSpellEvent>(OnChangeComponentsSpell);
}
private void OnDoAfter(EntityUid uid, SpellbookComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
args.Handled = true;
if (!component.LearnPermanently)
{
_actionsSystem.GrantActions(args.Args.User, component.Spells, uid);
return;
}
foreach (var (id, charges) in component.SpellActions)
{
// TOOD store spells entity ids on some sort of innate magic user component or something like that.
EntityUid? actionId = null;
if (_actionsSystem.AddAction(args.Args.User, ref actionId, id))
_actionsSystem.SetCharges(actionId, charges < 0 ? null : charges);
}
component.SpellActions.Clear();
}
private void OnInit(EntityUid uid, SpellbookComponent component, MapInitEvent args)
{
if (component.LearnPermanently)
return;
foreach (var (id, charges) in component.SpellActions)
{
var spell = _actionContainer.AddAction(uid, id);
if (spell == null)
continue;
_actionsSystem.SetCharges(spell, charges < 0 ? null : charges);
component.Spells.Add(spell.Value);
}
}
private void OnUse(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
AttemptLearn(uid, component, args);
args.Handled = true;
}
private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
{
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.LearnTime, new SpellbookDoAfterEvent(), uid, target: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
NeedHand = true //What, are you going to read with your eyes only??
};
_doAfter.TryStartDoAfter(doAfterEventArgs);
}
#region Spells
/// <summary>
/// Handles the instant action (i.e. on the caster) attempting to spawn an entity.
/// </summary>
private void OnInstantSpawn(InstantSpawnSpellEvent args)
{
if (args.Handled)
@@ -145,12 +73,12 @@ public sealed class MagicSystem : EntitySystem
{
var ent = Spawn(args.Prototype, position.SnapToGrid(EntityManager, _mapManager));
if (args.PreventCollideWithCaster)
{
if (!args.PreventCollideWithCaster)
continue;
var comp = EnsureComp<PreventCollideComponent>(ent);
comp.Uid = args.Performer;
}
}
Speak(args);
args.Handled = true;
@@ -166,22 +94,17 @@ public sealed class MagicSystem : EntitySystem
var xform = Transform(ev.Performer);
// var userVelocity = _physics.GetMapLinearVelocity(ev.Performer); WD EDIT
foreach (var pos in GetSpawnPositions(xform, ev.Pos))
{
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
var mapPos = pos.ToMap(EntityManager);
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid) // WD EDIT
var mapPos = _transformSystem.ToMapCoordinates(pos);
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid)
? pos.WithEntityId(gridUid, EntityManager)
: new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
: new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
// WD EDIT
var userVelocity = Vector2.Zero;
if (grid != null && TryComp(gridUid, out PhysicsComponent? physics))
userVelocity = physics.LinearVelocity;
// WD EDIT
var ent = Spawn(ev.Prototype, spawnCoords);
var direction = ev.Target.ToMapPos(EntityManager, _transformSystem) -
@@ -194,7 +117,9 @@ public sealed class MagicSystem : EntitySystem
{
if (ev.Handled)
return;
ev.Handled = true;
Speak(ev);
foreach (var toRemove in ev.ToRemove)
@@ -209,75 +134,12 @@ public sealed class MagicSystem : EntitySystem
continue;
var component = (Component) _compFact.GetComponent(name);
component.Owner = ev.Target;
var temp = (object) component;
_seriMan.CopyTo(data.Component, ref temp);
_serializationManager.CopyTo(data.Component, ref temp);
EntityManager.AddComponent(ev.Target, (Component) temp!);
}
}
private List<EntityCoordinates> GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data)
{
switch (data)
{
case TargetCasterPos:
return new List<EntityCoordinates>(1) {casterXform.Coordinates};
case TargetInFront:
{
// This is shit but you get the idea.
var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized());
if (!_mapManager.TryGetGrid(casterXform.GridUid, out var mapGrid))
return new List<EntityCoordinates>();
if (!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager))
return new List<EntityCoordinates>();
var tileIndex = tileReference.Value.GridIndices;
var coords = mapGrid.GridTileToLocal(tileIndex);
EntityCoordinates coordsPlus;
EntityCoordinates coordsMinus;
var dir = casterXform.LocalRotation.GetCardinalDir();
switch (dir)
{
case Direction.North:
case Direction.South:
{
coordsPlus = mapGrid.GridTileToLocal(tileIndex + (1, 0));
coordsMinus = mapGrid.GridTileToLocal(tileIndex + (-1, 0));
return new List<EntityCoordinates>(3)
{
coords,
coordsPlus,
coordsMinus,
};
}
case Direction.East:
case Direction.West:
{
coordsPlus = mapGrid.GridTileToLocal(tileIndex + (0, 1));
coordsMinus = mapGrid.GridTileToLocal(tileIndex + (0, -1));
return new List<EntityCoordinates>(3)
{
coords,
coordsPlus,
coordsMinus,
};
}
}
return new List<EntityCoordinates>();
}
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Teleports the user to the clicked location
/// </summary>
/// <param name="args"></param>
private void OnTeleportSpell(TeleportSpellEvent args)
{
if (args.Handled)
@@ -285,19 +147,16 @@ public sealed class MagicSystem : EntitySystem
var transform = Transform(args.Performer);
if (transform.MapID != args.Target.GetMapId(EntityManager)) return;
if (transform.MapID != args.Target.GetMapId(EntityManager))
return;
_transformSystem.SetCoordinates(args.Performer, args.Target);
transform.AttachToGridOrMap();
_transformSystem.AttachToGridOrMap(args.Performer);
_audio.PlayPvs(args.BlinkSound, args.Performer, AudioParams.Default.WithVolume(args.BlinkVolume));
Speak(args);
args.Handled = true;
}
/// <summary>
/// Opens all doors within range
/// </summary>
/// <param name="args"></param>
private void OnKnockSpell(KnockSpellEvent args)
{
if (args.Handled)
@@ -306,13 +165,11 @@ public sealed class MagicSystem : EntitySystem
args.Handled = true;
Speak(args);
//Get the position of the player
var transform = Transform(args.Performer);
var coords = transform.Coordinates;
_audio.PlayPvs(args.KnockSound, args.Performer, AudioParams.Default.WithVolume(args.KnockVolume));
//Look for doors and don't open them if they're already open.
foreach (var entity in _lookup.GetEntitiesInRange(coords, args.Range))
{
if (TryComp<DoorBoltComponent>(entity, out var bolts))
@@ -329,9 +186,10 @@ public sealed class MagicSystem : EntitySystem
return;
ev.Handled = true;
Speak(ev);
var direction = Transform(ev.Target).MapPosition.Position - Transform(ev.Performer).MapPosition.Position;
var direction = _transformSystem.GetMapCoordinates(ev.Target).Position - _transformSystem.GetMapCoordinates(ev.Performer).Position;
var impulseVector = direction * 10000;
_physics.ApplyLinearImpulse(ev.Target, impulseVector);
@@ -339,28 +197,17 @@ public sealed class MagicSystem : EntitySystem
if (!TryComp<BodyComponent>(ev.Target, out var body))
return;
var ents = _bodySystem.GibBody(ev.Target, true, body);
var entities = _bodySystem.GibBody(ev.Target, true, body);
if (!ev.DeleteNonBrainParts)
return;
foreach (var part in ents)
{
// just leaves a brain and clothes
if (HasComp<BodyComponent>(part) && !HasComp<BrainComponent>(part))
foreach (var part in entities.Where(part => HasComp<BodyComponent>(part) && !HasComp<BrainComponent>(part)))
{
QueueDel(part);
}
}
}
/// <summary>
/// Spawns entity prototypes from a list within range of click.
/// </summary>
/// <remarks>
/// It will offset mobs after the first mob based on the OffsetVector2 property supplied.
/// </remarks>
/// <param name="args"> The Spawn Spell Event args.</param>
private void OnWorldSpawn(WorldSpawnSpellEvent args)
{
if (args.Handled)
@@ -373,24 +220,85 @@ public sealed class MagicSystem : EntitySystem
args.Handled = true;
}
/// <summary>
/// Loops through a supplied list of entity prototypes and spawns them
/// </summary>
/// <remarks>
/// If an offset of 0, 0 is supplied then the entities will all spawn on the same tile.
/// Any other offset will spawn entities starting from the source Map Coordinates and will increment the supplied
/// offset
/// </remarks>
/// <param name="entityEntries"> The list of Entities to spawn in</param>
/// <param name="entityCoords"> Map Coordinates where the entities will spawn</param>
/// <param name="lifetime"> Check to see if the entities should self delete</param>
/// <param name="offsetVector2"> A Vector2 offset that the entities will spawn in</param>
private void SpawnSpellHelper(List<EntitySpawnEntry> entityEntries, EntityCoordinates entityCoords, float? lifetime, Vector2 offsetVector2)
#endregion
#region Helpers
public List<EntityCoordinates> GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data)
{
var getProtos = EntitySpawnCollection.GetSpawns(entityEntries, _random);
return data switch
{
TargetCasterPos => GetCasterPosition(casterXform),
TargetInFront => GetPositionsInFront(casterXform),
_ => throw new ArgumentOutOfRangeException()
};
}
public List<EntityCoordinates> GetCasterPosition(TransformComponent casterXform)
{
return new List<EntityCoordinates>(1) { casterXform.Coordinates };
}
public List<EntityCoordinates> GetPositionsInFront(TransformComponent casterXform)
{
var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized());
if (!TryComp<MapGridComponent>(casterXform.GridUid, out var mapGrid) ||
!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager))
{
return new List<EntityCoordinates>();
}
var tileIndex = tileReference.Value.GridIndices;
var coords = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex);
var directions = GetCardinalDirections(casterXform.LocalRotation.GetCardinalDir());
var spawnPositions = new List<EntityCoordinates>(3);
foreach (var direction in directions)
{
var offset = GetOffsetForDirection(direction);
var coordinates = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex + offset);
spawnPositions.Add(coordinates);
}
spawnPositions.Add(coords);
return spawnPositions;
}
public IEnumerable<Direction> GetCardinalDirections(Direction dir)
{
switch (dir)
{
case Direction.North:
case Direction.South:
return new[] { Direction.North, Direction.South };
case Direction.East:
case Direction.West:
return new[] { Direction.East, Direction.West };
default:
return Array.Empty<Direction>();
}
}
public (int, int) GetOffsetForDirection(Direction direction)
{
return direction switch
{
Direction.North => (1, 0),
Direction.South => (-1, 0),
Direction.East => (0, 1),
Direction.West => (0, -1),
_ => (0, 0)
};
}
public void SpawnSpellHelper(List<EntitySpawnEntry> entityEntries, EntityCoordinates entityCoords, float? lifetime, Vector2 offsetVector2)
{
var getPrototypes = EntitySpawnCollection.GetSpawns(entityEntries, _random);
var offsetCoords = entityCoords;
foreach (var proto in getProtos)
foreach (var proto in getPrototypes)
{
// TODO: Share this code with instant because they're both doing similar things for positioning.
var entity = Spawn(proto, offsetCoords);
@@ -404,8 +312,6 @@ public sealed class MagicSystem : EntitySystem
}
}
#endregion
private void Speak(BaseActionEvent args)
{
if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech))
@@ -414,4 +320,6 @@ public sealed class MagicSystem : EntitySystem
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(speak.Speech),
InGameICChatType.Speak, false);
}
#endregion
}

View File

@@ -84,9 +84,18 @@ namespace Content.Server.Power.EntitySystems
while (query.MoveNext(out var uid, out var comp, out var batt))
{
if (!comp.AutoRecharge) continue;
if (comp.AutoRechargeRate > 0)
{
if (batt.IsFullyCharged) continue;
SetCharge(uid, batt.CurrentCharge + comp.AutoRechargeRate * frameTime, batt);
}
if (comp.AutoRechargeRate < 0) //self discharging
{
if (batt.Charge == 0) continue;
UseCharge(uid, -comp.AutoRechargeRate * frameTime, batt);
}
}
}
/// <summary>

View File

@@ -2,6 +2,8 @@
using Content.Shared.Eye;
using Content.Shared.Movement.Systems;
using Content.Shared.Physics;
using Content.Shared.Stealth;
using Content.Shared.Stealth.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
@@ -10,11 +12,10 @@ namespace Content.Server._White.IncorporealSystem;
public sealed class IncorporealSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
[Dependency] private readonly SharedStealthSystem _stealth = default!;
public override void Initialize()
{
@@ -41,6 +42,9 @@ public sealed class IncorporealSystem : EntitySystem
_visibilitySystem.RefreshVisibility(uid);
}
Spawn("EffectEmpPulse", Transform(uid).Coordinates);
EnsureComp<StealthComponent>(uid);
_stealth.SetVisibility(uid, -1);
_movement.RefreshMovementSpeedModifiers(uid);
}
@@ -50,8 +54,8 @@ public sealed class IncorporealSystem : EntitySystem
{
var fixture = fixtures.Fixtures.First();
_physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) (CollisionGroup.FlyingMobMask | CollisionGroup.GhostImpassable), fixtures);
_physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.FlyingMobLayer, fixtures);
_physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) (CollisionGroup.MobMask | CollisionGroup.GhostImpassable), fixtures);
_physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.MobLayer, fixtures);
}
if (TryComp<VisibilityComponent>(uid, out var visibility))
@@ -62,6 +66,10 @@ public sealed class IncorporealSystem : EntitySystem
}
component.MovementSpeedBuff = 1;
Spawn("EffectEmpPulse", Transform(uid).Coordinates);
_stealth.SetVisibility(uid, 1);
RemComp<StealthComponent>(uid);
_movement.RefreshMovementSpeedModifiers(uid);
}

View File

@@ -0,0 +1,183 @@
using Content.Shared._White.Wizard;
using Content.Shared._White.Wizard.Charging;
using Content.Shared.Follower;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
namespace Content.Server._White.Wizard.Charging;
public sealed class ChargingSystem : SharedChargingSystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly FollowerSystem _followerSystem = default!;
private readonly Dictionary<EntityUid, List<EntityUid>> _charges = new();
private readonly Dictionary<EntityUid, EntityUid> _chargingLoops = new();
private readonly Dictionary<EntityUid, EntityUid> _chargedLoop = new();
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<RequestSpellChargingAudio>(OnCharging);
SubscribeNetworkEvent<RequestSpellChargedAudio>(OnCharged);
SubscribeNetworkEvent<RequestAudioSpellStop>(OnStop);
SubscribeLocalEvent<PlayerDetachedEvent>(OnDetach);
SubscribeNetworkEvent<AddWizardChargeEvent>(Add);
SubscribeNetworkEvent<RemoveWizardChargeEvent>(Remove);
}
#region Audio
private void OnCharging(RequestSpellChargingAudio msg, EntitySessionEventArgs args)
{
var user = args.SenderSession?.AttachedEntity;
if (user == null)
return;
var shouldLoop = msg.Loop;
var sound = msg.Sound;
if (!shouldLoop)
{
_audio.PlayPvs(sound, user.Value);
return;
}
if (_chargingLoops.TryGetValue(user.Value, out var currentStream))
{
_audio.Stop(currentStream);
_chargingLoops.Remove(user.Value);
}
var newStream = _audio.PlayPvs(sound, user.Value, AudioParams.Default.WithLoop(true));
if (newStream.HasValue)
{
_chargingLoops[user.Value] = newStream.Value.Entity;
}
}
private void OnCharged(RequestSpellChargedAudio msg, EntitySessionEventArgs args)
{
var user = args.SenderSession?.AttachedEntity;
if (user == null)
return;
if (_chargingLoops.TryGetValue(user.Value, out var currentStream))
{
_audio.Stop(currentStream);
_chargingLoops.Remove(user.Value);
}
var shouldLoop = msg.Loop;
var sound = msg.Sound;
if (!shouldLoop)
{
_audio.PlayPvs(sound, user.Value);
return;
}
if (_chargedLoop.TryGetValue(user.Value, out var chargedLoop))
{
_audio.Stop(chargedLoop);
_chargedLoop.Remove(user.Value);
}
var newStream = _audio.PlayPvs(sound, user.Value, AudioParams.Default.WithLoop(true));
if (newStream.HasValue)
{
_chargedLoop[user.Value] = newStream.Value.Entity;
}
}
private void OnStop(RequestAudioSpellStop msg, EntitySessionEventArgs args)
{
var user = args.SenderSession?.AttachedEntity;
if (user == null)
return;
if (_chargingLoops.TryGetValue(user.Value, out var currentStream))
{
_audio.Stop(currentStream);
_chargingLoops.Remove(user.Value);
}
if (_chargedLoop.TryGetValue(user.Value, out var chargedLoop))
{
_audio.Stop(chargedLoop);
_chargedLoop.Remove(user.Value);
}
}
private void OnDetach(PlayerDetachedEvent msg, EntitySessionEventArgs args)
{
var user = msg.Entity;
if (_chargingLoops.TryGetValue(user, out var currentStream))
{
_audio.Stop(currentStream);
_chargingLoops.Remove(user);
}
if (_chargedLoop.TryGetValue(user, out var chargedLoop))
{
_audio.Stop(chargedLoop);
_chargedLoop.Remove(user);
}
}
#endregion
#region Charges
private void Add(AddWizardChargeEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity != null)
AddCharge(args.SenderSession.AttachedEntity.Value, msg.ChargeProto);
}
private void Remove(RemoveWizardChargeEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity != null)
RemoveAllCharges(args.SenderSession.AttachedEntity.Value);
}
#endregion
#region Helpers
public void AddCharge(EntityUid uid, string msgChargeProto)
{
var itemEnt = Spawn(msgChargeProto, Transform(uid).Coordinates);
_followerSystem.StartFollowingEntity(itemEnt, uid);
if (!_charges.ContainsKey(uid))
{
_charges[uid] = new List<EntityUid>();
}
_charges[uid].Add(itemEnt);
}
public void RemoveAllCharges(EntityUid uid)
{
if (!_charges.ContainsKey(uid))
return;
foreach (var followerEnt in _charges[uid])
{
Del(followerEnt);
}
_charges.Remove(uid);
}
#endregion
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server._White.Wizard.Magic.Amaterasu;
[RegisterComponent]
public sealed partial class AmaterasuComponent : Component
{
}

View File

@@ -0,0 +1,34 @@
using Content.Server.Atmos.Components;
using Content.Server.Body.Systems;
using Content.Shared.Mobs;
namespace Content.Server._White.Wizard.Magic.Amaterasu;
public sealed class AmaterasuSystem : EntitySystem
{
[Dependency] private readonly BodySystem _bodySystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AmaterasuComponent, MobStateChangedEvent>(OnMobState);
}
private void OnMobState(EntityUid uid, AmaterasuComponent component, MobStateChangedEvent args)
{
if (args.NewMobState is MobState.Critical or MobState.Dead)
{
if(!TryComp<FlammableComponent>(uid, out var flammable))
return;
if (flammable.OnFire)
{
_bodySystem.GibBody(uid);
return;
}
RemComp<AmaterasuComponent>(uid);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server._White.Wizard.Magic.Other;
[RegisterComponent]
public sealed partial class InstantRecallComponent : Component
{
public EntityUid? Item;
}

View File

@@ -0,0 +1,4 @@
namespace Content.Server._White.Wizard.Magic.TeslaProjectile;
[RegisterComponent]
public sealed partial class TeslaProjectileComponent : Component {}

View File

@@ -0,0 +1,21 @@
using Content.Server.Lightning;
using Content.Shared.Projectiles;
namespace Content.Server._White.Wizard.Magic.TeslaProjectile;
public sealed class TeslaProjectileSystem : EntitySystem
{
[Dependency] private readonly LightningSystem _lightning = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TeslaProjectileComponent, ProjectileHitEvent>(OnStartCollide);
}
private void OnStartCollide(Entity<TeslaProjectileComponent> ent, ref ProjectileHitEvent args)
{
_lightning.ShootRandomLightnings(ent, 2, 4, arcDepth:2);
}
}

View File

@@ -0,0 +1,701 @@
using System.Linq;
using System.Numerics;
using Content.Server._White.IncorporealSystem;
using Content.Server._White.Wizard.Magic.Amaterasu;
using Content.Server._White.Wizard.Magic.Other;
using Content.Server.Abilities.Mime;
using Content.Server.Administration.Commands;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Chat.Systems;
using Content.Server.Emp;
using Content.Server.Lightning;
using Content.Server.Magic;
using Content.Server.Singularity.EntitySystems;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared._White.Wizard;
using Content.Shared._White.Wizard.Magic;
using Content.Shared.Actions;
using Content.Shared.Cluwne;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Item;
using Content.Shared.Magic;
using Content.Shared.Maps;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.StatusEffect;
using Content.Shared.Throwing;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
namespace Content.Server._White.Wizard.Magic;
public sealed class WizardSpellsSystem : EntitySystem
{
#region Dependencies
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly LightningSystem _lightning = default!;
[Dependency] private readonly MagicSystem _magicSystem = default!;
[Dependency] private readonly GravityWellSystem _gravityWell = default!;
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly EmpSystem _empSystem = default!;
#endregion
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<InstantRecallSpellEvent>(OnInstantRecallSpell);
SubscribeLocalEvent<MimeTouchSpellEvent>(OnMimeTouchSpell);
SubscribeLocalEvent<BananaTouchSpellEvent>(OnBananaTouchSpell);
SubscribeLocalEvent<CluwneCurseSpellEvent>(OnCluwneCurseSpell);
SubscribeLocalEvent<EmpSpellEvent>(OnEmpSpell);
SubscribeLocalEvent<EtherealJauntSpellEvent>(OnJauntSpell);
SubscribeLocalEvent<BlinkSpellEvent>(OnBlinkSpell);
SubscribeLocalEvent<ForceWallSpellEvent>(OnForcewallSpell);
SubscribeLocalEvent<CardsSpellEvent>(OnCardsSpell);
SubscribeLocalEvent<FireballSpellEvent>(OnFireballSpell);
SubscribeLocalEvent<ForceSpellEvent>(OnForceSpell);
SubscribeLocalEvent<ArcSpellEvent>(OnArcSpell);
SubscribeLocalEvent<MagicComponent, BeforeCastSpellEvent>(OnBeforeCastSpell);
}
#region Instant Recall
private void OnInstantRecallSpell(InstantRecallSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
if (!TryComp<HandsComponent>(msg.Performer, out var handsComponent))
return;
if (!TryComp<InstantRecallComponent>(msg.Action, out var recallComponent))
{
_popupSystem.PopupEntity("Что-то поломалось!", msg.Performer, msg.Performer);
return;
}
if (handsComponent.ActiveHandEntity != null)
{
if (HasComp<VirtualItemComponent>(handsComponent.ActiveHandEntity.Value))
{
_popupSystem.PopupEntity("Не могу работать с этим!", msg.Performer, msg.Performer);
return;
}
recallComponent.Item = handsComponent.ActiveHandEntity.Value;
_popupSystem.PopupEntity($"Сопряжено с {MetaData(handsComponent.ActiveHandEntity.Value).EntityName}", msg.Performer, msg.Performer);
return;
}
if (handsComponent.ActiveHandEntity == null && recallComponent.Item != null)
{
var coordsItem = Transform(recallComponent.Item.Value).Coordinates;
var coordsPerformer = Transform(msg.Performer).Coordinates;
Spawn("EffectEmpPulse", coordsItem);
_transformSystem.SetCoordinates(recallComponent.Item.Value, coordsPerformer);
_transformSystem.AttachToGridOrMap(recallComponent.Item.Value);
_handsSystem.TryForcePickupAnyHand(msg.Performer, recallComponent.Item.Value);
msg.Handled = true;
return;
}
_popupSystem.PopupEntity("Нет привязки.", msg.Performer, msg.Performer);
}
#endregion
#region Mime Touch
private void OnMimeTouchSpell(MimeTouchSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
if (!HasComp<HumanoidAppearanceComponent>(msg.Target))
{
_popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
return;
}
SetOutfitCommand.SetOutfit(msg.Target, "MimeGear", EntityManager);
EnsureComp<MimePowersComponent>(msg.Target);
Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
msg.Handled = true;
Speak(msg);
}
#endregion
#region Banana Touch
private void OnBananaTouchSpell(BananaTouchSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
if (!HasComp<HumanoidAppearanceComponent>(msg.Target))
{
_popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
return;
}
SetOutfitCommand.SetOutfit(msg.Target, "ClownGear", EntityManager);
EnsureComp<ClumsyComponent>(msg.Target);
Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
msg.Handled = true;
Speak(msg);
}
#endregion
#region Cluwne Curse
private void OnCluwneCurseSpell(CluwneCurseSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
if (!HasComp<HumanoidAppearanceComponent>(msg.Target))
{
_popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
return;
}
EnsureComp<CluwneComponent>(msg.Target);
Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
msg.Handled = true;
Speak(msg);
}
#endregion
#region EMP
private void OnEmpSpell(EmpSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
var coords = _transformSystem.ToMapCoordinates(Transform(msg.Performer).Coordinates);
_empSystem.EmpPulse(coords, 15, 1000000, 60f);
msg.Handled = true;
Speak(msg);
}
#endregion
#region Ethereal Jaunt
private void OnJauntSpell(EtherealJauntSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
if (_statusEffectsSystem.HasStatusEffect(msg.Performer, "Incorporeal"))
{
_popupSystem.PopupEntity("Вы уже в потустороннем мире", msg.Performer, msg.Performer);
return;
}
Spawn("AdminInstantEffectSmoke10", Transform(msg.Performer).Coordinates);
_statusEffectsSystem.TryAddStatusEffect<IncorporealComponent>(msg.Performer, "Incorporeal", TimeSpan.FromSeconds(10), false);
msg.Handled = true;
Speak(msg);
}
#endregion
#region Blink
private void OnBlinkSpell(BlinkSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
var transform = Transform(msg.Performer);
var oldCoords = transform.Coordinates;
EntityCoordinates coords = default;
var foundTeleportPos = false;
var attempts = 10;
while (attempts > 0)
{
attempts--;
var random = new Random().Next(10, 20);
var offset = transform.LocalRotation.ToWorldVec().Normalized();
var direction = transform.LocalRotation.GetDir().ToVec();
var newOffset = offset + direction * random;
coords = transform.Coordinates.Offset(newOffset).SnapToGrid(EntityManager);
var tile = coords.GetTileRef();
if (tile != null && _turf.IsTileBlocked(tile.Value, CollisionGroup.AllMask))
continue;
foundTeleportPos = true;
break;
}
if (!foundTeleportPos)
return;
_transformSystem.SetCoordinates(msg.Performer, coords);
_transformSystem.AttachToGridOrMap(msg.Performer);
_audio.PlayPvs("/Audio/White/Cult/veilin.ogg", coords);
_audio.PlayPvs("/Audio/White/Cult/veilout.ogg", oldCoords);
Spawn("AdminInstantEffectSmoke10", oldCoords);
Spawn("AdminInstantEffectSmoke10", coords);
msg.Handled = true;
Speak(msg);
}
#endregion
#region Forcewall
private void OnForcewallSpell(ForceWallSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
ForcewallSpellDefault(msg);
break;
case ActionUseType.Charge:
ForcewallSpellCharge(msg);
break;
case ActionUseType.AltUse:
ForcewallSpellAlt(msg);
break;
}
msg.Handled = true;
Speak(msg);
}
private void ForcewallSpellDefault(ForceWallSpellEvent msg)
{
var transform = Transform(msg.Performer);
foreach (var position in _magicSystem.GetPositionsInFront(transform))
{
var ent = Spawn(msg.Prototype, position.SnapToGrid(EntityManager, _mapManager));
var comp = EnsureComp<PreventCollideComponent>(ent);
comp.Uid = msg.Performer;
}
}
private void ForcewallSpellCharge(ForceWallSpellEvent msg)
{
var xform = Transform(msg.Performer);
var positions = GetArenaPositions(xform, msg.ChargeLevel);
foreach (var position in positions)
{
var ent = Spawn(msg.Prototype, position);
var comp = EnsureComp<PreventCollideComponent>(ent);
comp.Uid = msg.Performer;
}
}
private void ForcewallSpellAlt(ForceWallSpellEvent msg)
{
var xform = Transform(msg.TargetUid);
var positions = GetArenaPositions(xform, 2);
foreach (var direction in positions)
{
var ent = Spawn(msg.Prototype, direction);
var comp = EnsureComp<PreventCollideComponent>(ent);
comp.Uid = msg.Performer;
}
}
#endregion
#region Cards
private void OnCardsSpell(CardsSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
CardsSpellDefault(msg);
break;
case ActionUseType.Charge:
CardsSpellCharge(msg);
break;
case ActionUseType.AltUse:
CardsSpellAlt(msg);
break;
}
msg.Handled = true;
Speak(msg);
}
private void CardsSpellDefault(CardsSpellEvent msg)
{
var xform = Transform(msg.Performer);
for (var i = 0; i < 10; i++)
{
foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
{
var mapPos = _transformSystem.ToMapCoordinates(pos);
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _)
? pos.WithEntityId(gridUid, EntityManager)
: new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
var ent = Spawn(msg.Prototype, spawnCoords);
var direction = msg.Target.ToMapPos(EntityManager, _transformSystem) - spawnCoords.ToMapPos(EntityManager, _transformSystem);
var randomizedDirection = direction + new Vector2(_random.Next(-2, 2), _random.Next(-2, 2));
_throwingSystem.TryThrow(ent, randomizedDirection, 60, msg.Performer);
}
}
}
private void CardsSpellCharge(CardsSpellEvent msg)
{
var xform = Transform(msg.Performer);
var count = 5 * msg.ChargeLevel;
var angleStep = 360f / count;
for (var i = 0; i < count; i++)
{
var angle = i * angleStep;
var direction = new Vector2(MathF.Cos(MathHelper.DegreesToRadians(angle)), MathF.Sin(MathHelper.DegreesToRadians(angle)));
foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
{
var mapPos = _transformSystem.ToMapCoordinates(pos);
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _)
? pos.WithEntityId(gridUid, EntityManager)
: new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
var ent = Spawn(msg.Prototype, spawnCoords);
_throwingSystem.TryThrow(ent, direction, 60, msg.Performer);
}
}
}
private void CardsSpellAlt(CardsSpellEvent msg)
{
if (!HasComp<ItemComponent>(msg.TargetUid))
return;
Del(msg.TargetUid);
var item = Spawn(msg.Prototype);
_handsSystem.TryPickupAnyHand(msg.Performer, item);
}
#endregion
#region Fireball
private void OnFireballSpell(FireballSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
FireballSpellDefault(msg);
break;
case ActionUseType.Charge:
FireballSpellCharge(msg);
break;
case ActionUseType.AltUse:
FireballSpellAlt(msg);
break;
}
msg.Handled = true;
Speak(msg);
}
private void FireballSpellDefault(FireballSpellEvent msg)
{
var xform = Transform(msg.Performer);
foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
{
var mapPos = _transformSystem.ToMapCoordinates(pos);
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid)
? pos.WithEntityId(gridUid, EntityManager)
: new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
var userVelocity = Vector2.Zero;
if (grid != null && TryComp(gridUid, out PhysicsComponent? physics))
userVelocity = physics.LinearVelocity;
var ent = Spawn(msg.Prototype, spawnCoords);
var direction = msg.Target.ToMapPos(EntityManager, _transformSystem) - spawnCoords.ToMapPos(EntityManager, _transformSystem);
_gunSystem.ShootProjectile(ent, direction, userVelocity, msg.Performer, msg.Performer);
}
}
private void FireballSpellCharge(FireballSpellEvent msg)
{
var coords = Transform(msg.Performer).Coordinates;
var targets = _lookup.GetEntitiesInRange<FlammableComponent>(coords, 2 * msg.ChargeLevel);
foreach (var target in targets.Where(target => target.Owner != msg.Performer))
{
target.Comp.FireStacks += 3;
_flammableSystem.Ignite(target, msg.Performer);
}
}
private void FireballSpellAlt(FireballSpellEvent msg)
{
if (!TryComp<FlammableComponent>(msg.TargetUid, out var flammableComponent))
return;
flammableComponent.FireStacks += 4;
_flammableSystem.Ignite(msg.TargetUid, msg.Performer);
EnsureComp<AmaterasuComponent>(msg.TargetUid);
}
#endregion
#region Force
private void OnForceSpell(ForceSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
ForceSpellDefault(msg);
break;
case ActionUseType.Charge:
ForceSpellCharge(msg);
break;
case ActionUseType.AltUse:
ForceSpellAlt(msg);
break;
}
msg.Handled = true;
Speak(msg);
}
private void ForceSpellDefault(ForceSpellEvent msg)
{
Spawn("AdminInstantEffectMinusGravityWell", msg.Target);
}
private void ForceSpellCharge(ForceSpellEvent msg)
{
_gravityWell.GravPulse(msg.Performer, 15, 0, -80 * msg.ChargeLevel, -2 * msg.ChargeLevel);
}
private void ForceSpellAlt(ForceSpellEvent msg)
{
_gravityWell.GravPulse(msg.Target, 10, 0, 200, 10);
}
#endregion
#region Arc
private void OnArcSpell(ArcSpellEvent msg)
{
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
ArcSpellDefault(msg);
break;
case ActionUseType.Charge:
ArcSpellCharge(msg);
break;
case ActionUseType.AltUse:
ArcSpellAlt(msg);
break;
}
msg.Handled = true;
Speak(msg);
}
private void ArcSpellDefault(ArcSpellEvent msg)
{
const int possibleEntitiesCount = 2;
var entitiesInRange = _lookup.GetEntitiesInRange(msg.Target, 1);
var entitiesToHit = entitiesInRange.Where(HasComp<MobStateComponent>).Take(possibleEntitiesCount);
foreach (var entity in entitiesToHit)
{
_lightning.ShootLightning(msg.Performer, entity);
}
}
private void ArcSpellCharge(ArcSpellEvent msg)
{
_lightning.ShootRandomLightnings(msg.Performer, 2 * msg.ChargeLevel, msg.ChargeLevel * 2, arcDepth: 2);
}
private void ArcSpellAlt(ArcSpellEvent msg)
{
var xform = Transform(msg.Performer);
foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
{
var mapPos = _transformSystem.ToMapCoordinates(pos);
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid)
? pos.WithEntityId(gridUid, EntityManager)
: new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
var userVelocity = Vector2.Zero;
if (grid != null && TryComp(gridUid, out PhysicsComponent? physics))
userVelocity = physics.LinearVelocity;
var ent = Spawn(msg.Prototype, spawnCoords);
var direction = msg.Target.ToMapPos(EntityManager, _transformSystem) - spawnCoords.ToMapPos(EntityManager, _transformSystem);
_gunSystem.ShootProjectile(ent, direction, userVelocity, msg.Performer, msg.Performer);
}
}
#endregion
#region Helpers
private void Speak(BaseActionEvent args)
{
if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech))
return;
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(speak.Speech),
InGameICChatType.Speak, false);
}
private List<EntityCoordinates> GetArenaPositions(TransformComponent casterXform, int arenaSize)
{
var positions = new List<EntityCoordinates>();
arenaSize--;
for (var i = -arenaSize; i <= arenaSize; i++)
{
for (var j = -arenaSize; j <= arenaSize; j++)
{
var position = new Vector2(i, j);
var coordinates = casterXform.Coordinates.Offset(position);
positions.Add(coordinates);
}
}
return positions;
}
private bool CheckRequirements(EntityUid spell, EntityUid performer)
{
var ev = new BeforeCastSpellEvent(performer);
RaiseLocalEvent(spell, ref ev);
return !ev.Cancelled;
}
private void OnBeforeCastSpell(Entity<MagicComponent> ent, ref BeforeCastSpellEvent args)
{
var comp = ent.Comp;
var hasReqs = false;
if (comp.RequiresClothes)
{
var enumerator = _inventory.GetSlotEnumerator(args.Performer, SlotFlags.OUTERCLOTHING | SlotFlags.HEAD);
while (enumerator.MoveNext(out var containerSlot))
{
if (containerSlot.ContainedEntity is { } item)
hasReqs = HasComp<WizardClothesComponent>(item);
else
hasReqs = false;
if (!hasReqs)
break;
}
}
if (!hasReqs)
{
args.Cancelled = true;
_popupSystem.PopupEntity("Missing Requirements! You need to wear your robe and hat!", args.Performer, args.Performer);
}
}
#endregion
}

View File

@@ -0,0 +1,8 @@
using Content.Shared._White.Wizard.ScrollSystem;
namespace Content.Server._White.Wizard.Scrolls;
public sealed class ScrollSystem : SharedScrollSystem
{
protected override void BurnScroll(EntityUid uid) => Del(uid);
}

View File

@@ -81,8 +81,10 @@ public sealed class GetItemActionsEvent : EntityEventArgs
public sealed class RequestPerformActionEvent : EntityEventArgs
{
public readonly NetEntity Action;
public readonly NetEntity? EntityTarget;
public NetEntity? EntityTarget;
public readonly NetCoordinates? EntityCoordinatesTarget;
public ActionUseType ActionUseType = ActionUseType.Default;
public int ChargeLevel;
public RequestPerformActionEvent(NetEntity action)
{
@@ -148,6 +150,8 @@ public abstract partial class WorldTargetActionEvent : BaseActionEvent
/// The coordinates of the location that the user targeted.
/// </summary>
public EntityCoordinates Target;
public EntityUid TargetUid;
}
/// <summary>
@@ -161,4 +165,18 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs
/// The user performing the action.
/// </summary>
public EntityUid Performer;
public EntityUid Action;
public ActionUseType ActionUseType = ActionUseType.Default;
public int ChargeLevel;
}
[Serializable, NetSerializable]
public enum ActionUseType
{
Default, // left mouse click.
Charge, // Holding right mouse click(has 4 charges).
AltUse // Alt + left mouse click.
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Interaction;
using Robust.Shared.Audio;
namespace Content.Shared.Actions;
@@ -40,4 +41,28 @@ public abstract partial class BaseTargetActionComponent : BaseActionComponent
/// over lay in place of the currently held item "held item".
/// </summary>
[DataField("targetingIndicator")] public bool TargetingIndicator = true;
[DataField]
public bool IsAltEnabled;
[DataField]
public bool IsChargeEnabled;
[DataField]
public string ChargeProto = "MagicFollowerEntity";
[DataField]
public int MaxChargeLevel = 4;
[DataField]
public SoundSpecifier ChargingSound = new SoundPathSpecifier("/Audio/White/Magic/chargingfallback.ogg");
[DataField]
public bool LoopCharging = true;
[DataField]
public SoundSpecifier MaxChargedSound = new SoundPathSpecifier("/Audio/White/Magic/maxchargefallback.ogg");
[DataField]
public bool LoopMaxCharged;
}

View File

@@ -413,6 +413,8 @@ public abstract class SharedActionsSystem : EntitySystem
if (worldAction.Event != null)
{
worldAction.Event.Target = entityCoordinatesTarget;
if (ev.EntityTarget != null)
worldAction.Event.TargetUid = GetEntity(ev.EntityTarget.Value);
Dirty(actionEnt, worldAction);
performEvent = worldAction.Event;
}
@@ -430,7 +432,12 @@ public abstract class SharedActionsSystem : EntitySystem
}
if (performEvent != null)
{
performEvent.Performer = user;
performEvent.Action = actionEnt;
performEvent.ActionUseType = ev.ActionUseType;
performEvent.ChargeLevel = ev.ChargeLevel;
}
// All checks passed. Perform the action!
PerformAction(user, component, actionEnt, action, performEvent, curTime);
@@ -677,6 +684,8 @@ public abstract class SharedActionsSystem : EntitySystem
/// <param name="performer">Entity to receive the actions</param>
/// <param name="actions">The actions to add</param>
/// <param name="container">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
/// <param name="comp">ActionsComponent.</param>
/// <param name="containerComp">ActionContainerComponent.</param>
public void GrantActions(EntityUid performer, IEnumerable<EntityUid> actions, EntityUid container, ActionsComponent? comp = null, ActionsContainerComponent? containerComp = null)
{
if (!Resolve(container, ref containerComp))

View File

@@ -0,0 +1,5 @@
namespace Content.Shared._White.Wizard.Charging;
public abstract class SharedChargingSystem : EntitySystem
{
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared._White.Wizard.Magic;
[RegisterComponent, NetworkedComponent]
public sealed partial class MagicComponent : Component
{
/// <summary>
/// Does this spell require Wizard Robes & Hat?
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool RequiresClothes;
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared._White.Wizard.Magic;
[RegisterComponent, NetworkedComponent]
public sealed partial class WizardClothesComponent : Component
{
}

View File

@@ -0,0 +1,43 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared._White.Wizard.ScrollSystem;
[RegisterComponent, NetworkedComponent, Access(typeof(SharedScrollSystem))]
public sealed partial class ScrollComponent : Component
{
/// <summary>
/// ActionId to give on use.
/// </summary>
[DataField]
[ViewVariables]
public string ActionId;
/// <summary>
/// How time it takes to learn.
/// </summary>
[DataField]
[ViewVariables(VVAccess.ReadWrite)]
public float LearnTime = 5f;
/// <summary>
/// Popup on learn.
/// </summary>
[DataField]
[ViewVariables]
public string LearnPopup;
/// <summary>
/// Sound to play on use.
/// </summary>
[DataField]
[ViewVariables]
public SoundSpecifier UseSound;
/// <summary>
/// Sound to play after use.
/// </summary>
[DataField]
[ViewVariables]
public SoundSpecifier AfterUseSound;
}

View File

@@ -0,0 +1,87 @@
using Content.Shared.Actions;
using Content.Shared.DoAfter;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
namespace Content.Shared._White.Wizard.ScrollSystem;
public abstract class SharedScrollSystem : EntitySystem
{
#region Dependencies
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
#endregion
#region Init
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ScrollComponent, UseInHandEvent>(OnScrollUse);
SubscribeLocalEvent<ScrollComponent, ScrollDoAfterEvent>(OnScrollDoAfter);
}
#endregion
#region Handlers
private void OnScrollUse(EntityUid uid, ScrollComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.LearnTime, new ScrollDoAfterEvent(), uid, target: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
NeedHand = true
};
if (_net.IsServer)
{
_audioSystem.PlayPvs(component.UseSound, args.User);
}
_popupSystem.PopupClient($"You start learning about {component.LearnPopup}.", args.User, args.User, PopupType.Medium);
_doAfterSystem.TryStartDoAfter(doAfterEventArgs);
args.Handled = true;
}
private void OnScrollDoAfter(EntityUid uid, ScrollComponent component, ScrollDoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
_actionsSystem.AddAction(args.User, component.ActionId);
if (_net.IsServer)
{
_audioSystem.PlayEntity(component.AfterUseSound, args.User, args.User);
}
_popupSystem.PopupClient($"You learned much about {component.LearnPopup}. The scroll is slowly burning in your hands.", args.User, args.User, PopupType.Medium);
BurnScroll(uid);
args.Handled = true;
}
#endregion
#region Helpers
protected virtual void BurnScroll(EntityUid uid) {}
#endregion
}

View File

@@ -0,0 +1,175 @@
using Content.Shared.Actions;
using Content.Shared.DoAfter;
using Content.Shared.Magic;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared._White.Wizard;
#region HelperEvents
[Serializable, NetSerializable]
public sealed partial class ScrollDoAfterEvent : SimpleDoAfterEvent
{
}
[ByRefEvent]
public struct BeforeCastSpellEvent
{
public EntityUid Performer;
public bool Cancelled;
public BeforeCastSpellEvent(EntityUid performer)
{
Performer = performer;
}
}
[Serializable, NetSerializable]
public sealed partial class AddWizardChargeEvent : EntityEventArgs
{
public string ChargeProto;
public AddWizardChargeEvent(string chargeProto)
{
ChargeProto = chargeProto;
}
}
[Serializable, NetSerializable]
public sealed partial class RemoveWizardChargeEvent : EntityEventArgs
{
}
[Serializable, NetSerializable]
public sealed partial class RequestSpellChargingAudio : EntityEventArgs
{
public SoundSpecifier Sound;
public bool Loop;
public RequestSpellChargingAudio(SoundSpecifier sound, bool loop)
{
Sound = sound;
Loop = loop;
}
}
[Serializable, NetSerializable]
public sealed partial class RequestSpellChargedAudio : EntityEventArgs
{
public SoundSpecifier Sound;
public bool Loop;
public RequestSpellChargedAudio(SoundSpecifier sound, bool loop)
{
Sound = sound;
Loop = loop;
}
}
[Serializable, NetSerializable]
public sealed partial class RequestAudioSpellStop : EntityEventArgs
{
}
#endregion
#region Spells
public sealed partial class ArcSpellEvent : WorldTargetActionEvent, ISpeakSpell
{
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Prototype = default!;
[DataField("posData")]
public MagicSpawnData Pos = new TargetCasterPos();
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class ForceSpellEvent : WorldTargetActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class FireballSpellEvent : WorldTargetActionEvent, ISpeakSpell
{
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Prototype = default!;
[DataField("posData")]
public MagicSpawnData Pos = new TargetCasterPos();
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class CardsSpellEvent : WorldTargetActionEvent, ISpeakSpell
{
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Prototype = default!;
[DataField("posData")]
public MagicSpawnData Pos = new TargetCasterPos();
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class ForceWallSpellEvent : WorldTargetActionEvent, ISpeakSpell
{
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Prototype = default!;
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class BlinkSpellEvent : InstantActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class EtherealJauntSpellEvent : InstantActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class EmpSpellEvent : InstantActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class CluwneCurseSpellEvent : EntityTargetActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class BananaTouchSpellEvent : EntityTargetActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class MimeTouchSpellEvent : EntityTargetActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
public sealed partial class InstantRecallSpellEvent : InstantActionEvent, ISpeakSpell
{
[DataField("speech")]
public string? Speech { get; private set; }
}
#endregion

View File

@@ -152,3 +152,23 @@
license: "CC0-1.0"
copyright: "dakamakat on freesound.org"
source: "https://freesound.org/people/Dakamakat/sounds/717370/"
- files: ["energyshield_up.ogg"]
license: "CC0-1.0"
copyright: "unfa on freesound.org"
source: "https://freesound.org/people/unfa/sounds/584173/"
- files: ["energyshield_down.ogg"]
license: "CC-BY-4.0"
copyright: "SilverIllusionist on freesound.org"
source: "https://freesound.org/people/SilverIllusionist/sounds/673556/"
- files: ["energyshield_ambient.ogg"]
license: "CC0-1.0"
copyright: "julianmateo_ on freesound.org"
source: "https://freesound.org/people/julianmateo_/sounds/524165/"
- files: ["energyshield_parry.ogg"]
license: "CC-BY-4.0"
copyright: "Robinhood76 on freesound.org"
source: "https://freesound.org/people/Robinhood76/sounds/107613/"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,9 @@
energy-dome-access-denied = Access denied
energy-dome-recharging = Recharging...
energy-dome-no-power = Low battery
energy-dome-no-cell = There is no power source
energy-dome-on-examine-is-on-message = The energy barrier is [color=darkgreen]up[/color].
energy-dome-on-examine-is-off-message = The energy barrier is [color=darkred]down[/color].
energy-dome-verb-toggle = Toggle energy dome

View File

@@ -32,6 +32,7 @@ research-technology-wave-particle-harnessing = Wave Particle Harnessing
research-technology-advanced-riot-control = Advanced Riot Control
research-technology-portable-microfusion-weaponry = Portable Microfusion Weaponry
research-technology-experimental-battery-ammo = Experimental Battery Ammo
research-technology-energy_barriers = Energy Barriers
research-technology-basic-shuttle-armament = Shuttle basic armament
research-technology-advanced-shuttle-weapon = Advanced shuttle weapons

View File

@@ -306,6 +306,9 @@ uplink-hardsuit-syndieelite-desc = An elite version of the blood-red hardsuit, w
uplink-clothing-outer-hardsuit-juggernaut-name = Cybersun Juggernaut Suit
uplink-clothing-outer-hardsuit-juggernaut-desc = Hyper resilient armor made of materials tested in the Tau chromosphere facility. The only thing that's going to be slowing you down is this suit... and tasers.
uplink-energy-dome-name = Personal energy dome
uplink-energy-dome-desc = A personal shield generator that protects the wearer from lasers and bullets but prevents from using ranged weapons himself. Comes with a small power cell.
# Misc
uplink-cyberpen-name = Cybersun Pen
uplink-cyberpen-desc = Cybersun's legal department pen, invaluable for forging documents and escaping prisons. Smells vaguely of hard-light and war profiteering.

View File

@@ -33,6 +33,16 @@
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: entity
id: ActionToggleDome
name: Toggle energy dome
description: Turn the energy barrier on or off.
noSpawn: true
components:
- type: InstantAction
icon: { sprite: Objects/Weapons/Melee/e_shield.rsi, state: eshield-on }
event: !type:ToggleActionEvent
- type: entity
id: ActionOpenStorageImplant
name: Open Storage Implant

View File

@@ -171,6 +171,16 @@
flatReductions:
Heat: 3
- type: damageModifierSet
id: HardLightBarrier
coefficients:
Heat: 0.8
Blunt: 0.8
Slash: 0.8
Piercing: 0.8
Cold: 0.8
Shock: 1.6
- type: damageModifierSet
id: Scale # Skin tougher, bones weaker, strong stomachs, cold-blooded (kindof)
coefficients:

View File

@@ -664,12 +664,36 @@
Heat: 0.25
Radiation: 0.25
Caustic: 0.75
- type: ClothingSpeedModifier
walkModifier: 0.8
sprintModifier: 0.8
- type: HeldSpeedModifier
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetHardsuitWizard
- type: ContainerContainer
containers:
cell_slot: !type:ContainerSlot
toggleable-clothing: !type:ContainerSlot
- type: PowerCellSlot
cellSlotId: cell_slot
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellMicroreactor
whitelist:
tags:
- PowerCell
- PowerCellSmall
- PowerCellHyper
- PowerCellMicroreactor
- type: EnergyDomeGenerator
damageEnergyDraw: 3
domePrototype: EnergyDomeSmallPink
- type: ClothingSpeedModifier
walkModifier: 1
sprintModifier: 1
- type: PowerCellDraw
drawRate: 8
useRate: 0
- type: UseDelay
delay: 10.0
#Ling Space Suit
- type: entity

View File

@@ -103,10 +103,34 @@
path: /Audio/Effects/Grenades/Supermatter/supermatter_loop.ogg
- type: GravityWell
maxRange: 8
baseRadialAcceleration: 10
baseRadialAcceleration: 250
baseTangentialAcceleration: 0
gravPulsePeriod: 0.01
- type: SingularityDistortion
intensity: 10
falloffPower: 1.5
- type: entity
id: AdminInstantEffectMinusGravityWell
suffix: Gravity Well
parent: AdminInstantEffectBase
components:
- type: SoundOnTrigger
removeOnTrigger: true
sound:
path: /Audio/Effects/Grenades/Supermatter/supermatter_start.ogg
volume: 5
- type: AmbientSound
enabled: true
volume: -5
range: 14
sound:
path: /Audio/Effects/Grenades/Supermatter/supermatter_loop.ogg
- type: GravityWell
maxRange: 10
baseRadialAcceleration: -200
baseTangentialAcceleration: -5
gravPulsePeriod: 0.01
- type: SingularityDistortion
intensity: 10
falloffPower: 1.5

View File

@@ -0,0 +1,129 @@
- type: entity
id: EnergyDomeBase
abstract: true
components:
- type: Sprite
drawdepth: Effects
noRot: true
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeCircle
radius: 0.8
density: 0
mask:
- None
layer:
- BulletImpassable
- Opaque
- type: Physics
bodyType: Static
- type: Damageable
damageContainer: Inorganic
damageModifierSet: HardLightBarrier
- type: AmbientSound
volume: 35
range: 5
sound:
path: /Audio/Machines/energyshield_ambient.ogg
- type: EnergyDome
- type: Tag
tags:
- HideContextMenu
- type: entity
id: EnergyDomeSmallPink
noSpawn: true
parent: EnergyDomeBase
components:
- type: Sprite
sprite: Effects/EnergyDome/energydome_small.rsi
layers:
- state: small
color: "#f5166b"
- type: PointLight
enabled: true
radius: 5
power: 2
color: "#f5166b"
- type: entity
id: EnergyDomeSmallRed
noSpawn: true
parent: EnergyDomeBase
components:
- type: Sprite
drawdepth: Effects
noRot: true
sprite: Effects/EnergyDome/energydome_small.rsi
layers:
- state: small
color: "#b00000"
- type: PointLight
enabled: true
radius: 5
power: 2
color: "#b00000"
- type: entity
id: EnergyDomeMediumBlue
noSpawn: true
parent: EnergyDomeBase
components:
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeCircle
radius: 1.8
density: 0
mask:
- None
layer:
- BulletImpassable
- Opaque
- type: Sprite
sprite: Effects/EnergyDome/energydome_medium.rsi
layers:
- state: medium
color: "#64b9de"
- type: PointLight
enabled: true
radius: 5
power: 10
color: "#64b9de"
- type: entity
id: EnergyDomeSlowing
noSpawn: true
parent: EnergyDomeBase
components:
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeCircle
radius: 2.8
density: 0
hard: false
mask:
- None
layer:
- MidImpassable
- type: Sprite
drawdepth: LowFloors
sprite: Effects/EnergyDome/energydome_slowdown_big.rsi
layers:
- state: big
color: "#a3d177"
- type: PointLight
enabled: true
radius: 5
power: 30
color: "#a3d177"
- type: DamageContacts
damage:
types:
Slash: -1.5
Piercing: -1.5

View File

@@ -297,6 +297,7 @@
- Muted
- Pacified
- StaminaModifier
- Incorporeal
- type: Blindable
# Other
- type: Temperature

View File

@@ -1,106 +0,0 @@
- type: entity
id: BaseSpellbook
name: spellbook
parent: BaseItem
abstract: true
components:
- type: Sprite
sprite: Objects/Misc/books.rsi
layers:
- state: book_demonomicon
- type: Spellbook
- type: Tag
tags:
- Spellbook
- type: entity
id: SpawnSpellbook
name: spawn spellbook
parent: BaseSpellbook
components:
- type: Spellbook
spells:
ActionSpawnMagicarpSpell: -1
- type: entity
id: ForceWallSpellbook
name: force wall spellbook
parent: BaseSpellbook
components:
- type: Sprite
sprite: Objects/Magic/spellbooks.rsi
layers:
- state: bookforcewall
- type: Spellbook
spells:
ActionForceWall: -1
- type: entity
id: BlinkBook
name: blink spellbook
parent: BaseSpellbook
components:
- type: Sprite
sprite: Objects/Magic/spellbooks.rsi
layers:
- state: spellbook
- type: Spellbook
spells:
ActionBlink: -1
- type: entity
id: SmiteBook
name: smite spellbook
parent: BaseSpellbook
components:
- type: Sprite
sprite: Objects/Magic/spellbooks.rsi
layers:
- state: spellbook
- type: Spellbook
spells:
ActionSmite: -1
- type: entity
id: KnockSpellbook
name: knock spellbook
parent: BaseSpellbook
components:
- type: Sprite
sprite: Objects/Magic/spellbooks.rsi
layers:
- state: bookknock
- type: Spellbook
spells:
ActionKnock: -1
- type: entity
id: FireballSpellbook
name: fireball spellbook
parent: BaseSpellbook
components:
- type: Sprite
sprite: Objects/Magic/spellbooks.rsi
layers:
- state: bookfireball
- type: Spellbook
spells:
ActionFireball: -1
- type: entity
id: ScrollRunes
name: scroll of runes
parent: BaseSpellbook
components:
- type: Item
size: Normal
- type: Sprite
sprite: Objects/Magic/magicactions.rsi
layers:
- state: spell_default
- type: Spellbook
spells:
ActionFlashRune: -1
ActionExplosionRune: -1
ActionIgniteRune: -1
ActionStunRune: -1

View File

@@ -0,0 +1,179 @@
- type: entity
name: blood red personal shield generator
description: A personal shield generator that protects the wearer from lasers and bullets but prevents from using ranged weapons himself. Uses a power cell.
id: EnergyDomeGeneratorPersonalSyndie
parent: BaseItem
components:
- type: Item
size: Ginormous
- type: Clothing
quickEquip: false
slots:
- Belt
- type: Sprite
sprite: Objects/Tools/EnergyDome/syndie.rsi
layers:
- state: icon
- type: ContainerContainer
containers:
cell_slot: !type:ContainerSlot
- type: PowerCellSlot
cellSlotId: cell_slot
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellSmall
whitelist:
tags:
- PowerCell
- PowerCellSmall
- type: EnergyDomeGenerator
damageEnergyDraw: 5
domePrototype: EnergyDomeSmallRed
- type: PowerCellDraw
drawRate: 10
useRate: 0
- type: UseDelay
delay: 10.0
- type: entity
name: BR-40c "Turtle"
description: A two-handed and heavy energy barrier with extremely low passive energy consumption. Can be tethered with a multitool.
id: EnergyDomeDirectionalTurtle
parent: BaseItem
components:
- type: Sprite
sprite: Objects/Tools/EnergyDome/reinhardt.rsi
layers:
- state: icon
- type: InteractionOutline
- type: MultiHandedItem
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.25,0.25,0.25"
density: 20
mask:
- ItemMask
restitution: 0.3
friction: 0.2
- type: Item
size: Ginormous
- type: HeldSpeedModifier
walkModifier: 0.7
sprintModifier: 0.7
- type: ContainerContainer
containers:
cell_slot: !type:ContainerSlot
- type: PowerCellSlot
cellSlotId: cell_slot
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellSmall
whitelist:
tags:
- PowerCell
- PowerCellSmall
- type: EnergyDomeGenerator
damageEnergyDraw: 7
domePrototype: EnergyDomeMediumBlue
canDeviceNetworkUse: true
- type: PowerCellDraw
drawRate: 2
useRate: 0
- type: UseDelay
delay: 10.0
- type: DeviceNetwork
deviceNetId: Wireless
receiveFrequencyId: BasicDevice
- type: WirelessNetworkConnection
range: 200
- type: DeviceLinkSink
ports:
- Toggle
- On
- Off
- type: entity
id: EnergyDomeWiredTest
name: Static Dome
description: Test energy barrier powered by station wiring. I don't know how the hell to balance it.....
parent: BaseMachine
suffix: DO NOT MERGE
placement:
mode: SnapgridCenter
components:
- type: Transform
anchored: true
- type: Physics
bodyType: Static
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- type: Sprite
sprite: Structures/Power/Generation/Tesla/coil.rsi
snapCardinals: true
noRot: true
layers:
- state: coil
- type: ExaminableBattery
- type: Battery
maxCharge: 30000 #<- max supply
startingCharge: 10000
- type: PowerNetworkBattery
maxSupply: 30000
maxChargeRate: 1000 #<- passive charging frow power net
supplyRampTolerance: 500
supplyRampRate: 50
- type: BatteryCharger
voltage: Medium
- type: NodeContainer
examinable: true
nodes:
input:
!type:CableDeviceNode
nodeGroupID: MVPower
- type: BatterySelfRecharger
autoRecharge: false # true only when active
autoRechargeRate: -800 #<- discharge per second while active
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 200
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: UseDelay
delay: 30.0
- type: DeviceNetwork
deviceNetId: Wireless
receiveFrequencyId: BasicDevice
- type: WirelessNetworkConnection
range: 200
- type: DeviceLinkSink
ports:
- Toggle
- On
- Off
- type: EnergyDomeGenerator
enabled: true
damageEnergyDraw: 100
domePrototype: EnergyDomeSlowing
canDeviceNetworkUse: true

View File

@@ -204,3 +204,34 @@
radius: 2.0
energy: 7.0
- type: BloodBoilProjectile
- type: entity
id: ProjectileTeslaBall
name: teslaball
description: You better GITTAH WEIGH.
parent: BulletRocket
noSpawn: true
components:
- type: PointLight
color: "#B3CEFF"
radius: 2.0
energy: 5.0
- type: Projectile
damage:
types:
Caustic: 10
- type: Sprite
sprite: Structures/Power/Generation/Tesla/energy_miniball.rsi
layers:
- state: tesla_projectile
shader: unshaded
- type: Explosive
explosionType: Default
maxIntensity: 100
intensitySlope: 0.1
totalIntensity: 0.3
maxTileBreak: 0
- type: StunOnCollide
stunAmount: 2
knockdownAmount: 2
- type: TeslaProjectile

View File

@@ -50,3 +50,37 @@
# also limits the crew's use
- type: TimedDespawn
lifetime: 30
- type: entity
parent: BaseItem
id: ThrowingCard
name: card
components:
- type: Sprite
sprite: Objects/Magic/card.rsi
layers:
- state: icon
- type: Appearance
- type: Fixtures
fixtures:
fix1:
shape: !type:PhysShapeCircle
radius: 0.2
density: 5
mask:
- ItemMask
restitution: 0.3
friction: 0.2
- type: EmbeddableProjectile
sound: /Audio/Weapons/star_hit.ogg
- type: DamageOtherOnHit
damage:
types:
Slash: 8
Piercing: 10
- type: StaminaDamageOnCollide
damage: 45
- type: StaminaDamageOnEmbed
damage: 10
- type: TimedDespawn
lifetime: 60

View File

@@ -0,0 +1,253 @@
- type: entity
id: ActionElectricArcSpell
name: Electric arc
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: WorldTargetAction
useDelay: 60
itemIconStyle: BigAction
checkCanAccess: false
range: 10
sound: !type:SoundPathSpecifier
path: /Audio/White/Magic/Arc/cast.ogg
icon:
sprite: Objects/Magic/magicactions.rsi
state: thunder
isChargeEnabled: true
chargingSound:
path: /Audio/White/Magic/Arc/charge.ogg
chargeProto: MagicFollowerArcEntity
maxChargedSound:
path: /Audio/White/Magic/Arc/max.ogg
isAltEnabled: true
event: !type:ArcSpellEvent
speech: "KUH, ABAH'RAH"
prototype: ProjectileTeslaBall
posData: !type:TargetCasterPos
- type: entity
id: ActionForceSpell
name: Force
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: WorldTargetAction
useDelay: 60
itemIconStyle: BigAction
checkCanAccess: false
range: 10
sound: !type:SoundPathSpecifier
path: /Audio/White/Magic/Force/cast.ogg
icon:
sprite: Objects/Magic/magicactions.rsi
state: push
isChargeEnabled: true
chargingSound:
path: /Audio/White/Magic/Force/charge.ogg
chargeProto: MagicFollowerForceEntity
maxChargedSound:
path: /Audio/White/Magic/Force/max.ogg
isAltEnabled: true
event: !type:ForceSpellEvent
speech: "EL DRITCH!"
- type: entity
id: ActionFireballSpell
name: Fireball
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: WorldTargetAction
useDelay: 60
itemIconStyle: BigAction
checkCanAccess: false
range: 45
isChargeEnabled: true
chargeProto: MagicFollowerFireEntity
isAltEnabled: true
sound: !type:SoundPathSpecifier
path: /Audio/Magic/fireball.ogg
icon:
sprite: Objects/Magic/magicactions.rsi
state: fireball
event: !type:FireballSpellEvent
prototype: ProjectileFireball
posData: !type:TargetCasterPos
speech: action-speech-spell-fireball
- type: entity
id: ActionCardSpell
name: Cards
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: WorldTargetAction
useDelay: 60
itemIconStyle: BigAction
checkCanAccess: false
range: 45
isChargeEnabled: true
chargingSound:
path: /Audio/White/Magic/Cards/charge.ogg
chargeProto: MagicFollowerCardEntity
maxChargedSound:
path: /Audio/White/Magic/Cards/max.ogg
isAltEnabled: true
sound: !type:SoundPathSpecifier
path: /Audio/White/Magic/Cards/cast.ogg
icon:
sprite: Objects/Magic/card.rsi
state: icon
event: !type:CardsSpellEvent
prototype: ThrowingCard
posData: !type:TargetCasterPos
speech: "SHIZO NERO!"
- type: entity
id: ActionForcewallSpell
name: Forcewall
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: WorldTargetAction
useDelay: 60
itemIconStyle: BigAction
checkCanAccess: false
range: 10
sound: !type:SoundPathSpecifier
path: /Audio/White/Magic/Force/cast.ogg
icon:
sprite: Objects/Magic/magicactions.rsi
state: shield
isChargeEnabled: true
chargingSound:
path: /Audio/White/Magic/Force/charge.ogg
chargeProto: MagicFollowerForceEntity
maxChargedSound:
path: /Audio/White/Magic/Force/max.ogg
isAltEnabled: true
event: !type:ForceWallSpellEvent
speech: "TARCOL MINTI ZHERI!"
prototype: WallForce
- type: entity
id: ActionBlinkSpell
name: Blink
noSpawn: true
components:
- type: InstantAction
useDelay: 60
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: blink
event: !type:BlinkSpellEvent
speech: "SYCAR TYN!"
- type: entity
id: ActionEtherealJauntSpell
name: Ethereal Jaunt
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: InstantAction
useDelay: 60
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: jaunt
event: !type:EtherealJauntSpellEvent
speech: "SCYAR NILA!"
- type: entity
id: ActionEmpSpell
name: Emp
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: InstantAction
useDelay: 60
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: emp_new
event: !type:EmpSpellEvent
speech: "OCYAR TRINA!"
- type: entity
id: ActionCluwneCurseSpell
name: Cluwne Curse
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: EntityTargetAction
canTargetSelf: false
range: 2
useDelay: 300
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: cluwne
event: !type:CluwneCurseSpellEvent
speech: "CLUWNE FOR ME!"
- type: entity
id: ActionBananaTouchSpell
name: Banana Touch
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: EntityTargetAction
canTargetSelf: false
range: 2
useDelay: 300
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: clown
event: !type:BananaTouchSpellEvent
speech: "HONK FOR ME!"
- type: entity
id: ActionMimeTouchSpell
name: Mime Touch
noSpawn: true
components:
- type: Magic
requiresClothes: true
- type: EntityTargetAction
canTargetSelf: false
range: 2
useDelay: 300
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: mime_curse
event: !type:MimeTouchSpellEvent
speech: "SILENCE!"
- type: entity
id: ActionInstantRecallSpell
name: Instant Recall
noSpawn: true
components:
- type: InstantRecall
- type: Magic
requiresClothes: true
- type: InstantAction
useDelay: 10
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: summons
event: !type:InstantRecallSpellEvent

View File

@@ -0,0 +1,35 @@
- type: entity
parent: ClothingHeadHatWizard
id: ClothingHeadHatRealWizardBlue
components:
- type: WizardClothes
- type: entity
parent: ClothingOuterWizard
id: ClothingOuterRealWizardBlue
components:
- type: WizardClothes
- type: entity
parent: ClothingHeadHatRedwizard
id: ClothingHeadHatRealWizardRed
components:
- type: WizardClothes
- type: entity
parent: ClothingOuterWizardRed
id: ClothingOuterRealWizardRed
components:
- type: WizardClothes
- type: entity
parent: ClothingHeadHatVioletwizard
id: ClothingHeadHatRealWizardViolet
components:
- type: WizardClothes
- type: entity
parent: ClothingOuterWizardViolet
id: ClothingOuterRealWizardViolet
components:
- type: WizardClothes

View File

@@ -0,0 +1,119 @@
- type: entity
id: MagicFollowerEntity
name: magic
components:
- type: Physics
bodyType: Dynamic
fixedRotation: false
- type: Sprite
sprite: Structures/Specific/Anomalies/Cores/bluespace_core.rsi
noRot: true
layers:
- state: core
- state: pulse
map: ["decay"]
- type: Appearance
- type: GenericVisualizer
visuals:
enum.AnomalyCoreVisuals.Decaying:
decay:
True: { visible: true }
False: { visible: false }
- type: PointLight
radius: 1.5
energy: 3.5
color: "#00ccff"
castShadows: false
- type: entity
id: MagicFollowerArcEntity
name: magic
components:
- type: Physics
bodyType: Dynamic
fixedRotation: false
- type: Sprite
sprite: Structures/Specific/Anomalies/Cores/electric_core.rsi
noRot: true
layers:
- state: core
- state: pulse
map: ["decay"]
- type: Appearance
- type: GenericVisualizer
visuals:
enum.AnomalyCoreVisuals.Decaying:
decay:
True: { visible: true }
False: { visible: false }
- type: PointLight
radius: 1.5
energy: 3.5
color: "#ccf404"
castShadows: false
- type: entity
id: MagicFollowerForceEntity
name: magic
components:
- type: Physics
bodyType: Dynamic
fixedRotation: false
- type: Sprite
sprite: Structures/Specific/Anomalies/Cores/bluespace_core.rsi
noRot: true
layers:
- state: core
- state: pulse
map: ["decay"]
- type: Appearance
- type: GenericVisualizer
visuals:
enum.AnomalyCoreVisuals.Decaying:
decay:
True: { visible: true }
False: { visible: false }
- type: PointLight
radius: 1.5
energy: 3.5
color: "#00ccff"
castShadows: false
- type: entity
id: MagicFollowerFireEntity
name: magic
components:
- type: Physics
bodyType: Dynamic
fixedRotation: false
- type: Sprite
sprite: Structures/Specific/Anomalies/Cores/pyro_core.rsi
noRot: true
layers:
- state: core
- state: pulse
map: ["decay"]
- type: Appearance
- type: GenericVisualizer
visuals:
enum.AnomalyCoreVisuals.Decaying:
decay:
True: { visible: true }
False: { visible: false }
- type: PointLight
radius: 1.5
energy: 3.5
color: "#ce5a25"
castShadows: false
- type: entity
id: MagicFollowerCardEntity
name: magic
components:
- type: Physics
bodyType: Dynamic
fixedRotation: false
- type: Sprite
sprite: Objects/Magic/card.rsi
layers:
- state: icon

View File

@@ -0,0 +1,133 @@
- type: entity
id: BaseScroll
parent: BaseItem
name: "Magic Scroll"
description: "A relic of arcane lore, this ancient parchment bears witness to countless mystical incantations and forgotten spells."
noSpawn: true
components:
- type: Sprite
sprite: White/Misc/scrolls.rsi
layers:
- state: scroll
- type: Scroll
useSound:
path: /Audio/White/Items/scroll/use.ogg
afterUseSound:
path: /Audio/White/Items/scroll/after_use.ogg
- type: entity
id: ScrollFireball
parent: BaseScroll
name: "Fireball scroll"
components:
- type: Scroll
actionId: ActionFireballSpell
learnPopup: fireball
- type: entity
id: ScrollForcewall
parent: BaseScroll
name: "Forcewall scroll"
components:
- type: Scroll
actionId: ActionForcewallSpell
learnPopup: forcewall
- type: entity
id: ScrollKnock
parent: BaseScroll
name: "Knock scroll"
components:
- type: Scroll
actionId: ActionKnock
learnPopup: knock-knock
- type: entity
id: ScrollArc
parent: BaseScroll
name: "Electric Arc scroll"
components:
- type: Scroll
actionId: ActionElectricArcSpell
learnPopup: lightning
- type: entity
id: ScrollForce
parent: BaseScroll
name: "Force scroll"
components:
- type: Scroll
actionId: ActionForceSpell
learnPopup: force
- type: entity
id: ScrollCards
parent: BaseScroll
name: "Cards scroll"
components:
- type: Scroll
actionId: ActionCardSpell
learnPopup: cards
- type: entity
id: ScrollBlink
parent: BaseScroll
name: "Blink scroll"
components:
- type: Scroll
actionId: ActionBlinkSpell
learnPopup: blink
- type: entity
id: ScrollEtherealJaunt
parent: BaseScroll
name: "Ethereal Jaunt scroll"
components:
- type: Scroll
actionId: ActionEtherealJauntSpell
learnPopup: jaunt
- type: entity
id: ScrollEmp
parent: BaseScroll
name: "Emp scroll"
components:
- type: Scroll
actionId: ActionEmpSpell
learnPopup: emp
- type: entity
id: ScrollCluwneCurse
parent: BaseScroll
name: "Cluwne curse scroll"
components:
- type: Scroll
actionId: ActionCluwneCurseSpell
learnPopup: curse
- type: entity
id: ScrollBananaTouch
parent: BaseScroll
name: "Banana touch scroll"
components:
- type: Scroll
actionId: ActionBananaTouchSpell
learnPopup: banana
- type: entity
id: ScrollMimeTouch
parent: BaseScroll
name: "Mime touch scroll"
components:
- type: Scroll
actionId: ActionMimeTouchSpell
learnPopup: silence
- type: entity
id: ScrollInstantRecall
parent: BaseScroll
name: "Instant recall scroll"
components:
- type: Scroll
actionId: ActionInstantRecallSpell
learnPopup: recall

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (github) for Space Station 14",
"size": {
"x": 192,
"y": 192
},
"states": [
{
"name": "big",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

View File

@@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (github) for Space Station 14",
"size": {
"x": 96,
"y": 64
},
"states": [
{
"name": "small",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (github) for Space Station 14",
"size": {
"x": 128,
"y": 128
},
"states": [
{
"name": "medium",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (github) for Space Station 14",
"size": {
"x": 192,
"y": 192
},
"states": [
{
"name": "big",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (github) for Space Station 14",
"size": {
"x": 64,
"y": 64
},
"states": [
{
"name": "small",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

View File

@@ -0,0 +1,14 @@
{
"version": 1,
"license": null,
"copyright": null,
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

View File

@@ -27,6 +27,30 @@
},
{
"name": "gib"
},
{
"name": "push"
},
{
"name": "thunder"
},
{
"name": "clown"
},
{
"name": "cluwne"
},
{
"name": "emp_new"
},
{
"name": "jaunt"
},
{
"name": "mime_curse"
},
{
"name": "summons"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,22 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (github) for Space Station 14",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

View File

@@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (github) for Space Station 14",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "equipped-BELT",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,30 @@
{
"version": 1,
"license": null,
"copyright": null,
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "flux",
"delays": [
[
0.1,
0.1,
1.5,
0.1,
0.1,
1.5,
0.1,
0.1,
1.5,
0.1,
0.1,
1.5
]
]
}
]
}

View File

@@ -0,0 +1,14 @@
{
"version": 1,
"license": null,
"copyright": null,
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "scroll"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B