Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abilities & MassSpawn #56

Merged
merged 3 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Content.Client/Entry/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public override void Init()
_prototypeManager.RegisterIgnore("nukeopsRole");
_prototypeManager.RegisterIgnore("stationGoal"); // Corvax-StationGoal
_prototypeManager.RegisterIgnore("ghostRoleRaffleDecider");
_prototypeManager.RegisterIgnore("spawnGroupProto"); // Exodus-Lavaland

_componentFactory.GenerateNetIds();
_adminManager.Initialize();
Expand Down
4 changes: 4 additions & 0 deletions Content.Server/Explosion/EntitySystems/TriggerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, Tr
{
var xform = Transform(uid);
_audio.PlayPvs(component.Sound, xform.Coordinates); // play the sound at its last known coordinates

// Exodus-Lavaland-start
RemCompDeferred<SoundOnTriggerComponent>(uid);
Lokilife marked this conversation as resolved.
Show resolved Hide resolved
// Exodus-Lavaland-end
}
else // if the component doesn't get removed when triggered
{
Expand Down
56 changes: 56 additions & 0 deletions Content.Server/NPC/Components/NPCAbilityCombatComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Exodus-Lavaland
namespace Content.Server.NPC.Components;

/// <summary>
/// Added to NPCs whenever they're in ability combat so they can be handled by the dedicated system.
/// </summary>
[RegisterComponent]
public sealed partial class NPCAbilityCombatComponent : Component
{
[ViewVariables]
public EntityUid Target;

[ViewVariables]
public AbilityCombatStatus Status = AbilityCombatStatus.Normal;

[ViewVariables]
public TimeSpan NextAction = new();

[ViewVariables]
public int ActionsPerUpd = 1;

[ViewVariables]
public int UsedActionsLastUpd = 0;

[ViewVariables]
public float ActionsTimeReload = 1.0f;

}

public enum AbilityCombatStatus : byte
{
/// <summary>
/// The target isn't in LOS anymore.
/// </summary>
NotInSight,

/// <summary>
/// Due to some generic reason we are unable to attack the target.
/// </summary>
Unspecified,

/// <summary>
/// Set if we can't reach the target for whatever reason.
/// </summary>
TargetUnreachable,

/// <summary>
/// If the target is outside of our melee range.
/// </summary>
TargetOutOfRange,

/// <summary>
/// No dramas.
/// </summary>
Normal,
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public sealed partial class JukeOperator : HTNOperator, IHtnConditionalShutdown
[DataField("jukeType")]
public JukeType JukeType = JukeType.AdjacentTile;

// Exodus-Lavaland-AdvancedAI-Start
[DataField("jukeDuration")]
public float JukeDuration = 0.5f;
Lokilife marked this conversation as resolved.
Show resolved Hide resolved
// Exodus-Lavaland-AdvancedAI-End

[DataField("shutdownState")]
public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.PlanFinished;

Expand All @@ -17,6 +22,7 @@ public override void Startup(NPCBlackboard blackboard)
base.Startup(blackboard);
var juke = _entManager.EnsureComponent<NPCJukeComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
juke.JukeType = JukeType;
juke.JukeDuration = JukeDuration; // Exodus-Lavaland-AdvancedAI
}

public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
Expand Down
1 change: 1 addition & 0 deletions Content.Server/NPC/Systems/NPCCombatSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ public override void Update(float frameTime)
base.Update(frameTime);
UpdateMelee(frameTime);
UpdateRanged(frameTime);
UpdateAbility(frameTime); // Exodus-Lavaland
}
}
239 changes: 239 additions & 0 deletions Content.Server/NPC/Systems/NPCCombatSystrem.Ability.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Exodus-AdvancedAI
using System.Numerics;
using Content.Server.NPC.Components;
using Content.Shared.NPC;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions.Events;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;

using Content.Shared.Interaction;
using Content.Shared.Actions;
using Content.Server.Actions;
using Content.Shared.Directions;

namespace Content.Server.NPC.Systems;

public sealed partial class NPCCombatSystem
{
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;

private const float TargetAbilityLostRange = 28f;

private void InitializeAbility()
{
SubscribeLocalEvent<NPCAbilityCombatComponent, ComponentShutdown>(OnAbilityShutdown);
}

private void OnAbilityShutdown(EntityUid uid, NPCAbilityCombatComponent component, ComponentShutdown args)
{
_steering.Unregister(uid);
}

private void UpdateAbility(float frameTime)
{
var xformQuery = GetEntityQuery<TransformComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var curTime = _timing.CurTime;
var query = EntityQueryEnumerator<NPCAbilityCombatComponent, ActiveNPCComponent>();

while (query.MoveNext(out var uid, out var comp, out _))
{
CastAction(uid, comp, curTime, physicsQuery, xformQuery);
}
}

private void CastAction(EntityUid uid, NPCAbilityCombatComponent combatComp, TimeSpan curTime, EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery)
{
combatComp.Status = AbilityCombatStatus.Normal;

if (!xformQuery.TryGetComponent(uid, out var xform) ||
!xformQuery.TryGetComponent(combatComp.Target, out var targetXform))
{
combatComp.Status = AbilityCombatStatus.TargetUnreachable;
return;
}

if (!xform.Coordinates.TryDistance(EntityManager, targetXform.Coordinates, out var distance))
{
combatComp.Status = AbilityCombatStatus.TargetUnreachable;
return;
}

if (distance > TargetMeleeLostRange)
{
combatComp.Status = AbilityCombatStatus.TargetUnreachable;
return;
}

if (TryComp<NPCSteeringComponent>(uid, out var steering) &&
steering.Status == SteeringStatus.NoPath)
{
combatComp.Status = AbilityCombatStatus.TargetUnreachable;
return;
}

_steering.Register(uid, new EntityCoordinates(combatComp.Target, Vector2.Zero), steering);

if (combatComp.NextAction > curTime)
return;

// Get Actions
if (!TryComp(uid, out ActionsComponent? actionComp))
return;

List<EntityUid> actions = [];
actions.AddRange(actionComp.Actions);

while (actions.Count > 0)
{
if (combatComp.UsedActionsLastUpd >= combatComp.ActionsPerUpd)
break;

var act = _random.PickAndTake(actions);

var attemptEv = new ActionAttemptEvent(uid);
RaiseLocalEvent(act, ref attemptEv);
if (attemptEv.Cancelled)
return;

if (TryUseAction(uid, act, distance, combatComp, curTime))
combatComp.UsedActionsLastUpd++;
}

if (combatComp.UsedActionsLastUpd >= combatComp.ActionsPerUpd)
{
combatComp.UsedActionsLastUpd = 0;
combatComp.NextAction = curTime + TimeSpan.FromSeconds(combatComp.ActionsTimeReload);
}
}

private bool TryUseAction(EntityUid uid,
EntityUid actionUid,
float distance,
NPCAbilityCombatComponent combatComp,
TimeSpan curTime)
{
if (!TryComp(uid, out ActionsComponent? actionComp))
return false;

if (!TryComp(actionUid, out MetaDataComponent? actionMeta))
return false;

if (!_actions.TryGetActionData(actionUid, out var action))
return false;

if (!action.Enabled)
return false;

// check for action use prevention
var attemptEv = new ActionAttemptEvent(uid);
RaiseLocalEvent(actionUid, ref attemptEv);
if (attemptEv.Cancelled)
return false;

if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
return false;

if (action is { Charges: < 1, RenewCharges: true })
_actions.ResetCharges(actionUid);

BaseActionEvent? performEvent = null;

if (action.CheckConsciousness && !_actionBlockerSystem.CanConsciouslyPerformAction(uid))
return false;

if (!combatComp.Target.IsValid())
{
Log.Error($"Attempted to perform an entity-targeted action without a target! Action: {actionMeta.EntityName}");
return false;
}

if (action.MinAIUseRange >= distance ||
action.MaxAIUseRange <= distance)
{
Log.Error("Out");
return false;
}

// Validate request by checking action blockers and the like:
switch (action)
{
case EntityTargetActionComponent entityAction:
var targetWorldPos = _transform.GetWorldPosition(combatComp.Target);

if (entityAction.Range <= distance)
return false;

_rotateToFaceSystem.TryFaceCoordinates(uid, targetWorldPos);

if (!_actions.ValidateEntityTarget(uid, combatComp.Target, (actionUid, entityAction)))
return false;

_adminLogger.Add(LogType.Action,
$"{ToPrettyString(uid):user} is performing the {actionMeta.EntityName:action} action (provided by {ToPrettyString(action.Container ?? uid):provider}) targeted at {ToPrettyString(combatComp.Target):target}.");

if (entityAction.Event != null)
{
entityAction.Event.Target = combatComp.Target;
Dirty(actionUid, entityAction);
performEvent = entityAction.Event;
}
break;
case WorldTargetActionComponent worldAction:
var entityCoordinatesTarget = Transform(combatComp.Target).Coordinates;

if (worldAction.Range <= distance)
{
var mapTargetPos = entityCoordinatesTarget.ToMapPos(EntityManager, _transform);
var mapUserPos = Transform(uid).Coordinates.ToMapPos(EntityManager, _transform);

var direction = mapTargetPos - mapUserPos;
var coefficient = worldAction.Range / distance;
var delta = new Vector2(direction.X * coefficient, direction.Y * coefficient);
entityCoordinatesTarget = new EntityCoordinates(entityCoordinatesTarget.EntityId, mapUserPos + delta);
}

_rotateToFaceSystem.TryFaceCoordinates(uid, entityCoordinatesTarget.ToMapPos(EntityManager, _transform));

if (!_actions.ValidateWorldTarget(uid, entityCoordinatesTarget, (actionUid, worldAction)))
return false;

_adminLogger.Add(LogType.Action,
$"{ToPrettyString(uid):user} is performing the {actionMeta.EntityName:action} action (provided by {ToPrettyString(action.Container ?? uid):provider}) targeted at {entityCoordinatesTarget:target}.");

if (worldAction.Event != null)
{
worldAction.Event.Target = entityCoordinatesTarget;
Dirty(actionUid, worldAction);
performEvent = worldAction.Event;
}

break;
case InstantActionComponent instantAction:
if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(uid, null))
return false;

_adminLogger.Add(LogType.Action,
$"{ToPrettyString(uid):user} is performing the {actionMeta.EntityName:action} action provided by {ToPrettyString(action.Container ?? uid):provider}.");

performEvent = instantAction.Event;
break;
}

if (performEvent != null)
performEvent.Performer = uid;

// All checks passed. Perform the action!
_actions.PerformAction(uid, actionComp, actionUid, action, performEvent, curTime);

return true;
}

}
5 changes: 5 additions & 0 deletions Content.Shared/Actions/BaseActionComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ public EntityUid? EntityIcon
/// If not null, this sound will be played when performing this action.
/// </summary>
[DataField("sound")] public SoundSpecifier? Sound;

// Exodus-Lavaland-Start
[DataField("maxUseRange")] public float MaxAIUseRange = float.PositiveInfinity;
[DataField("minUseRange")] public float MinAIUseRange = 0;
// Exodus-Lavaland-End
}

[Serializable, NetSerializable]
Expand Down
2 changes: 1 addition & 1 deletion Content.Shared/Magic/Events/ProjectileSpellEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ public sealed partial class ProjectileSpellEvent : WorldTargetActionEvent, ISpea
public EntProtoId Prototype;

[DataField]
public string? Speech { get; private set; }
public string? Speech { get; set; } // Exodus-Lavaland
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- files: ["spawn.ogg", "bum.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Taken from Zvukipro.com"
source: "https://zvukipro.com/electronic/368-zvuki-lazera.html"
Binary file not shown.
Binary file not shown.
Loading