Re-organize all projects (#4166)
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface gives components behavior on getting destroyed.
|
||||
/// </summary>
|
||||
public interface IDestroyAct
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when object is destroyed
|
||||
/// </summary>
|
||||
void OnDestroy(DestructionEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public class DestructionEventArgs : EventArgs
|
||||
{
|
||||
public IEntity Owner { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class BreakageEventArgs : EventArgs
|
||||
{
|
||||
public IEntity Owner { get; set; } = default!;
|
||||
}
|
||||
|
||||
public interface IBreakAct
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when object is broken
|
||||
/// </summary>
|
||||
void OnBreak(BreakageEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public interface IExAct
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when explosion reaches the entity
|
||||
/// </summary>
|
||||
void OnExplosion(ExplosionEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public class ExplosionEventArgs : EventArgs
|
||||
{
|
||||
public EntityCoordinates Source { get; set; }
|
||||
public IEntity Target { get; set; } = default!;
|
||||
public ExplosionSeverity Severity { get; set; }
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ActSystem : EntitySystem
|
||||
{
|
||||
public void HandleDestruction(IEntity owner)
|
||||
{
|
||||
var eventArgs = new DestructionEventArgs
|
||||
{
|
||||
Owner = owner
|
||||
};
|
||||
|
||||
var destroyActs = owner.GetAllComponents<IDestroyAct>().ToList();
|
||||
|
||||
foreach (var destroyAct in destroyActs)
|
||||
{
|
||||
destroyAct.OnDestroy(eventArgs);
|
||||
}
|
||||
|
||||
owner.QueueDelete();
|
||||
}
|
||||
|
||||
public void HandleExplosion(EntityCoordinates source, IEntity target, ExplosionSeverity severity)
|
||||
{
|
||||
var eventArgs = new ExplosionEventArgs
|
||||
{
|
||||
Source = source,
|
||||
Target = target,
|
||||
Severity = severity
|
||||
};
|
||||
var exActs = target.GetAllComponents<IExAct>().ToList();
|
||||
|
||||
foreach (var exAct in exActs)
|
||||
{
|
||||
exAct.OnExplosion(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleBreakage(IEntity owner)
|
||||
{
|
||||
var eventArgs = new BreakageEventArgs
|
||||
{
|
||||
Owner = owner,
|
||||
};
|
||||
var breakActs = owner.GetAllComponents<IBreakAct>().ToList();
|
||||
foreach (var breakAct in breakActs)
|
||||
{
|
||||
breakAct.OnBreak(eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ExplosionSeverity
|
||||
{
|
||||
Light,
|
||||
Heavy,
|
||||
Destruction,
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
#nullable enable
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems.ActionBlocker
|
||||
{
|
||||
public static class ActionBlockerExtensions
|
||||
{
|
||||
public static bool CanMove(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanMove(entity);
|
||||
}
|
||||
|
||||
public static bool CanInteract(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanInteract(entity);
|
||||
}
|
||||
|
||||
public static bool CanUse(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanUse(entity);
|
||||
}
|
||||
|
||||
public static bool CanThrow(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanThrow(entity);
|
||||
}
|
||||
|
||||
public static bool CanSpeak(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanSpeak(entity);
|
||||
}
|
||||
|
||||
public static bool CanDrop(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanDrop(entity);
|
||||
}
|
||||
|
||||
public static bool CanPickup(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanPickup(entity);
|
||||
}
|
||||
|
||||
public static bool CanEmote(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanEmote(entity);
|
||||
}
|
||||
|
||||
public static bool CanAttack(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanAttack(entity);
|
||||
}
|
||||
|
||||
public static bool CanEquip(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanEquip(entity);
|
||||
}
|
||||
|
||||
public static bool CanUnequip(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanUnequip(entity);
|
||||
}
|
||||
|
||||
public static bool CanChangeDirection(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanChangeDirection(entity);
|
||||
}
|
||||
|
||||
public static bool CanShiver(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanShiver(entity);
|
||||
}
|
||||
|
||||
public static bool CanSweat(this IEntity entity)
|
||||
{
|
||||
return ActionBlockerSystem.CanSweat(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.GameObjects.Components.Mobs.Speech;
|
||||
using Content.Shared.GameObjects.EntitySystems.EffectBlocker;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems.ActionBlocker
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods to check if a specific entity is allowed to perform an action.
|
||||
/// For effects see <see cref="EffectBlockerSystem"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ActionBlockerSystem : EntitySystem
|
||||
{
|
||||
public static bool CanMove(IEntity entity)
|
||||
{
|
||||
var canMove = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canMove &= blocker.CanMove(); // Sets var to false if false
|
||||
}
|
||||
|
||||
return canMove;
|
||||
}
|
||||
|
||||
public static bool CanInteract([NotNullWhen(true)] IEntity? entity)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var canInteract = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canInteract &= blocker.CanInteract();
|
||||
}
|
||||
|
||||
return canInteract;
|
||||
}
|
||||
|
||||
public static bool CanUse([NotNullWhen(true)] IEntity? entity)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var canUse = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canUse &= blocker.CanUse();
|
||||
}
|
||||
|
||||
return canUse;
|
||||
}
|
||||
|
||||
public static bool CanThrow(IEntity entity)
|
||||
{
|
||||
var canThrow = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canThrow &= blocker.CanThrow();
|
||||
}
|
||||
|
||||
return canThrow;
|
||||
}
|
||||
|
||||
public static bool CanSpeak(IEntity entity)
|
||||
{
|
||||
if (!entity.HasComponent<SharedSpeechComponent>())
|
||||
return false;
|
||||
|
||||
var canSpeak = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canSpeak &= blocker.CanSpeak();
|
||||
}
|
||||
|
||||
return canSpeak;
|
||||
}
|
||||
|
||||
public static bool CanDrop(IEntity entity)
|
||||
{
|
||||
var canDrop = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canDrop &= blocker.CanDrop();
|
||||
}
|
||||
|
||||
return canDrop;
|
||||
}
|
||||
|
||||
public static bool CanPickup(IEntity entity)
|
||||
{
|
||||
var canPickup = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canPickup &= blocker.CanPickup();
|
||||
}
|
||||
|
||||
return canPickup;
|
||||
}
|
||||
|
||||
public static bool CanEmote(IEntity entity)
|
||||
{
|
||||
if (!entity.HasComponent<SharedEmotingComponent>())
|
||||
return false;
|
||||
|
||||
var canEmote = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canEmote &= blocker.CanEmote();
|
||||
}
|
||||
|
||||
return canEmote;
|
||||
}
|
||||
|
||||
public static bool CanAttack(IEntity entity)
|
||||
{
|
||||
var canAttack = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canAttack &= blocker.CanAttack();
|
||||
}
|
||||
|
||||
return canAttack;
|
||||
}
|
||||
|
||||
public static bool CanEquip(IEntity entity)
|
||||
{
|
||||
var canEquip = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canEquip &= blocker.CanEquip();
|
||||
}
|
||||
|
||||
return canEquip;
|
||||
}
|
||||
|
||||
public static bool CanUnequip(IEntity entity)
|
||||
{
|
||||
var canUnequip = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canUnequip &= blocker.CanUnequip();
|
||||
}
|
||||
|
||||
return canUnequip;
|
||||
}
|
||||
|
||||
public static bool CanChangeDirection(IEntity entity)
|
||||
{
|
||||
var canChangeDirection = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canChangeDirection &= blocker.CanChangeDirection();
|
||||
}
|
||||
|
||||
return canChangeDirection;
|
||||
}
|
||||
|
||||
public static bool CanShiver(IEntity entity)
|
||||
{
|
||||
var canShiver = true;
|
||||
foreach (var component in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canShiver &= component.CanShiver();
|
||||
}
|
||||
return canShiver;
|
||||
}
|
||||
|
||||
public static bool CanSweat(IEntity entity)
|
||||
{
|
||||
var canSweat = true;
|
||||
foreach (var component in entity.GetAllComponents<IActionBlocker>())
|
||||
{
|
||||
canSweat &= component.CanSweat();
|
||||
}
|
||||
return canSweat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.EntitySystems.EffectBlocker;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems.ActionBlocker
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface gives components the ability to block certain actions from
|
||||
/// being done by the owning entity. For effects see <see cref="IEffectBlocker"/>
|
||||
/// </summary>
|
||||
public interface IActionBlocker
|
||||
{
|
||||
bool CanMove() => true;
|
||||
|
||||
bool CanInteract() => true;
|
||||
|
||||
bool CanUse() => true;
|
||||
|
||||
bool CanThrow() => true;
|
||||
|
||||
bool CanSpeak() => true;
|
||||
|
||||
bool CanDrop() => true;
|
||||
|
||||
bool CanPickup() => true;
|
||||
|
||||
bool CanEmote() => true;
|
||||
|
||||
bool CanAttack() => true;
|
||||
|
||||
bool CanEquip() => true;
|
||||
|
||||
bool CanUnequip() => true;
|
||||
|
||||
bool CanChangeDirection() => true;
|
||||
|
||||
bool CanShiver() => true;
|
||||
|
||||
bool CanSweat() => true;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface gives components behavior on whether entities solution (implying SolutionComponent is in place) is changed
|
||||
/// </summary>
|
||||
public interface ISolutionChange
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when solution is mixed with some other solution, or when some part of the solution is removed
|
||||
/// </summary>
|
||||
void SolutionChanged(SolutionChangeEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public class SolutionChangeEventArgs : EventArgs
|
||||
{
|
||||
public IEntity Owner { get; set; } = default!;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public class ChemistrySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public void HandleSolutionChange(IEntity owner)
|
||||
{
|
||||
var eventArgs = new SolutionChangeEventArgs
|
||||
{
|
||||
Owner = owner,
|
||||
};
|
||||
var solutionChangeArgs = owner.GetAllComponents<ISolutionChange>().ToList();
|
||||
|
||||
foreach (var solutionChangeArg in solutionChangeArgs)
|
||||
{
|
||||
solutionChangeArg.SolutionChanged(eventArgs);
|
||||
|
||||
if (owner.Deleted)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReactionEntity(IEntity? entity, ReactionMethod method, string reagentId, ReagentUnit reactVolume, Solution? source)
|
||||
{
|
||||
// We throw if the reagent specified doesn't exist.
|
||||
ReactionEntity(entity, method, _prototypeManager.Index<ReagentPrototype>(reagentId), reactVolume, source);
|
||||
}
|
||||
|
||||
public void ReactionEntity(IEntity? entity, ReactionMethod method, ReagentPrototype reagent, ReagentUnit reactVolume, Solution? source)
|
||||
{
|
||||
if (entity == null || entity.Deleted || !entity.TryGetComponent(out ReactiveComponent? reactive))
|
||||
return;
|
||||
|
||||
foreach (var reaction in reactive.Reactions)
|
||||
{
|
||||
// If we have a source solution, use the reagent quantity we have left. Otherwise, use the reaction volume specified.
|
||||
reaction.React(method, entity, reagent, source?.GetReagentQuantity(reagent.ID) ?? reactVolume, source);
|
||||
|
||||
// Make sure we still have enough reagent to go...
|
||||
if (source != null && !source.ContainsReagent(reagent.ID))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Content.Shared.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class DamageSystem : EntitySystem
|
||||
{
|
||||
public static ImmutableDictionary<DamageClass, ImmutableList<DamageType>> ClassToType { get; } = DefaultClassToType();
|
||||
|
||||
public static ImmutableDictionary<DamageType, DamageClass> TypeToClass { get; } = DefaultTypeToClass();
|
||||
|
||||
private static ImmutableDictionary<DamageClass, ImmutableList<DamageType>> DefaultClassToType()
|
||||
{
|
||||
return new Dictionary<DamageClass, ImmutableList<DamageType>>
|
||||
{
|
||||
[DamageClass.Brute] = new List<DamageType>
|
||||
{
|
||||
DamageType.Blunt,
|
||||
DamageType.Slash,
|
||||
DamageType.Piercing
|
||||
}.ToImmutableList(),
|
||||
[DamageClass.Burn] = new List<DamageType>
|
||||
{
|
||||
DamageType.Heat,
|
||||
DamageType.Shock,
|
||||
DamageType.Cold
|
||||
}.ToImmutableList(),
|
||||
[DamageClass.Toxin] = new List<DamageType>
|
||||
{
|
||||
DamageType.Poison,
|
||||
DamageType.Radiation
|
||||
}.ToImmutableList(),
|
||||
[DamageClass.Airloss] = new List<DamageType>
|
||||
{
|
||||
DamageType.Asphyxiation,
|
||||
DamageType.Bloodloss
|
||||
}.ToImmutableList(),
|
||||
[DamageClass.Genetic] = new List<DamageType>
|
||||
{
|
||||
DamageType.Cellular
|
||||
}.ToImmutableList()
|
||||
}.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
private static ImmutableDictionary<DamageType, DamageClass> DefaultTypeToClass()
|
||||
{
|
||||
return new Dictionary<DamageType, DamageClass>
|
||||
{
|
||||
{DamageType.Blunt, DamageClass.Brute},
|
||||
{DamageType.Slash, DamageClass.Brute},
|
||||
{DamageType.Piercing, DamageClass.Brute},
|
||||
{DamageType.Heat, DamageClass.Burn},
|
||||
{DamageType.Shock, DamageClass.Burn},
|
||||
{DamageType.Cold, DamageClass.Burn},
|
||||
{DamageType.Poison, DamageClass.Toxin},
|
||||
{DamageType.Radiation, DamageClass.Toxin},
|
||||
{DamageType.Asphyxiation, DamageClass.Airloss},
|
||||
{DamageType.Bloodloss, DamageClass.Airloss},
|
||||
{DamageType.Cellular, DamageClass.Genetic}
|
||||
}.ToImmutableDictionary();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#nullable enable
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems.EffectBlocker
|
||||
{
|
||||
public static class EffectBlockerExtensions
|
||||
{
|
||||
public static bool CanFall(this IEntity entity)
|
||||
{
|
||||
return EffectBlockerSystem.CanFall(entity);
|
||||
}
|
||||
|
||||
public static bool CanSlip(this IEntity entity)
|
||||
{
|
||||
return EffectBlockerSystem.CanSlip(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems.EffectBlocker
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods to check if an effect is allowed to affect a specific entity.
|
||||
/// For actions see <see cref="ActionBlockerSystem"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class EffectBlockerSystem : EntitySystem
|
||||
{
|
||||
public static bool CanFall(IEntity entity)
|
||||
{
|
||||
var canFall = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IEffectBlocker>())
|
||||
{
|
||||
canFall &= blocker.CanFall(); // Sets var to false if false
|
||||
}
|
||||
|
||||
return canFall;
|
||||
}
|
||||
|
||||
public static bool CanSlip(IEntity entity)
|
||||
{
|
||||
var canSlip = true;
|
||||
|
||||
foreach (var blocker in entity.GetAllComponents<IEffectBlocker>())
|
||||
{
|
||||
canSlip &= blocker.CanSlip(); // Sets var to false if false
|
||||
}
|
||||
|
||||
return canSlip;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems.EffectBlocker
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface gives components the ability to block certain effects
|
||||
/// from affecting the owning entity. For actions see <see cref="IActionBlocker"/>
|
||||
/// </summary>
|
||||
public interface IEffectBlocker
|
||||
{
|
||||
bool CanFall() => true;
|
||||
bool CanSlip() => true;
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Shared.GameObjects.EntitySystems.SharedInteractionSystem;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[Obsolete("Use ExaminedEvent instead.")]
|
||||
public interface IExamine
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a status examine value for components appended to the end of the description of the entity
|
||||
/// </summary>
|
||||
/// <param name="message">The message to append to which will be displayed.</param>
|
||||
/// <param name="inDetailsRange">Whether the examiner is within the 'Details' range, allowing you to show information logically only availabe when close to the examined entity.</param>
|
||||
void Examine(FormattedMessage message, bool inDetailsRange);
|
||||
}
|
||||
|
||||
public abstract class ExamineSystemShared : EntitySystem
|
||||
{
|
||||
public const float ExamineRange = 16f;
|
||||
public const float ExamineRangeSquared = ExamineRange * ExamineRange;
|
||||
protected const float ExamineDetailsRange = 3f;
|
||||
|
||||
private static bool IsInDetailsRange(IEntity examiner, IEntity entity)
|
||||
{
|
||||
return examiner.InRangeUnobstructed(entity, ExamineDetailsRange, ignoreInsideBlocker: true) &&
|
||||
examiner.IsInSameOrNoContainer(entity);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
protected static bool CanExamine(IEntity examiner, IEntity examined)
|
||||
{
|
||||
if (!examiner.TryGetComponent(out ExaminerComponent? examinerComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!examinerComponent.DoRangeCheck)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (examiner.Transform.MapID != examined.Transform.MapID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Ignored predicate = entity => entity == examiner || entity == examined;
|
||||
|
||||
if (examiner.TryGetContainer(out var container))
|
||||
{
|
||||
predicate += entity => entity == container.Owner;
|
||||
}
|
||||
|
||||
return InRangeUnOccluded(
|
||||
examiner.Transform.MapPosition,
|
||||
examined.Transform.MapPosition,
|
||||
ExamineRange,
|
||||
predicate: predicate,
|
||||
ignoreInsideBlocker: true);
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(MapCoordinates origin, MapCoordinates other, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var occluderSystem = Get<OccluderSystem>();
|
||||
if (!origin.InRange(other, range)) return false;
|
||||
|
||||
var dir = other.Position - origin.Position;
|
||||
|
||||
if (dir.LengthSquared.Equals(0f)) return true;
|
||||
if (range > 0f && !(dir.LengthSquared <= range * range)) return false;
|
||||
|
||||
predicate ??= _ => false;
|
||||
|
||||
var ray = new Ray(origin.Position, dir.Normalized);
|
||||
var rayResults = occluderSystem
|
||||
.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
|
||||
|
||||
if (rayResults.Count == 0) return true;
|
||||
|
||||
if (!ignoreInsideBlocker) return false;
|
||||
|
||||
foreach (var result in rayResults)
|
||||
{
|
||||
if (!result.HitEntity.TryGetComponent(out OccluderComponent? o))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var bBox = o.BoundingBox.Translated(o.Owner.Transform.WorldPosition);
|
||||
|
||||
if (bBox.Contains(origin.Position) || bBox.Contains(other.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(IEntity origin, IEntity other, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var originPos = origin.Transform.MapPosition;
|
||||
var otherPos = other.Transform.MapPosition;
|
||||
|
||||
return InRangeUnOccluded(originPos, otherPos, range, predicate, ignoreInsideBlocker);
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(IEntity origin, IComponent other, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var originPos = origin.Transform.MapPosition;
|
||||
var otherPos = other.Owner.Transform.MapPosition;
|
||||
|
||||
return InRangeUnOccluded(originPos, otherPos, range, predicate, ignoreInsideBlocker);
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(IEntity origin, EntityCoordinates other, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var originPos = origin.Transform.MapPosition;
|
||||
var otherPos = other.ToMap(origin.EntityManager);
|
||||
|
||||
return InRangeUnOccluded(originPos, otherPos, range, predicate, ignoreInsideBlocker);
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(IEntity origin, MapCoordinates other, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var originPos = origin.Transform.MapPosition;
|
||||
|
||||
return InRangeUnOccluded(originPos, other, range, predicate, ignoreInsideBlocker);
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(ITargetedInteractEventArgs args, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var originPos = args.User.Transform.MapPosition;
|
||||
var otherPos = args.Target.Transform.MapPosition;
|
||||
|
||||
return InRangeUnOccluded(originPos, otherPos, range, predicate, ignoreInsideBlocker);
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(DragDropEvent args, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var originPos = args.User.Transform.MapPosition;
|
||||
var otherPos = args.DropLocation.ToMap(args.User.EntityManager);
|
||||
|
||||
return InRangeUnOccluded(originPos, otherPos, range, predicate, ignoreInsideBlocker);
|
||||
}
|
||||
|
||||
public static bool InRangeUnOccluded(AfterInteractEventArgs args, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
|
||||
{
|
||||
var originPos = args.User.Transform.MapPosition;
|
||||
var otherPos = args.Target?.Transform.MapPosition ?? args.ClickLocation.ToMap(args.User.EntityManager);
|
||||
|
||||
return InRangeUnOccluded(originPos, otherPos, range, predicate, ignoreInsideBlocker);
|
||||
}
|
||||
|
||||
public FormattedMessage GetExamineText(IEntity entity, IEntity? examiner)
|
||||
{
|
||||
var message = new FormattedMessage();
|
||||
|
||||
if (examiner == null)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
var doNewline = false;
|
||||
|
||||
//Add an entity description if one is declared
|
||||
if (!string.IsNullOrEmpty(entity.Description))
|
||||
{
|
||||
message.AddText(entity.Description);
|
||||
doNewline = true;
|
||||
}
|
||||
|
||||
message.PushColor(Color.DarkGray);
|
||||
|
||||
// Raise the event and let things that subscribe to it change the message...
|
||||
RaiseLocalEvent(entity.Uid, new ExaminedEvent(message, entity, examiner, IsInDetailsRange(examiner, entity)));
|
||||
|
||||
//Add component statuses from components that report one
|
||||
foreach (var examineComponent in entity.GetAllComponents<IExamine>())
|
||||
{
|
||||
var subMessage = new FormattedMessage();
|
||||
examineComponent.Examine(subMessage, IsInDetailsRange(examiner, entity));
|
||||
if (subMessage.Tags.Count == 0)
|
||||
continue;
|
||||
|
||||
if (doNewline)
|
||||
message.AddText("\n");
|
||||
|
||||
message.AddMessage(subMessage);
|
||||
doNewline = true;
|
||||
}
|
||||
|
||||
message.Pop();
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an entity is examined.
|
||||
/// You have to manually add a newline at the start, and perform cleanup (popping state) at the end.
|
||||
/// </summary>
|
||||
public class ExaminedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The message that will be displayed as the examine text.
|
||||
/// Use the methods it exposes to change it, and don't forget to add a newline at the start!
|
||||
/// Input/Output parameter.
|
||||
/// </summary>
|
||||
public FormattedMessage Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The entity performing the examining.
|
||||
/// </summary>
|
||||
public IEntity Examiner { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Entity being examined, for broadcast event purposes.
|
||||
/// </summary>
|
||||
public IEntity Examined { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the examiner is in range of the entity to get some extra details.
|
||||
/// </summary>
|
||||
public bool IsInDetailsRange { get; }
|
||||
|
||||
public ExaminedEvent(FormattedMessage message, IEntity examined, IEntity examiner, bool isInDetailsRange)
|
||||
{
|
||||
Message = message;
|
||||
Examined = examined;
|
||||
Examiner = examiner;
|
||||
IsInDetailsRange = isInDetailsRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class MechanismSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var mechanism in ComponentManager.EntityQuery<IMechanism>(true))
|
||||
{
|
||||
mechanism.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using Content.Shared.GameObjects.Components.Projectiles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public sealed class ProjectileSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedProjectileComponent, PreventCollideEvent>(PreventCollision);
|
||||
}
|
||||
|
||||
private void PreventCollision(EntityUid uid, SharedProjectileComponent component, PreventCollideEvent args)
|
||||
{
|
||||
if (component.IgnoreShooter && args.BodyB.Owner.Uid == component.Shooter)
|
||||
{
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Evicts action states with expired cooldowns.
|
||||
/// </summary>
|
||||
public class SharedActionSystem : EntitySystem
|
||||
{
|
||||
private const float CooldownCheckIntervalSeconds = 10;
|
||||
private float _timeSinceCooldownCheck;
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_timeSinceCooldownCheck += frameTime;
|
||||
if (_timeSinceCooldownCheck < CooldownCheckIntervalSeconds) return;
|
||||
|
||||
foreach (var comp in ComponentManager.EntityQuery<SharedActionsComponent>(false))
|
||||
{
|
||||
comp.ExpireCooldowns();
|
||||
}
|
||||
_timeSinceCooldownCheck -= CooldownCheckIntervalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Content.Shared.GameObjects.Components.Buckle;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public abstract class SharedBuckleSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedBuckleComponent, PreventCollideEvent>(PreventCollision);
|
||||
}
|
||||
|
||||
private void PreventCollision(EntityUid uid, SharedBuckleComponent component, PreventCollideEvent args)
|
||||
{
|
||||
if (args.BodyB.Owner.Uid != component.LastEntityBuckledTo) return;
|
||||
|
||||
component.IsOnStrapEntityThisFrame = true;
|
||||
if (component.Buckled || component.DontCollide)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public abstract class SharedChemicalReactionSystem : EntitySystem
|
||||
{
|
||||
private IEnumerable<ReactionPrototype> _reactions = default!;
|
||||
|
||||
private const int MaxReactionIterations = 20;
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a solution can undergo a specified reaction.
|
||||
/// </summary>
|
||||
/// <param name="solution">The solution to check.</param>
|
||||
/// <param name="reaction">The reaction to check.</param>
|
||||
/// <param name="lowestUnitReactions">How many times this reaction can occur.</param>
|
||||
/// <returns></returns>
|
||||
private static bool CanReact(Solution solution, ReactionPrototype reaction, out ReagentUnit lowestUnitReactions)
|
||||
{
|
||||
lowestUnitReactions = ReagentUnit.MaxValue;
|
||||
|
||||
foreach (var reactantData in reaction.Reactants)
|
||||
{
|
||||
var reactantName = reactantData.Key;
|
||||
var reactantCoefficient = reactantData.Value.Amount;
|
||||
|
||||
if (!solution.ContainsReagent(reactantName, out var reactantQuantity))
|
||||
return false;
|
||||
|
||||
var unitReactions = reactantQuantity / reactantCoefficient;
|
||||
|
||||
if (unitReactions < lowestUnitReactions)
|
||||
{
|
||||
lowestUnitReactions = unitReactions;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a reaction on a solution. This assumes all reaction criteria are met.
|
||||
/// Removes the reactants from the solution, then returns a solution with all products.
|
||||
/// </summary>
|
||||
private Solution PerformReaction(Solution solution, IEntity owner, ReactionPrototype reaction, ReagentUnit unitReactions)
|
||||
{
|
||||
//Remove reactants
|
||||
foreach (var reactant in reaction.Reactants)
|
||||
{
|
||||
if (!reactant.Value.Catalyst)
|
||||
{
|
||||
var amountToRemove = unitReactions * reactant.Value.Amount;
|
||||
solution.RemoveReagent(reactant.Key, amountToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
//Create products
|
||||
var products = new Solution();
|
||||
foreach (var product in reaction.Products)
|
||||
{
|
||||
products.AddReagent(product.Key, product.Value * unitReactions);
|
||||
}
|
||||
|
||||
// Trigger reaction effects
|
||||
OnReaction(reaction, owner, unitReactions);
|
||||
|
||||
return products;
|
||||
}
|
||||
|
||||
protected virtual void OnReaction(ReactionPrototype reaction, IEntity owner, ReagentUnit unitReactions)
|
||||
{
|
||||
foreach (var effect in reaction.Effects)
|
||||
{
|
||||
effect.React(owner, unitReactions.Double());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs all chemical reactions that can be run on a solution.
|
||||
/// Removes the reactants from the solution, then returns a solution with all products.
|
||||
/// WARNING: Does not trigger reactions between solution and new products.
|
||||
/// </summary>
|
||||
private Solution ProcessReactions(Solution solution, IEntity owner)
|
||||
{
|
||||
//TODO: make a hashmap at startup and then look up reagents in the contents for a reaction
|
||||
var overallProducts = new Solution();
|
||||
foreach (var reaction in _reactions)
|
||||
{
|
||||
if (CanReact(solution, reaction, out var unitReactions))
|
||||
{
|
||||
var reactionProducts = PerformReaction(solution, owner, reaction, unitReactions);
|
||||
overallProducts.AddSolution(reactionProducts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return overallProducts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continually react a solution until no more reactions occur.
|
||||
/// </summary>
|
||||
public void FullyReactSolution(Solution solution, IEntity owner)
|
||||
{
|
||||
for (var i = 0; i < MaxReactionIterations; i++)
|
||||
{
|
||||
var products = ProcessReactions(solution, owner);
|
||||
|
||||
if (products.TotalVolume <= 0)
|
||||
return;
|
||||
|
||||
solution.AddSolution(products);
|
||||
}
|
||||
Logger.Error($"{nameof(Solution)} on {owner} (Uid: {owner.Uid}) could not finish reacting in under {MaxReactionIterations} loops.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continually react a solution until no more reactions occur, with a volume constraint.
|
||||
/// If a reaction's products would exceed the max volume, some product is deleted.
|
||||
/// </summary>
|
||||
public void FullyReactSolution(Solution solution, IEntity owner, ReagentUnit maxVolume)
|
||||
{
|
||||
for (var i = 0; i < MaxReactionIterations; i++)
|
||||
{
|
||||
var products = ProcessReactions(solution, owner);
|
||||
|
||||
if (products.TotalVolume <= 0)
|
||||
return;
|
||||
|
||||
var totalVolume = solution.TotalVolume + products.TotalVolume;
|
||||
var excessVolume = totalVolume - maxVolume;
|
||||
|
||||
if (excessVolume > 0)
|
||||
{
|
||||
products.RemoveSolution(excessVolume); //excess product is deleted to fit under volume limit
|
||||
}
|
||||
|
||||
solution.AddSolution(products);
|
||||
}
|
||||
Logger.Error($"{nameof(Solution)} on {owner} (Uid: {owner.Uid}) could not finish reacting in under {MaxReactionIterations} loops.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public abstract class SharedCombatModeSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<CombatModeSystemMessages.SetCombatModeActiveMessage>(CombatModeActiveHandler);
|
||||
SubscribeLocalEvent<CombatModeSystemMessages.SetCombatModeActiveMessage>(CombatModeActiveHandler);
|
||||
}
|
||||
|
||||
private void CombatModeActiveHandler(CombatModeSystemMessages.SetCombatModeActiveMessage ev, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
var entity = eventArgs.SenderSession?.AttachedEntity;
|
||||
|
||||
if (entity == null || !entity.TryGetComponent(out SharedCombatModeComponent? combatModeComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
combatModeComponent.IsInCombatMode = ev.Active;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public class SharedConstructionSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Sent client -> server to to tell the server that we started building
|
||||
/// a structure-construction.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class TryStartStructureConstructionMessage : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Position to start building.
|
||||
/// </summary>
|
||||
public readonly EntityCoordinates Location;
|
||||
|
||||
/// <summary>
|
||||
/// The construction prototype to start building.
|
||||
/// </summary>
|
||||
public readonly string PrototypeName;
|
||||
|
||||
public readonly Angle Angle;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier to be sent back in the acknowledgement so that the client can clean up its ghost.
|
||||
/// </summary>
|
||||
public readonly int Ack;
|
||||
|
||||
public TryStartStructureConstructionMessage(EntityCoordinates loc, string prototypeName, Angle angle, int ack)
|
||||
{
|
||||
Location = loc;
|
||||
PrototypeName = prototypeName;
|
||||
Angle = angle;
|
||||
Ack = ack;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent client -> server to to tell the server that we started building
|
||||
/// an item-construction.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class TryStartItemConstructionMessage : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The construction prototype to start building.
|
||||
/// </summary>
|
||||
public readonly string PrototypeName;
|
||||
|
||||
public TryStartItemConstructionMessage(string prototypeName)
|
||||
{
|
||||
PrototypeName = prototypeName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send server -> client to tell the client that a ghost has started to be constructed.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class AckStructureConstructionMessage : EntityEventArgs
|
||||
{
|
||||
public readonly int GhostId;
|
||||
|
||||
public AckStructureConstructionMessage(int ghostId)
|
||||
{
|
||||
GhostId = ghostId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class SharedDisposalUnitSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<SharedDisposalUnitComponent>(true))
|
||||
{
|
||||
comp.Update(frameTime);
|
||||
}
|
||||
|
||||
foreach (var comp in ComponentManager.EntityQuery<SharedDisposalMailingUnitComponent>(true))
|
||||
{
|
||||
comp.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.GameObjects.Components.Doors;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public abstract class SharedDoorSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedDoorComponent, PreventCollideEvent>(PreventCollision);
|
||||
}
|
||||
|
||||
private void PreventCollision(EntityUid uid, SharedDoorComponent component, PreventCollideEvent args)
|
||||
{
|
||||
if (component.IsCrushing(args.BodyB.Owner))
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public abstract class SharedGhostRoleSystem : EntitySystem
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class GhostRole
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public EntityUid Id;
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Physics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Governs interactions during clicking on entities
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class SharedInteractionSystem : EntitySystem
|
||||
{
|
||||
public const float InteractionRange = 2;
|
||||
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
|
||||
|
||||
public delegate bool Ignored(IEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Traces a ray from coords to otherCoords and returns the length
|
||||
/// of the vector between coords and the ray's first hit.
|
||||
/// </summary>
|
||||
/// <param name="origin">Set of coordinates to use.</param>
|
||||
/// <param name="other">Other set of coordinates to use.</param>
|
||||
/// <param name="collisionMask">the mask to check for collisions</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <returns>Length of resulting ray.</returns>
|
||||
public float UnobstructedDistance(
|
||||
MapCoordinates origin,
|
||||
MapCoordinates other,
|
||||
int collisionMask = (int) CollisionGroup.Impassable,
|
||||
Ignored? predicate = null)
|
||||
{
|
||||
var dir = other.Position - origin.Position;
|
||||
|
||||
if (dir.LengthSquared.Equals(0f)) return 0f;
|
||||
|
||||
predicate ??= _ => false;
|
||||
var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask);
|
||||
var rayResults = Get<SharedBroadPhaseSystem>().IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
|
||||
|
||||
if (rayResults.Count == 0) return dir.Length;
|
||||
return (rayResults[0].HitPos - origin.Position).Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traces a ray from coords to otherCoords and returns the length
|
||||
/// of the vector between coords and the ray's first hit.
|
||||
/// </summary>
|
||||
/// <param name="origin">Set of coordinates to use.</param>
|
||||
/// <param name="other">Other set of coordinates to use.</param>
|
||||
/// <param name="collisionMask">The mask to check for collisions</param>
|
||||
/// <param name="ignoredEnt">
|
||||
/// The entity to be ignored when checking for collisions.
|
||||
/// </param>
|
||||
/// <returns>Length of resulting ray.</returns>
|
||||
public float UnobstructedDistance(
|
||||
MapCoordinates origin,
|
||||
MapCoordinates other,
|
||||
int collisionMask = (int) CollisionGroup.Impassable,
|
||||
IEntity? ignoredEnt = null)
|
||||
{
|
||||
var predicate = ignoredEnt == null
|
||||
? null
|
||||
: (Ignored) (e => e == ignoredEnt);
|
||||
|
||||
return UnobstructedDistance(origin, other, collisionMask, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that these coordinates are within a certain distance without any
|
||||
/// entity that matches the collision mask obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the two sets
|
||||
/// of coordinates.
|
||||
/// </summary>
|
||||
/// <param name="origin">Set of coordinates to use.</param>
|
||||
/// <param name="other">Other set of coordinates to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two sets of coordinates.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
MapCoordinates origin,
|
||||
MapCoordinates other,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false)
|
||||
{
|
||||
if (!origin.InRange(other, range)) return false;
|
||||
|
||||
var dir = other.Position - origin.Position;
|
||||
|
||||
if (dir.LengthSquared.Equals(0f)) return true;
|
||||
if (range > 0f && !(dir.LengthSquared <= range * range)) return false;
|
||||
|
||||
predicate ??= _ => false;
|
||||
|
||||
var ray = new CollisionRay(origin.Position, dir.Normalized, (int) collisionMask);
|
||||
var rayResults = Get<SharedBroadPhaseSystem>().IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
|
||||
|
||||
if (rayResults.Count == 0) return true;
|
||||
|
||||
if (!ignoreInsideBlocker) return false;
|
||||
|
||||
foreach (var result in rayResults)
|
||||
{
|
||||
if (!result.HitEntity.TryGetComponent(out IPhysBody? p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var bBox = p.GetWorldAABB();
|
||||
|
||||
if (bBox.Contains(origin.Position) || bBox.Contains(other.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that two entities are within a certain distance without any
|
||||
/// entity that matches the collision mask obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the two entities.
|
||||
/// </summary>
|
||||
/// <param name="origin">The first entity to use.</param>
|
||||
/// <param name="other">Other entity to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two entities.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the origin entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
IEntity origin,
|
||||
IEntity other,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
predicate ??= e => e == origin || e == other;
|
||||
return InRangeUnobstructed(origin, other.Transform.MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that an entity and a component are within a certain
|
||||
/// distance without any entity that matches the collision mask
|
||||
/// obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the entity and component.
|
||||
/// </summary>
|
||||
/// <param name="origin">The entity to use.</param>
|
||||
/// <param name="other">The component to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the entity and component.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the origin entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
IEntity origin,
|
||||
IComponent other,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
return InRangeUnobstructed(origin, other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that an entity and a set of grid coordinates are within a certain
|
||||
/// distance without any entity that matches the collision mask
|
||||
/// obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the entity and component.
|
||||
/// </summary>
|
||||
/// <param name="origin">The entity to use.</param>
|
||||
/// <param name="other">The grid coordinates to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two entity and set of grid coordinates.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the origin entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
IEntity origin,
|
||||
EntityCoordinates other,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
return InRangeUnobstructed(origin, other.ToMap(EntityManager), range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that an entity and a set of map coordinates are within a certain
|
||||
/// distance without any entity that matches the collision mask
|
||||
/// obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the entity and component.
|
||||
/// </summary>
|
||||
/// <param name="origin">The entity to use.</param>
|
||||
/// <param name="other">The map coordinates to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two entity and set of map coordinates.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the origin entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
IEntity origin,
|
||||
MapCoordinates other,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var originPosition = origin.Transform.MapPosition;
|
||||
predicate ??= e => e == origin;
|
||||
|
||||
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker);
|
||||
|
||||
if (!inRange && popup)
|
||||
{
|
||||
var message = Loc.GetString("You can't reach there!");
|
||||
origin.PopupMessage(message);
|
||||
}
|
||||
|
||||
return inRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public sealed class SharedMobMoverSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Get<SharedPhysicsSystem>().KinematicControllerCollision += HandleCollisionMessage;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
Get<SharedPhysicsSystem>().KinematicControllerCollision -= HandleCollisionMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fake pushing for player collisions.
|
||||
/// </summary>
|
||||
private void HandleCollisionMessage(Fixture ourFixture, Fixture otherFixture, float frameTime, Manifold manifold)
|
||||
{
|
||||
var otherBody = otherFixture.Body;
|
||||
|
||||
if (otherBody.BodyType != BodyType.Dynamic || !otherFixture.Hard) return;
|
||||
|
||||
var normal = manifold.LocalNormal;
|
||||
|
||||
if (!ourFixture.Body.Owner.TryGetComponent(out IMobMoverComponent? mobMover) || normal == Vector2.Zero) return;
|
||||
|
||||
otherBody.ApplyLinearImpulse(-normal * mobMover.PushStrength * frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Storage;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles converting inputs into movement.
|
||||
/// </summary>
|
||||
public sealed class SharedMoverSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var moveUpCmdHandler = new MoverDirInputCmdHandler(Direction.North);
|
||||
var moveLeftCmdHandler = new MoverDirInputCmdHandler(Direction.West);
|
||||
var moveRightCmdHandler = new MoverDirInputCmdHandler(Direction.East);
|
||||
var moveDownCmdHandler = new MoverDirInputCmdHandler(Direction.South);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(EngineKeyFunctions.MoveUp, moveUpCmdHandler)
|
||||
.Bind(EngineKeyFunctions.MoveLeft, moveLeftCmdHandler)
|
||||
.Bind(EngineKeyFunctions.MoveRight, moveRightCmdHandler)
|
||||
.Bind(EngineKeyFunctions.MoveDown, moveDownCmdHandler)
|
||||
.Bind(EngineKeyFunctions.Walk, new WalkInputCmdHandler())
|
||||
.Register<SharedMoverSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<SharedMoverSystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private static void HandleDirChange(ICommonSession? session, Direction dir, ushort subTick, bool state)
|
||||
{
|
||||
if (!TryGetAttachedComponent<IMoverComponent>(session, out var moverComp))
|
||||
return;
|
||||
|
||||
var owner = session?.AttachedEntity;
|
||||
|
||||
if (owner != null && session != null)
|
||||
{
|
||||
foreach (var comp in owner.GetAllComponents<IRelayMoveInput>())
|
||||
{
|
||||
comp.MoveInputPressed(session);
|
||||
}
|
||||
|
||||
// For stuff like "Moving out of locker" or the likes
|
||||
if (owner.IsInContainer() &&
|
||||
(!owner.TryGetComponent(out IMobStateComponent? mobState) ||
|
||||
mobState.IsAlive()))
|
||||
{
|
||||
var relayEntityMoveMessage = new RelayMovementEntityMessage(owner);
|
||||
owner.Transform.Parent!.Owner.SendMessage(owner.Transform, relayEntityMoveMessage);
|
||||
}
|
||||
}
|
||||
|
||||
moverComp.SetVelocityDirection(dir, subTick, state);
|
||||
}
|
||||
|
||||
private static void HandleRunChange(ICommonSession? session, ushort subTick, bool walking)
|
||||
{
|
||||
if (!TryGetAttachedComponent<IMoverComponent>(session, out var moverComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
moverComp.SetSprinting(subTick, walking);
|
||||
}
|
||||
|
||||
private static bool TryGetAttachedComponent<T>(ICommonSession? session, [NotNullWhen(true)] out T? component)
|
||||
where T : class, IComponent
|
||||
{
|
||||
component = default;
|
||||
|
||||
var ent = session?.AttachedEntity;
|
||||
|
||||
if (ent == null || !ent.IsValid())
|
||||
return false;
|
||||
|
||||
if (!ent.TryGetComponent(out T? comp))
|
||||
return false;
|
||||
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
private sealed class MoverDirInputCmdHandler : InputCmdHandler
|
||||
{
|
||||
private readonly Direction _dir;
|
||||
|
||||
public MoverDirInputCmdHandler(Direction dir)
|
||||
{
|
||||
_dir = dir;
|
||||
}
|
||||
|
||||
public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message)
|
||||
{
|
||||
if (message is not FullInputCmdMessage full)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleDirChange(session, _dir, message.SubTick, full.State == BoundKeyState.Down);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WalkInputCmdHandler : InputCmdHandler
|
||||
{
|
||||
public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message)
|
||||
{
|
||||
if (message is not FullInputCmdMessage full)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleRunChange(session, full.SubTick, full.State == BoundKeyState.Down);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.GameObjects.Components.Pulling;
|
||||
using Content.Shared.GameObjects.Components.Rotatable;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages.Pulling;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public abstract class SharedPullingSystem : EntitySystem, IResettingEntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapping of pullers to the entity that they are pulling.
|
||||
/// </summary>
|
||||
private readonly Dictionary<IEntity, IEntity> _pullers =
|
||||
new();
|
||||
|
||||
private readonly HashSet<SharedPullableComponent> _moving = new();
|
||||
private readonly HashSet<SharedPullableComponent> _stoppedMoving = new();
|
||||
|
||||
/// <summary>
|
||||
/// If distance between puller and pulled entity lower that this threshold,
|
||||
/// pulled entity will not change its rotation.
|
||||
/// Helps with small distance jittering
|
||||
/// </summary>
|
||||
private const float ThresholdRotDistance = 1;
|
||||
|
||||
/// <summary>
|
||||
/// If difference between puller and pulled angle lower that this threshold,
|
||||
/// pulled entity will not change its rotation.
|
||||
/// Helps with diagonal movement jittering
|
||||
/// </summary>
|
||||
private const float ThresholdRotAngle = 30;
|
||||
|
||||
public IReadOnlySet<SharedPullableComponent> Moving => _moving;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PullStartedMessage>(OnPullStarted);
|
||||
SubscribeLocalEvent<PullStoppedMessage>(OnPullStopped);
|
||||
SubscribeLocalEvent<MoveEvent>(PullerMoved);
|
||||
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInsert);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject))
|
||||
.Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject))
|
||||
.Register<SharedPullingSystem>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_moving.ExceptWith(_stoppedMoving);
|
||||
_stoppedMoving.Clear();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_pullers.Clear();
|
||||
_moving.Clear();
|
||||
_stoppedMoving.Clear();
|
||||
}
|
||||
|
||||
private void OnPullStarted(PullStartedMessage message)
|
||||
{
|
||||
if (_pullers.TryGetValue(message.Puller.Owner, out var pulled) &&
|
||||
pulled.TryGetComponent(out SharedPullableComponent? pulledComponent))
|
||||
{
|
||||
pulledComponent.TryStopPull();
|
||||
}
|
||||
|
||||
SetPuller(message.Puller.Owner, message.Pulled.Owner);
|
||||
}
|
||||
|
||||
private void OnPullStopped(PullStoppedMessage message)
|
||||
{
|
||||
RemovePuller(message.Puller.Owner);
|
||||
}
|
||||
|
||||
protected void OnPullableMove(EntityUid uid, SharedPullableComponent component, PullableMoveMessage args)
|
||||
{
|
||||
_moving.Add(component);
|
||||
}
|
||||
|
||||
protected void OnPullableStopMove(EntityUid uid, SharedPullableComponent component, PullableStopMovingMessage args)
|
||||
{
|
||||
_stoppedMoving.Add(component);
|
||||
}
|
||||
|
||||
private void PullerMoved(MoveEvent ev)
|
||||
{
|
||||
var puller = ev.Sender;
|
||||
if (!TryGetPulled(ev.Sender, out var pulled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pulled.TryGetComponent(out IPhysBody? physics))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePulledRotation(puller, pulled);
|
||||
|
||||
physics.WakeBody();
|
||||
|
||||
if (pulled.TryGetComponent(out SharedPullableComponent? pullable))
|
||||
{
|
||||
pullable.MovingTo = null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: When Joint networking is less shitcodey fix this to use a dedicated joints message.
|
||||
private void HandleContainerInsert(EntInsertedIntoContainerMessage message)
|
||||
{
|
||||
if (message.Entity.TryGetComponent(out SharedPullableComponent? pullable))
|
||||
{
|
||||
pullable.TryStopPull();
|
||||
}
|
||||
|
||||
if (message.Entity.TryGetComponent(out SharedPullerComponent? puller))
|
||||
{
|
||||
if (puller.Pulling == null) return;
|
||||
|
||||
if (!puller.Pulling.TryGetComponent(out SharedPullableComponent? pulling))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pulling.TryStopPull();
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
var player = session?.AttachedEntity;
|
||||
|
||||
if (player == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetPulled(player, out var pulled))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pulled.TryGetComponent(out SharedPullableComponent? pullable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pullable.TryMoveTo(coords.ToMap(EntityManager));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void HandleReleasePulledObject(ICommonSession? session)
|
||||
{
|
||||
var player = session?.AttachedEntity;
|
||||
|
||||
if (player == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetPulled(player, out var pulled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pulled.TryGetComponent(out SharedPullableComponent? pullable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pullable.TryStopPull();
|
||||
}
|
||||
|
||||
private void SetPuller(IEntity puller, IEntity pulled)
|
||||
{
|
||||
_pullers[puller] = pulled;
|
||||
}
|
||||
|
||||
private bool RemovePuller(IEntity puller)
|
||||
{
|
||||
return _pullers.Remove(puller);
|
||||
}
|
||||
|
||||
public IEntity? GetPulled(IEntity by)
|
||||
{
|
||||
return _pullers.GetValueOrDefault(by);
|
||||
}
|
||||
|
||||
public bool TryGetPulled(IEntity by, [NotNullWhen(true)] out IEntity? pulled)
|
||||
{
|
||||
return (pulled = GetPulled(by)) != null;
|
||||
}
|
||||
|
||||
public bool IsPulling(IEntity puller)
|
||||
{
|
||||
return _pullers.ContainsKey(puller);
|
||||
}
|
||||
|
||||
private void UpdatePulledRotation(IEntity puller, IEntity pulled)
|
||||
{
|
||||
// TODO: update once ComponentReference works with directed event bus.
|
||||
if (!pulled.TryGetComponent(out SharedRotatableComponent? rotatable))
|
||||
return;
|
||||
|
||||
if (!rotatable.RotateWhilePulling)
|
||||
return;
|
||||
|
||||
var dir = puller.Transform.WorldPosition - pulled.Transform.WorldPosition;
|
||||
if (dir.LengthSquared > ThresholdRotDistance * ThresholdRotDistance)
|
||||
{
|
||||
var oldAngle = pulled.Transform.WorldRotation;
|
||||
var newAngle = Angle.FromWorldVec(dir);
|
||||
|
||||
var diff = newAngle - oldAngle;
|
||||
if (Math.Abs(diff.Degrees) > ThresholdRotAngle)
|
||||
pulled.Transform.WorldRotation = newAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public abstract class SharedStackSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedStackComponent, ComponentStartup>(OnStackStarted);
|
||||
SubscribeLocalEvent<SharedStackComponent, StackChangeCountEvent>(OnStackCountChange);
|
||||
SubscribeLocalEvent<SharedStackComponent, ExaminedEvent>(OnStackExamined);
|
||||
}
|
||||
|
||||
private void OnStackStarted(EntityUid uid, SharedStackComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!ComponentManager.TryGetComponent(uid, out SharedAppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
appearance.SetData(StackVisuals.Actual, component.Count);
|
||||
appearance.SetData(StackVisuals.MaxCount, component.MaxCount);
|
||||
appearance.SetData(StackVisuals.Hide, false);
|
||||
}
|
||||
|
||||
protected void OnStackCountChange(EntityUid uid, SharedStackComponent component, StackChangeCountEvent args)
|
||||
{
|
||||
if (args.Amount == component.Count)
|
||||
return;
|
||||
|
||||
var old = component.Count;
|
||||
|
||||
if (args.Amount > component.MaxCount)
|
||||
{
|
||||
args.Amount = component.MaxCount;
|
||||
args.Clamped = true;
|
||||
}
|
||||
|
||||
if (args.Amount < 0)
|
||||
{
|
||||
args.Amount = 0;
|
||||
args.Clamped = true;
|
||||
}
|
||||
|
||||
component.Count = args.Amount;
|
||||
component.Dirty();
|
||||
|
||||
// Queue delete stack if count reaches zero.
|
||||
if(component.Count <= 0)
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
|
||||
// Change appearance data.
|
||||
if (ComponentManager.TryGetComponent(uid, out SharedAppearanceComponent? appearance))
|
||||
appearance.SetData(StackVisuals.Actual, component.Count);
|
||||
|
||||
RaiseLocalEvent(uid, new StackCountChangedEvent(old, component.Count));
|
||||
}
|
||||
|
||||
private void OnStackExamined(EntityUid uid, SharedStackComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
args.Message.AddText("\n");
|
||||
args.Message.AddMarkup(
|
||||
Loc.GetString("comp-stack-examine-detail-count",
|
||||
("count", component.Count),
|
||||
("markupCountColor", "lightgray")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to change the amount of things in a stack to a specific number.
|
||||
/// If the amount had to be clamped to zero or the max amount, <see cref="Clamped"/> will be true
|
||||
/// and the amount will be changed to match the value set.
|
||||
/// Does nothing if the amount is the same as the stack count already.
|
||||
/// </summary>
|
||||
public class StackChangeCountEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount to set the stack to.
|
||||
/// Input/Output parameter.
|
||||
/// </summary>
|
||||
public int Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the <see cref="Amount"/> had to be clamped.
|
||||
/// Output parameter.
|
||||
/// </summary>
|
||||
public bool Clamped { get; set; }
|
||||
|
||||
public StackChangeCountEvent(int amount)
|
||||
{
|
||||
Amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a stack's count has changed.
|
||||
/// </summary>
|
||||
public class StackCountChangedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The old stack count.
|
||||
/// </summary>
|
||||
public int OldCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The new stack count.
|
||||
/// </summary>
|
||||
public int NewCount { get; }
|
||||
|
||||
public StackCountChangedEvent(int oldCount, int newCount)
|
||||
{
|
||||
OldCount = oldCount;
|
||||
NewCount = newCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.EntitySystems.EffectBlocker;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
public abstract class SharedStandingStateSystem : EntitySystem
|
||||
{
|
||||
protected abstract bool OnDown(IEntity entity, bool playSound = true, bool dropItems = true,
|
||||
bool force = false);
|
||||
|
||||
protected abstract bool OnStand(IEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Set's the mob standing state to down.
|
||||
/// </summary>
|
||||
/// <param name="entity">The mob in question</param>
|
||||
/// <param name="playSound">Whether to play a sound when falling down or not</param>
|
||||
/// <param name="dropItems">Whether to make the mob drop all the items on his hands</param>
|
||||
/// <param name="force">Whether or not to check if the entity can fall.</param>
|
||||
/// <returns>False if the mob was already downed or couldn't set the state</returns>
|
||||
public bool Down(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false)
|
||||
{
|
||||
if (dropItems)
|
||||
{
|
||||
DropAllItemsInHands(entity, false);
|
||||
}
|
||||
|
||||
if (!force && !EffectBlockerSystem.CanFall(entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return OnDown(entity, playSound, dropItems, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mob's standing state to standing.
|
||||
/// </summary>
|
||||
/// <param name="entity">The mob in question.</param>
|
||||
/// <returns>False if the mob was already standing or couldn't set the state</returns>
|
||||
public bool Standing(IEntity entity)
|
||||
{
|
||||
return OnStand(entity);
|
||||
}
|
||||
|
||||
public virtual void DropAllItemsInHands(IEntity entity, bool doMobChecks = true)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class SlipperySystem : EntitySystem
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var slipperyComp in ComponentManager.EntityQuery<SlipperyComponent>().ToArray())
|
||||
{
|
||||
slipperyComp.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class StunSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var component in ComponentManager.EntityQuery<SharedStunnableComponent>(true))
|
||||
{
|
||||
component.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity system backing <see cref="SubFloorHideComponent"/>.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class SubFloorHideSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
private bool _showAll;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ShowAll
|
||||
{
|
||||
get => _showAll;
|
||||
set
|
||||
{
|
||||
if (_showAll == value) return;
|
||||
_showAll = value;
|
||||
|
||||
UpdateAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAll()
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<SubFloorHideComponent>(true))
|
||||
{
|
||||
var transform = comp.Owner.Transform;
|
||||
if (!_mapManager.TryGetGrid(transform.GridID, out var grid)) return;
|
||||
UpdateTile(grid, grid.TileIndicesFor(transform.Coordinates));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_mapManager.GridChanged += MapManagerOnGridChanged;
|
||||
_mapManager.TileChanged += MapManagerOnTileChanged;
|
||||
|
||||
SubscribeLocalEvent<SubFloorHideComponent, ComponentStartup>(OnSubFloorStarted);
|
||||
SubscribeLocalEvent<SubFloorHideComponent, ComponentShutdown>(OnSubFloorTerminating);
|
||||
SubscribeLocalEvent<SubFloorHideComponent, SnapGridPositionChangedEvent>(OnSnapGridPositionChanged);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_mapManager.GridChanged -= MapManagerOnGridChanged;
|
||||
_mapManager.TileChanged -= MapManagerOnTileChanged;
|
||||
}
|
||||
|
||||
private void OnSubFloorStarted(EntityUid uid, SubFloorHideComponent component, ComponentStartup _)
|
||||
{
|
||||
UpdateEntity(uid);
|
||||
}
|
||||
|
||||
private void OnSubFloorTerminating(EntityUid uid, SubFloorHideComponent component, ComponentShutdown _)
|
||||
{
|
||||
UpdateEntity(uid);
|
||||
}
|
||||
|
||||
private void OnSnapGridPositionChanged(EntityUid uid, SubFloorHideComponent component, SnapGridPositionChangedEvent ev)
|
||||
{
|
||||
// We do this directly instead of calling UpdateEntity.
|
||||
if(_mapManager.TryGetGrid(ev.NewGrid, out var grid))
|
||||
UpdateTile(grid, ev.Position);
|
||||
}
|
||||
|
||||
private void MapManagerOnTileChanged(object? sender, TileChangedEventArgs e)
|
||||
{
|
||||
UpdateTile(_mapManager.GetGrid(e.NewTile.GridIndex), e.NewTile.GridIndices);
|
||||
}
|
||||
|
||||
private void MapManagerOnGridChanged(object? sender, GridChangedEventArgs e)
|
||||
{
|
||||
foreach (var modified in e.Modified)
|
||||
{
|
||||
UpdateTile(e.Grid, modified.position);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEntity(EntityUid uid)
|
||||
{
|
||||
if (!ComponentManager.TryGetComponent(uid, out ITransformComponent? transform) ||
|
||||
!_mapManager.TryGetGrid(transform.GridID, out var grid)) return;
|
||||
|
||||
UpdateTile(grid, grid.WorldToTile(transform.WorldPosition));
|
||||
}
|
||||
|
||||
private void UpdateTile(IMapGrid grid, Vector2i position)
|
||||
{
|
||||
var tile = grid.GetTileRef(position);
|
||||
var tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
|
||||
foreach (var anchored in grid.GetAnchoredEntities(position))
|
||||
{
|
||||
if (!ComponentManager.TryGetComponent(anchored, out SubFloorHideComponent? subFloorComponent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Show sprite
|
||||
if (ComponentManager.TryGetComponent(anchored, out SharedSpriteComponent ? spriteComponent))
|
||||
{
|
||||
spriteComponent.Visible = ShowAll || !subFloorComponent.Running || tileDef.IsSubFloor;
|
||||
}
|
||||
|
||||
// So for collision all we care about is that the component is running.
|
||||
if (ComponentManager.TryGetComponent(anchored, out PhysicsComponent ? physicsComponent))
|
||||
{
|
||||
physicsComponent.CanCollide = !subFloorComponent.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles throwing landing and collisions.
|
||||
/// </summary>
|
||||
public class ThrownItemSystem : EntitySystem
|
||||
{
|
||||
private List<IThrowCollide> _throwCollide = new();
|
||||
|
||||
private const string ThrowingFixture = "throw-fixture";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ThrownItemComponent, PhysicsSleepMessage>(HandleSleep);
|
||||
SubscribeLocalEvent<ThrownItemComponent, StartCollideEvent>(HandleCollision);
|
||||
SubscribeLocalEvent<ThrownItemComponent, PreventCollideEvent>(PreventCollision);
|
||||
SubscribeLocalEvent<ThrownItemComponent, ThrownEvent>(ThrowItem);
|
||||
SubscribeLocalEvent<ThrownItemComponent, LandEvent>(LandItem);
|
||||
SubscribeLocalEvent<PullStartedMessage>(HandlePullStarted);
|
||||
}
|
||||
|
||||
private void LandItem(EntityUid uid, ThrownItemComponent component, LandEvent args)
|
||||
{
|
||||
if (!component.Owner.TryGetComponent(out PhysicsComponent? physicsComponent)) return;
|
||||
|
||||
var fixture = physicsComponent.GetFixture(ThrowingFixture);
|
||||
if (fixture == null)
|
||||
{
|
||||
Logger.Error($"Tried to remove throwing fixture for {component.Owner} but none found?");
|
||||
return;
|
||||
}
|
||||
physicsComponent.RemoveFixture(fixture);
|
||||
}
|
||||
|
||||
private void ThrowItem(EntityUid uid, ThrownItemComponent component, ThrownEvent args)
|
||||
{
|
||||
if (!component.Owner.TryGetComponent(out PhysicsComponent? physicsComponent) ||
|
||||
physicsComponent.Fixtures.Count != 1) return;
|
||||
|
||||
if (physicsComponent.GetFixture(ThrowingFixture) != null)
|
||||
{
|
||||
Logger.Error($"Found existing throwing fixture on {component.Owner}");
|
||||
return;
|
||||
}
|
||||
|
||||
var shape = physicsComponent.Fixtures[0].Shape;
|
||||
var fixture = new Fixture(physicsComponent, shape) {CollisionLayer = (int) CollisionGroup.ThrownItem, Hard = false, ID = ThrowingFixture};
|
||||
physicsComponent.AddFixture(fixture);
|
||||
}
|
||||
|
||||
private void HandleCollision(EntityUid uid, ThrownItemComponent component, StartCollideEvent args)
|
||||
{
|
||||
var thrower = component.Thrower;
|
||||
var otherBody = args.OtherFixture.Body;
|
||||
|
||||
if (otherBody.Owner == thrower) return;
|
||||
ThrowCollideInteraction(thrower, args.OurFixture.Body, otherBody);
|
||||
}
|
||||
|
||||
private void PreventCollision(EntityUid uid, ThrownItemComponent component, PreventCollideEvent args)
|
||||
{
|
||||
if (args.BodyB.Owner == component.Thrower)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSleep(EntityUid uid, ThrownItemComponent thrownItem, PhysicsSleepMessage message)
|
||||
{
|
||||
LandComponent(thrownItem);
|
||||
}
|
||||
|
||||
private void HandlePullStarted(PullStartedMessage message)
|
||||
{
|
||||
// TODO: this isn't directed so things have to be done the bad way
|
||||
if (message.Pulled.Owner.TryGetComponent(out ThrownItemComponent? thrownItem))
|
||||
LandComponent(thrownItem);
|
||||
}
|
||||
|
||||
private void LandComponent(ThrownItemComponent thrownItem)
|
||||
{
|
||||
if (thrownItem.Owner.Deleted) return;
|
||||
|
||||
var user = thrownItem.Thrower;
|
||||
var landing = thrownItem.Owner;
|
||||
var coordinates = landing.Transform.Coordinates;
|
||||
|
||||
// LandInteraction
|
||||
// TODO: Refactor these to system messages
|
||||
var landMsg = new LandEvent(user, landing, coordinates);
|
||||
RaiseLocalEvent(landMsg);
|
||||
if (landMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = landing.GetAllComponents<ILand>().ToArray();
|
||||
var landArgs = new LandEventArgs(user, coordinates);
|
||||
|
||||
// Call Land on all components that implement the interface
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
if (landing.Deleted) break;
|
||||
comp.Land(landArgs);
|
||||
}
|
||||
|
||||
ComponentManager.RemoveComponent(landing.Uid, thrownItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls ThrowCollide on all components that implement the IThrowCollide interface
|
||||
/// on a thrown entity and the target entity it hit.
|
||||
/// </summary>
|
||||
public void ThrowCollideInteraction(IEntity? user, IPhysBody thrown, IPhysBody target)
|
||||
{
|
||||
// TODO: Just pass in the bodies directly
|
||||
var collideMsg = new ThrowCollideEvent(user, thrown.Owner, target.Owner);
|
||||
RaiseLocalEvent(collideMsg);
|
||||
if (collideMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var eventArgs = new ThrowCollideEventArgs(user, thrown.Owner, target.Owner);
|
||||
|
||||
foreach (var comp in target.Owner.GetAllComponents<IThrowCollide>())
|
||||
{
|
||||
_throwCollide.Add(comp);
|
||||
}
|
||||
|
||||
foreach (var collide in _throwCollide)
|
||||
{
|
||||
if (target.Owner.Deleted) break;
|
||||
collide.HitBy(eventArgs);
|
||||
}
|
||||
|
||||
_throwCollide.Clear();
|
||||
|
||||
foreach (var comp in thrown.Owner.GetAllComponents<IThrowCollide>())
|
||||
{
|
||||
_throwCollide.Add(comp);
|
||||
}
|
||||
|
||||
foreach (var collide in _throwCollide)
|
||||
{
|
||||
if (thrown.Owner.Deleted) break;
|
||||
collide.DoHit(eventArgs);
|
||||
}
|
||||
|
||||
_throwCollide.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user