Files
tbd-station-14/Content.Server/AI/Utility/AiLogic/UtilityAI.cs
metalgearsloth 4437fc7a1b Data-driven NPC behaviors (#3271)
* Data-driven NPC behaviors

* Nuked AiLogicProcessor
* BehaviorSets are now all stored in yaml (might try making actions also yaml someday)
* Added a test to validate all BehaviorSets
* Might also try pooling actions in the future to reduce allocs but that'll be way down the line (cough physics).

* Forgot to re-add sorting nothing suss

* Remove last references

* Proper vector2i serialization for tile atmos (#3266)

* update map files

* update submodule

Co-authored-by: cyclowns <cyclowns@protonmail.ch>

* Remove weird "S" jumpsuit from existence (#3267)

* Change character names to use datasets prototypes (#3259)

* Remove old name lists in .txts

* Fix tests

* LATEST MASTER TECHNOLOGY

* Converts AdminMenu to partially use XAML (#3231)

* Cleans up Hydroponics content. (#3025)

* Adds to IgnoredComponents.cs

* Jackboots

* Half Done

* Moved to diff PR

* Everything functional

* Fixed Sprays

* Nice

* Fixed

* Update submodule

* Fix tests

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>

* Stacked sprite visualizer (#3096)

* Add Stack Visualizer

* Add cigarette pack resources

Adds transparent layers for visualizing cigarettes

* Add Bag Open/Close Visualizer

So storage opened in inventory can have different icons when opened
or closed.

* Create a component that only enumerates single item

Used for creating stuff like matchbox, or cigarettes. As a bonus.
It will only update stack visualizer for that particullar item.

* Refactoring stuff

* Fix other usage of stack in Resources

* Add docs

* Apply suggestions from code review

Apply metalgearsloth suggestions

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Applied suggestions from metalgearsloth

* Changed SingleItemStorageComponent to StorageCounterComponent

Difference. New component doesn't spawn items, merely counts them.

* Refactored StackVisualizer

* Fix breakage with master

* Update Resources/Prototypes/Entities/Objects/Consumable/fancy.yml

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Update with MGS suggestions

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* ApcNet updating fix (#3078)

* GridPowerComponent

* ApcNet Powered update bugfix

* PowerTest fix

* Add GridPower to Saltern

* test fix

* Update canceling cleanup

* code cleanup

* nullable & code cleanup for test

* undo power test nullable

* Replaces GridPowerSystem with ApcNetSystem

* build fix

* Update Content.Server/GameObjects/EntitySystems/ApcNetSystem.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Change all XAML to use spacestation14.io namespace (#3277)

* fix pizzaboxes (#3291)

Co-authored-by: cyclowns <cyclowns@protonmail.ch>

* Spikes fix reopened (#3203)

* DoAfter, dead and stun check, DragDropOn

* Not ignored anymore

* Copied comment deleted

* Herbert's an ass

* Woops

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>

* Make component states dependant on the player getting them (#3280)

* Make component states dependant on the player getting them

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>

* Updated submodule to v0.3.7.

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
Co-authored-by: Acruid <shatter66@gmail.com>

* Hoe fix (#3296)

* Initial (#3297)

* Sort reagent dispenser entries (#3272)

* Sort reagent dispenser entries

Saves manually doing it.

* zumzum's suggestion

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>

* Made firelocks damageable & destructible (#3303)

* Move job priority enum parity test ot unit tests (#3300)

* Spill hand contents when dropping them in a fall (#3304)

* Spill hand contents when dropping them due to falling down

* Better approach

* cleanup

* grammar

* stupid

* PauseManager moved to Shared (#3288)

* Namespace changes for moving IPauseManager to shared.

* Namespace changes for moving ITimerManager from Timers to Timing.

* Rebase Fixes.

* Update engine submodule to v0.3.8

* Improves kick, teleport and ban menus (#3312)

* Fix the admin panel not showing the account name (#3322)

* Fix name serialization for secret stashes (#3301)

* Fix name serialization for secret stashes

* Fix old usages of secret part name

* Separate ghost warp message into two (#3310)

* Separate ghost warp message into two

* Remove redundant arguments

* Address reviews

* Move properties up

* Add health overlay and a command to toggle it (#3278)

* Add health overlay bar and a command to toggle it

* Remove empty line

* Content PR for YAML hot reloading (#3319)

* Content PR for YAML hot reloading

* Add CanAdminReloadPrototypes (host permission)

* IndexedPrototype fixes

* Update RobustToolbox

* Update RobustToolbox

* Add an unconspicuous, meaningless and in no way motivated by any external force XML doc to buckle component

* Update RobustToolbox

* Update submodule to v0.3.12.

* Removed unused using statements that prevented compiling.
Removed references to IIndexedPrototype that does not exist anymore in the engine.

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
Co-authored-by: mirrorcult <notzombiedude@gmail.com>
Co-authored-by: cyclowns <cyclowns@protonmail.ch>
Co-authored-by: Visne <39844191+Visne@users.noreply.github.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Leo <lzimann@users.noreply.github.com>
Co-authored-by: Swept <sweptwastaken@protonmail.com>
Co-authored-by: Ygg01 <y.laughing.man.y@gmail.com>
Co-authored-by: collinlunn <60152240+collinlunn@users.noreply.github.com>
Co-authored-by: komunre <49118681+komunre@users.noreply.github.com>
Co-authored-by: Acruid <shatter66@gmail.com>
Co-authored-by: Peptide90 <78795277+Peptide90@users.noreply.github.com>
Co-authored-by: Clyybber <darkmine956@gmail.com>
2021-02-19 22:37:17 -08:00

211 lines
7.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using Content.Server.AI.Operators;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Utility;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems.AI;
using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer;
using Content.Server.GameObjects.EntitySystems.JobQueues;
using Content.Shared.GameObjects.Components.Mobs.State;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
namespace Content.Server.AI.Utility.AiLogic
{
// TODO: Need to split out the IMover stuff for NPC to a generic one that can be used for hoomans as well.
[RegisterComponent]
[ComponentReference(typeof(AiControllerComponent)), ComponentReference(typeof(IMoverComponent))]
internal sealed class UtilityAi : AiControllerComponent
{
public override string Name => "UtilityAI";
// TODO: Look at having ParallelOperators (probably no more than that as then you'd have a full-blown BT)
// Also RepeatOperators (e.g. if we're following an entity keep repeating MoveToEntity)
private AiActionSystem _planner;
public Blackboard Blackboard => _blackboard;
private Blackboard _blackboard;
/// <summary>
/// The sum of all BehaviorSets gives us what actions the AI can take
/// </summary>
public HashSet<string> BehaviorSets { get; } = new();
public List<IAiUtility> AvailableActions { get; set; } = new();
/// <summary>
/// The currently running action; most importantly are the operators.
/// </summary>
public UtilityAction CurrentAction { get; private set; }
/// <summary>
/// How frequently we can re-plan. If an AI's in combat you could decrease the cooldown,
/// or if there's no players nearby increase it.
/// </summary>
public float PlanCooldown { get; } = 0.5f;
private float _planCooldownRemaining;
/// <summary>
/// If we've requested a plan then wait patiently for the action
/// </summary>
private AiActionRequestJob _actionRequest;
private CancellationTokenSource _actionCancellation;
/// <summary>
/// If we can't do anything then stop thinking; should probably use ActionBlocker instead
/// </summary>
private bool _isDead;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
var bSets = serializer.ReadDataField("behaviorSets", new List<string>());
if (bSets.Count > 0)
{
var behaviorManager = IoCManager.Resolve<INpcBehaviorManager>();
foreach (var bSet in bSets)
{
behaviorManager.AddBehaviorSet(this, bSet, false);
}
behaviorManager.RebuildActions(this);
}
}
public override void Initialize()
{
base.Initialize();
_planCooldownRemaining = PlanCooldown;
_blackboard = new Blackboard(Owner);
_planner = EntitySystem.Get<AiActionSystem>();
}
public override void OnRemove()
{
base.OnRemove();
var currentOp = CurrentAction?.ActionOperators.Peek();
currentOp?.Shutdown(Outcome.Failed);
CurrentAction?.Shutdown();
CurrentAction = null;
}
public void MobStateChanged(MobStateChangedMessage message)
{
var oldDeadState = _isDead;
_isDead = message.Component.IsIncapacitated();
if (oldDeadState != _isDead)
{
var entityManager = IoCManager.Resolve<IEntityManager>();
switch (_isDead)
{
case true:
entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, true));
break;
case false:
entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, false));
break;
}
}
}
private void ReceivedAction()
{
switch (_actionRequest.Exception)
{
case null:
break;
default:
Logger.FatalS("ai", _actionRequest.Exception.ToString());
throw _actionRequest.Exception;
}
var action = _actionRequest.Result;
_actionRequest = null;
// Actions with lower scores should be implicitly dumped by GetAction
// If we're not allowed to replace the action with an action of the same type then dump.
if (action == null || !action.CanOverride && CurrentAction?.GetType() == action.GetType())
{
return;
}
var currentOp = CurrentAction?.ActionOperators.Peek();
if (currentOp != null && currentOp.HasStartup)
{
currentOp.Shutdown(Outcome.Failed);
}
CurrentAction = action;
action.SetupOperators(_blackboard);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// If we asked for a new action we don't want to dump the existing one.
if (_actionRequest != null)
{
if (_actionRequest.Status != JobStatus.Finished)
{
return;
}
ReceivedAction();
// Do something next tick
return;
}
_planCooldownRemaining -= frameTime;
// Might find a better action while we're doing one already
if (_planCooldownRemaining <= 0.0f)
{
_planCooldownRemaining = PlanCooldown;
_actionCancellation = new CancellationTokenSource();
_actionRequest = _planner.RequestAction(new AiActionRequest(Owner.Uid, _blackboard, AvailableActions), _actionCancellation);
return;
}
// When we spawn in we won't get an action for a bit
if (CurrentAction == null)
{
return;
}
var outcome = CurrentAction.Execute(frameTime);
switch (outcome)
{
case Outcome.Success:
if (CurrentAction.ActionOperators.Count == 0)
{
CurrentAction.Shutdown();
CurrentAction = null;
// Nothing to compare new action to
_blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
}
break;
case Outcome.Continuing:
break;
case Outcome.Failed:
CurrentAction.Shutdown();
CurrentAction = null;
_blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}