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>
This commit is contained in:
@@ -1,60 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Shared.Utility;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Reflection;
|
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.AI
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
[TestOf(typeof(AiControllerTest))]
|
|
||||||
public class AiControllerTest : ContentIntegrationTest
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public async Task AiProcessorNamesValidTest()
|
|
||||||
{
|
|
||||||
var server = StartServerDummyTicker();
|
|
||||||
|
|
||||||
server.Assert(() =>
|
|
||||||
{
|
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
|
||||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
|
||||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
|
||||||
|
|
||||||
mapManager.CreateMap(new MapId(1));
|
|
||||||
var grid = mapManager.CreateGrid(new MapId(1));
|
|
||||||
|
|
||||||
var processorNames = new List<string>();
|
|
||||||
|
|
||||||
// Verify they all have the required attribute
|
|
||||||
foreach (var processor in reflectionManager.GetAllChildren(typeof(AiLogicProcessor)))
|
|
||||||
{
|
|
||||||
var attrib = (AiLogicProcessorAttribute) Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute));
|
|
||||||
Assert.That(attrib != null, $"No AiLogicProcessorAttribute found on {processor.Name}");
|
|
||||||
processorNames.Add(attrib.SerializeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entity in prototypeManager.EnumeratePrototypes<EntityPrototype>())
|
|
||||||
{
|
|
||||||
var comps = entity.Components;
|
|
||||||
|
|
||||||
if (!comps.ContainsKey("AiController")) continue;
|
|
||||||
|
|
||||||
var aiEntity = entityManager.SpawnEntity(entity.ID, grid.ToCoordinates());
|
|
||||||
var aiController = aiEntity.GetComponent<AiControllerComponent>();
|
|
||||||
Assert.That(processorNames.Contains(aiController.LogicName), $"Could not find valid processor named {aiController.LogicName} on entity {entity.ID}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.WaitIdleAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
Content.IntegrationTests/Tests/AI/BehaviorSetsTest.cs
Normal file
63
Content.IntegrationTests/Tests/AI/BehaviorSetsTest.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.AI.Utility;
|
||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Reflection;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.AI
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(BehaviorSetPrototype))]
|
||||||
|
public class BehaviorSetsTest : ContentIntegrationTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task TestBehaviorSets()
|
||||||
|
{
|
||||||
|
var options = new ServerIntegrationOptions();
|
||||||
|
var server = StartServerDummyTicker(options);
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
|
||||||
|
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||||
|
var reflectionManager = server.ResolveDependency<IReflectionManager>();
|
||||||
|
|
||||||
|
Dictionary<string, List<string>> behaviorSets = new();
|
||||||
|
|
||||||
|
// Test that all BehaviorSet actions exist.
|
||||||
|
await server.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
foreach (var proto in protoManager.EnumeratePrototypes<BehaviorSetPrototype>())
|
||||||
|
{
|
||||||
|
behaviorSets[proto.ID] = proto.Actions.ToList();
|
||||||
|
|
||||||
|
foreach (var action in proto.Actions)
|
||||||
|
{
|
||||||
|
if (!reflectionManager.TryLooseGetType(action, out var actionType) ||
|
||||||
|
!typeof(IAiUtility).IsAssignableFrom(actionType))
|
||||||
|
{
|
||||||
|
Assert.Fail($"Action {action} is not valid within BehaviorSet {proto.ID}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test that all BehaviorSets on NPCs exist.
|
||||||
|
await server.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
foreach (var entity in protoManager.EnumeratePrototypes<EntityPrototype>())
|
||||||
|
{
|
||||||
|
if (!entity.Components.TryGetValue("UtilityAI", out var npcNode)) continue;
|
||||||
|
var sets = npcNode["behaviorSets"];
|
||||||
|
|
||||||
|
foreach (var entry in (YamlSequenceNode) sets)
|
||||||
|
{
|
||||||
|
Assert.That(behaviorSets.ContainsKey(entry.ToString()), $"BehaviorSet {entry} in entity {entity.ID} not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ using Robust.Shared.GameObjects;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.AI
|
namespace Content.IntegrationTests.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public sealed class SpriteTest : ContentIntegrationTest
|
public sealed class SpriteTest : ContentIntegrationTest
|
||||||
@@ -8,7 +8,7 @@ namespace Content.Server.AI.Operators
|
|||||||
public bool HasShutdown { get; private set; }
|
public bool HasShutdown { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called once when the AiLogicProcessor starts this action
|
/// Called once when the NPC starts this action
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>true if it hasn't started up previously</returns>
|
/// <returns>true if it hasn't started up previously</returns>
|
||||||
public virtual bool Startup()
|
public virtual bool Startup()
|
||||||
@@ -24,7 +24,7 @@ namespace Content.Server.AI.Operators
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called once when the AiLogicProcessor is done with this action if the outcome is successful or fails.
|
/// Called once when the NPC is done with this action if the outcome is successful or fails.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool Shutdown(Outcome outcome)
|
public virtual bool Shutdown(Outcome outcome)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,27 +13,21 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
|||||||
{
|
{
|
||||||
public sealed class EquipGloves : UtilityAction
|
public sealed class EquipGloves : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public EquipGloves(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, Target),
|
||||||
new UseItemInInventoryOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -14,23 +14,17 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
|||||||
{
|
{
|
||||||
public sealed class PickUpGloves : UtilityAction
|
public sealed class PickUpGloves : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public PickUpGloves(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -13,27 +13,21 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
|||||||
{
|
{
|
||||||
public sealed class EquipHead : UtilityAction
|
public sealed class EquipHead : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public EquipHead(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, Target),
|
||||||
new UseItemInInventoryOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -14,23 +14,17 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
|||||||
{
|
{
|
||||||
public sealed class PickUpHead : UtilityAction
|
public sealed class PickUpHead : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public PickUpHead(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -13,27 +13,21 @@ namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
|||||||
{
|
{
|
||||||
public sealed class EquipOuterClothing : UtilityAction
|
public sealed class EquipOuterClothing : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public EquipOuterClothing(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, Target),
|
||||||
new UseItemInInventoryOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -14,23 +14,17 @@ namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
|||||||
{
|
{
|
||||||
public sealed class PickUpOuterClothing : UtilityAction
|
public sealed class PickUpOuterClothing : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public PickUpOuterClothing(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -13,27 +13,21 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
|||||||
{
|
{
|
||||||
public sealed class EquipShoes : UtilityAction
|
public sealed class EquipShoes : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public EquipShoes(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, Target),
|
||||||
new UseItemInInventoryOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -14,23 +14,17 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
|||||||
{
|
{
|
||||||
public sealed class PickUpShoes : UtilityAction
|
public sealed class PickUpShoes : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public PickUpShoes(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -15,27 +15,21 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
|||||||
{
|
{
|
||||||
public sealed class EquipMelee : UtilityAction
|
public sealed class EquipMelee : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public EquipMelee(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity)
|
new EquipEntityOperator(Owner, Target)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
context.GetState<WeaponEntityState>().SetValue(Target);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -21,13 +21,7 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
|||||||
{
|
{
|
||||||
public sealed class MeleeWeaponAttackEntity : UtilityAction
|
public sealed class MeleeWeaponAttackEntity : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public MeleeWeaponAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
@@ -35,26 +29,26 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
|||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
if (equipped != null && equipped.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent))
|
if (equipped != null && equipped.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent))
|
||||||
{
|
{
|
||||||
moveOperator = new MoveToEntityOperator(Owner, _entity, meleeWeaponComponent.Range - 0.01f);
|
moveOperator = new MoveToEntityOperator(Owner, Target, meleeWeaponComponent.Range - 0.01f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: Abort
|
// TODO: Abort
|
||||||
moveOperator = new MoveToEntityOperator(Owner, _entity);
|
moveOperator = new MoveToEntityOperator(Owner, Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
moveOperator,
|
moveOperator,
|
||||||
new SwingMeleeWeaponOperator(Owner, _entity),
|
new SwingMeleeWeaponOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
context.GetState<MoveTargetState>().SetValue(Target);
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
context.GetState<WeaponEntityState>().SetValue(equipped);
|
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,24 +15,18 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
|||||||
{
|
{
|
||||||
public sealed class PickUpMeleeWeapon : UtilityAction
|
public sealed class PickUpMeleeWeapon : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public PickUpMeleeWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
context.GetState<WeaponEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -19,40 +19,34 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
|||||||
{
|
{
|
||||||
public sealed class UnarmedAttackEntity : UtilityAction
|
public sealed class UnarmedAttackEntity : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public UnarmedAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
MoveToEntityOperator moveOperator;
|
MoveToEntityOperator moveOperator;
|
||||||
if (Owner.TryGetComponent(out UnarmedCombatComponent unarmedCombatComponent))
|
if (Owner.TryGetComponent(out UnarmedCombatComponent unarmedCombatComponent))
|
||||||
{
|
{
|
||||||
moveOperator = new MoveToEntityOperator(Owner, _entity, unarmedCombatComponent.Range - 0.01f);
|
moveOperator = new MoveToEntityOperator(Owner, Target, unarmedCombatComponent.Range - 0.01f);
|
||||||
}
|
}
|
||||||
// I think it's possible for this to happen given planning is time-sliced?
|
// I think it's possible for this to happen given planning is time-sliced?
|
||||||
// TODO: At this point we should abort
|
// TODO: At this point we should abort
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
moveOperator = new MoveToEntityOperator(Owner, _entity);
|
moveOperator = new MoveToEntityOperator(Owner, Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
moveOperator,
|
moveOperator,
|
||||||
new UnarmedCombatOperator(Owner, _entity),
|
new UnarmedCombatOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
context.GetState<MoveTargetState>().SetValue(Target);
|
||||||
// Can just set ourselves as entity given unarmed just inherits from meleeweapon
|
// Can just set ourselves as entity given unarmed just inherits from meleeweapon
|
||||||
context.GetState<WeaponEntityState>().SetValue(Owner);
|
context.GetState<WeaponEntityState>().SetValue(Owner);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions
|
namespace Content.Server.AI.Utility.Actions
|
||||||
{
|
{
|
||||||
public interface IAiUtility
|
public interface IAiUtility
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// NPC this action is attached to.
|
||||||
|
/// </summary>
|
||||||
|
IEntity Owner { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Highest possible score for this action.
|
||||||
|
/// </summary>
|
||||||
float Bonus { get; }
|
float Bonus { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ namespace Content.Server.AI.Utility.Actions.Idle
|
|||||||
{
|
{
|
||||||
public override float Bonus => IdleBonus + 0.01f;
|
public override float Bonus => IdleBonus + 0.01f;
|
||||||
|
|
||||||
public CloseLastEntityStorage(IEntity owner) : base(owner) {}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
var lastStorage = context.GetState<LastOpenedStorageState>().GetValue();
|
var lastStorage = context.GetState<LastOpenedStorageState>().GetValue();
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ namespace Content.Server.AI.Utility.Actions.Idle
|
|||||||
public override bool CanOverride => false;
|
public override bool CanOverride => false;
|
||||||
public override float Bonus => 1.0f;
|
public override float Bonus => 1.0f;
|
||||||
|
|
||||||
public WanderAndWait(IEntity owner) : base(owner) {}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
|||||||
@@ -14,23 +14,17 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
|||||||
{
|
{
|
||||||
public sealed class PickUpDrink : UtilityAction
|
public sealed class PickUpDrink : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public PickUpDrink(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -15,27 +15,21 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
|||||||
{
|
{
|
||||||
public sealed class UseDrinkInInventory : UtilityAction
|
public sealed class UseDrinkInInventory : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public UseDrinkInInventory(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, Target),
|
||||||
new UseDrinkInInventoryOperator(Owner, _entity),
|
new UseDrinkInInventoryOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -14,23 +14,17 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
|||||||
{
|
{
|
||||||
public sealed class PickUpFood : UtilityAction
|
public sealed class PickUpFood : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public PickUpFood(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -15,27 +15,21 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
|||||||
{
|
{
|
||||||
public sealed class UseFoodInInventory : UtilityAction
|
public sealed class UseFoodInInventory : UtilityAction
|
||||||
{
|
{
|
||||||
private readonly IEntity _entity;
|
public IEntity Target { get; set; }
|
||||||
|
|
||||||
public UseFoodInInventory(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, Target),
|
||||||
new UseFoodInInventoryOperator(Owner, _entity),
|
new UseFoodInInventoryOperator(Owner, Target),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
{
|
{
|
||||||
base.UpdateBlackboard(context);
|
base.UpdateBlackboard(context);
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
context.GetState<TargetEntityState>().SetValue(Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ namespace Content.Server.AI.Utility.Actions.Test
|
|||||||
{
|
{
|
||||||
public override bool CanOverride => false;
|
public override bool CanOverride => false;
|
||||||
|
|
||||||
public MoveRightAndLeftTen(IEntity owner) : base(owner) {}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
public override void SetupOperators(Blackboard context)
|
||||||
{
|
{
|
||||||
var currentPosition = Owner.Transform.Coordinates;
|
var currentPosition = Owner.Transform.Coordinates;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Content.Server.AI.Utility.Actions
|
|||||||
/// Threshold doesn't necessarily mean we'll do an action at a higher threshold;
|
/// Threshold doesn't necessarily mean we'll do an action at a higher threshold;
|
||||||
/// if it's really un-optimal (i.e. low score) then we'll also check lower tiers
|
/// if it's really un-optimal (i.e. low score) then we'll also check lower tiers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual float Bonus { get; protected set; } = IdleBonus;
|
public virtual float Bonus { get; set; } = IdleBonus;
|
||||||
// For GW2 they had the bonuses close together but IMO it feels better when they're more like discrete tiers.
|
// For GW2 they had the bonuses close together but IMO it feels better when they're more like discrete tiers.
|
||||||
|
|
||||||
// These are just baselines to make mass-updates easier; actions can do whatever
|
// These are just baselines to make mass-updates easier; actions can do whatever
|
||||||
@@ -36,7 +36,7 @@ namespace Content.Server.AI.Utility.Actions
|
|||||||
public const float CombatBonus = 30.0f;
|
public const float CombatBonus = 30.0f;
|
||||||
public const float DangerBonus = 50.0f;
|
public const float DangerBonus = 50.0f;
|
||||||
|
|
||||||
protected IEntity Owner { get; }
|
public IEntity Owner { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All the considerations are multiplied together to get the final score; a consideration of 0.0 means the action is not possible.
|
/// All the considerations are multiplied together to get the final score; a consideration of 0.0 means the action is not possible.
|
||||||
@@ -58,10 +58,8 @@ namespace Content.Server.AI.Utility.Actions
|
|||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
protected virtual void UpdateBlackboard(Blackboard context) {}
|
protected virtual void UpdateBlackboard(Blackboard context) {}
|
||||||
|
|
||||||
protected UtilityAction(IEntity owner)
|
// Needs to be able to be instantiated without args via typefactory.
|
||||||
{
|
public UtilityAction() {}
|
||||||
Owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Shutdown() {}
|
public virtual void Shutdown() {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.BehaviorSets;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.AiLogic
|
|
||||||
{
|
|
||||||
[AiLogicProcessor("Civilian")]
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class Civilian : UtilityAi
|
|
||||||
{
|
|
||||||
public override void Setup()
|
|
||||||
{
|
|
||||||
base.Setup();
|
|
||||||
AddBehaviorSet(new ClothingBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new HungerBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new ThirstBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new IdleBehaviorSet(SelfEntity), false);
|
|
||||||
SortActions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.BehaviorSets;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.AiLogic
|
|
||||||
{
|
|
||||||
[AiLogicProcessor("Mimic")]
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class Mimic : UtilityAi
|
|
||||||
{
|
|
||||||
public override void Setup()
|
|
||||||
{
|
|
||||||
base.Setup();
|
|
||||||
AddBehaviorSet(new UnarmedAttackPlayersBehaviorSet(SelfEntity), false);
|
|
||||||
SortActions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.BehaviorSets;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.AiLogic
|
|
||||||
{
|
|
||||||
[AiLogicProcessor("PathingDummy")]
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class PathingDummy : UtilityAi
|
|
||||||
{
|
|
||||||
public override void Setup()
|
|
||||||
{
|
|
||||||
base.Setup();
|
|
||||||
BehaviorSets.Add(typeof(PathingDummyBehaviorSet), new PathingDummyBehaviorSet(SelfEntity));
|
|
||||||
SortActions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.BehaviorSets;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.AiLogic
|
|
||||||
{
|
|
||||||
[AiLogicProcessor("Spirate")]
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class Spirate : UtilityAi
|
|
||||||
{
|
|
||||||
public override void Setup()
|
|
||||||
{
|
|
||||||
base.Setup();
|
|
||||||
AddBehaviorSet(new ClothingBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new HungerBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new ThirstBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new IdleBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new SpirateBehaviorSet(SelfEntity), false);
|
|
||||||
SortActions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,23 +3,28 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.AI.Operators;
|
using Content.Server.AI.Operators;
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.Utility.BehaviorSets;
|
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States.Utility;
|
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;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer;
|
using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer;
|
||||||
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
using Robust.Server.AI;
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.AiLogic
|
namespace Content.Server.AI.Utility.AiLogic
|
||||||
{
|
{
|
||||||
public abstract class UtilityAi : AiLogicProcessor
|
// 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)
|
// 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)
|
// Also RepeatOperators (e.g. if we're following an entity keep repeating MoveToEntity)
|
||||||
private AiActionSystem _planner;
|
private AiActionSystem _planner;
|
||||||
@@ -29,8 +34,9 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sum of all BehaviorSets gives us what actions the AI can take
|
/// The sum of all BehaviorSets gives us what actions the AI can take
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<Type, BehaviorSet> BehaviorSets { get; } = new();
|
public HashSet<string> BehaviorSets { get; } = new();
|
||||||
private readonly List<IAiUtility> _availableActions = new();
|
|
||||||
|
public List<IAiUtility> AvailableActions { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently running action; most importantly are the operators.
|
/// The currently running action; most importantly are the operators.
|
||||||
@@ -54,84 +60,37 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// If we can't do anything then stop thinking; should probably use ActionBlocker instead
|
/// If we can't do anything then stop thinking; should probably use ActionBlocker instead
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool _isDead = false;
|
private bool _isDead;
|
||||||
|
|
||||||
// These 2 methods will be used eventually if / when we get a director AI
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
public void AddBehaviorSet<T>(T behaviorSet, bool sort = true) where T : BehaviorSet
|
|
||||||
{
|
{
|
||||||
if (BehaviorSets.TryAdd(typeof(T), behaviorSet) && sort)
|
base.ExposeData(serializer);
|
||||||
|
var bSets = serializer.ReadDataField("behaviorSets", new List<string>());
|
||||||
|
|
||||||
|
if (bSets.Count > 0)
|
||||||
{
|
{
|
||||||
SortActions();
|
var behaviorManager = IoCManager.Resolve<INpcBehaviorManager>();
|
||||||
|
|
||||||
|
foreach (var bSet in bSets)
|
||||||
|
{
|
||||||
|
behaviorManager.AddBehaviorSet(this, bSet, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BehaviorSets.Count == 1 && !EntitySystem.Get<AiSystem>().IsAwake(this))
|
behaviorManager.RebuildActions(this);
|
||||||
{
|
|
||||||
IoCManager.Resolve<IEntityManager>()
|
|
||||||
.EventBus
|
|
||||||
.RaiseEvent(EventSource.Local, new SleepAiMessage(this, false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveBehaviorSet(Type behaviorSet)
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
DebugTools.Assert(behaviorSet.IsAssignableFrom(typeof(BehaviorSet)));
|
base.Initialize();
|
||||||
|
|
||||||
if (BehaviorSets.ContainsKey(behaviorSet))
|
|
||||||
{
|
|
||||||
BehaviorSets.Remove(behaviorSet);
|
|
||||||
SortActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BehaviorSets.Count == 0)
|
|
||||||
{
|
|
||||||
IoCManager.Resolve<IEntityManager>()
|
|
||||||
.EventBus
|
|
||||||
.RaiseEvent(EventSource.Local, new SleepAiMessage(this, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whenever the behavior sets are changed we'll re-sort the actions by bonus
|
|
||||||
/// </summary>
|
|
||||||
protected void SortActions()
|
|
||||||
{
|
|
||||||
_availableActions.Clear();
|
|
||||||
foreach (var set in BehaviorSets.Values)
|
|
||||||
{
|
|
||||||
foreach (var action in set.Actions)
|
|
||||||
{
|
|
||||||
var found = false;
|
|
||||||
|
|
||||||
for (var i = 0; i < _availableActions.Count; i++)
|
|
||||||
{
|
|
||||||
if (_availableActions[i].Bonus < action.Bonus)
|
|
||||||
{
|
|
||||||
_availableActions.Insert(i, action);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
_availableActions.Add(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_availableActions.Reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Setup()
|
|
||||||
{
|
|
||||||
base.Setup();
|
|
||||||
_planCooldownRemaining = PlanCooldown;
|
_planCooldownRemaining = PlanCooldown;
|
||||||
_blackboard = new Blackboard(SelfEntity);
|
_blackboard = new Blackboard(Owner);
|
||||||
_planner = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiActionSystem>();
|
_planner = EntitySystem.Get<AiActionSystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void OnRemove()
|
||||||
{
|
{
|
||||||
|
base.OnRemove();
|
||||||
var currentOp = CurrentAction?.ActionOperators.Peek();
|
var currentOp = CurrentAction?.ActionOperators.Peek();
|
||||||
currentOp?.Shutdown(Outcome.Failed);
|
currentOp?.Shutdown(Outcome.Failed);
|
||||||
CurrentAction?.Shutdown();
|
CurrentAction?.Shutdown();
|
||||||
@@ -190,6 +149,8 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
|
|
||||||
public override void Update(float frameTime)
|
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 we asked for a new action we don't want to dump the existing one.
|
||||||
if (_actionRequest != null)
|
if (_actionRequest != null)
|
||||||
{
|
{
|
||||||
@@ -210,7 +171,7 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
{
|
{
|
||||||
_planCooldownRemaining = PlanCooldown;
|
_planCooldownRemaining = PlanCooldown;
|
||||||
_actionCancellation = new CancellationTokenSource();
|
_actionCancellation = new CancellationTokenSource();
|
||||||
_actionRequest = _planner.RequestAction(new AiActionRequest(SelfEntity.Uid, _blackboard, _availableActions), _actionCancellation);
|
_actionRequest = _planner.RequestAction(new AiActionRequest(Owner.Uid, _blackboard, AvailableActions), _actionCancellation);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.BehaviorSets;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.AiLogic
|
|
||||||
{
|
|
||||||
[AiLogicProcessor("Xeno")]
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class Xeno : UtilityAi
|
|
||||||
{
|
|
||||||
public override void Setup()
|
|
||||||
{
|
|
||||||
base.Setup();
|
|
||||||
AddBehaviorSet(new IdleBehaviorSet(SelfEntity), false);
|
|
||||||
AddBehaviorSet(new UnarmedAttackPlayersBehaviorSet(SelfEntity), false);
|
|
||||||
SortActions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
Content.Server/AI/Utility/BehaviorSetPrototype.cs
Normal file
28
Content.Server/AI/Utility/BehaviorSetPrototype.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility
|
||||||
|
{
|
||||||
|
[Prototype("behaviorSet")]
|
||||||
|
public class BehaviorSetPrototype : IPrototype
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the BehaviorSet.
|
||||||
|
/// </summary>
|
||||||
|
public string ID { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actions that this BehaviorSet grants to the entity.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> Actions { get; private set; }
|
||||||
|
|
||||||
|
public void LoadFrom(YamlMappingNode mapping)
|
||||||
|
{
|
||||||
|
var serializer = YamlObjectSerializer.NewReader(mapping);
|
||||||
|
serializer.DataField(this, x => x.ID, "id", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.Actions, "actions", new List<string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// AKA DecisionMaker in IAUS. Just a group of actions that can be dynamically added or taken away from an AI.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class BehaviorSet
|
|
||||||
{
|
|
||||||
protected IEntity Owner;
|
|
||||||
|
|
||||||
public BehaviorSet(IEntity owner)
|
|
||||||
{
|
|
||||||
Owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IAiUtility> Actions { get; protected set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Clothing.Gloves;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Clothing.Head;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Clothing.OuterClothing;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Clothing.Shoes;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
public sealed class ClothingBehaviorSet : BehaviorSet
|
|
||||||
{
|
|
||||||
public ClothingBehaviorSet(IEntity owner) : base(owner)
|
|
||||||
{
|
|
||||||
Actions = new IAiUtility[]
|
|
||||||
{
|
|
||||||
new EquipAnyHeadExp(),
|
|
||||||
new EquipAnyOuterClothingExp(),
|
|
||||||
new EquipAnyGlovesExp(),
|
|
||||||
new EquipAnyShoesExp(),
|
|
||||||
new PickUpAnyNearbyHeadExp(),
|
|
||||||
new PickUpAnyNearbyOuterClothingExp(),
|
|
||||||
new PickUpAnyNearbyGlovesExp(),
|
|
||||||
new PickUpAnyNearbyShoesExp(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Nutrition;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
public sealed class HungerBehaviorSet : BehaviorSet
|
|
||||||
{
|
|
||||||
public HungerBehaviorSet(IEntity owner) : base(owner)
|
|
||||||
{
|
|
||||||
Actions = new IAiUtility[]
|
|
||||||
{
|
|
||||||
new PickUpNearbyFoodExp(),
|
|
||||||
new UseFoodInInventoryExp(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Idle;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
public class IdleBehaviorSet : BehaviorSet
|
|
||||||
{
|
|
||||||
public IdleBehaviorSet(IEntity owner) : base(owner)
|
|
||||||
{
|
|
||||||
Actions = new IAiUtility[]
|
|
||||||
{
|
|
||||||
new CloseLastEntityStorage(Owner),
|
|
||||||
new WanderAndWait(Owner),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Test;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
public sealed class PathingDummyBehaviorSet : BehaviorSet
|
|
||||||
{
|
|
||||||
public PathingDummyBehaviorSet(IEntity owner) : base(owner)
|
|
||||||
{
|
|
||||||
Actions = new IAiUtility[]
|
|
||||||
{
|
|
||||||
new MoveRightAndLeftTen(owner),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Combat.Melee;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
public sealed class SpirateBehaviorSet : BehaviorSet
|
|
||||||
{
|
|
||||||
public SpirateBehaviorSet(IEntity owner) : base(owner)
|
|
||||||
{
|
|
||||||
Actions = new IAiUtility[]
|
|
||||||
{
|
|
||||||
// TODO: Reload Ballistic
|
|
||||||
// TODO: Ideally long-term we should just store the weapons in backpack
|
|
||||||
new EquipMeleeExp(),
|
|
||||||
new PickUpMeleeWeaponExp(),
|
|
||||||
new MeleeAttackNearbyExp(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Nutrition;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
public sealed class ThirstBehaviorSet : BehaviorSet
|
|
||||||
{
|
|
||||||
public ThirstBehaviorSet(IEntity owner) : base(owner)
|
|
||||||
{
|
|
||||||
Actions = new IAiUtility[]
|
|
||||||
{
|
|
||||||
new PickUpNearbyDrinkExp(),
|
|
||||||
new UseDrinkInInventoryExp(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Combat.Melee;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
|
||||||
{
|
|
||||||
public sealed class UnarmedAttackPlayersBehaviorSet : BehaviorSet
|
|
||||||
{
|
|
||||||
public UnarmedAttackPlayersBehaviorSet(IEntity owner) : base(owner)
|
|
||||||
{
|
|
||||||
Actions = new IAiUtility[]
|
|
||||||
{
|
|
||||||
new UnarmedAttackNearbyPlayerExp(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Gloves
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.GLOVES) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.GLOVES) != 0)
|
||||||
{
|
{
|
||||||
yield return new EquipGloves(owner, entity, Bonus);
|
yield return new EquipGloves {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Gloves
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.GLOVES) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.GLOVES) != 0)
|
||||||
{
|
{
|
||||||
yield return new PickUpGloves(owner, entity, Bonus);
|
yield return new PickUpGloves {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Head
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.HEAD) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.HEAD) != 0)
|
||||||
{
|
{
|
||||||
yield return new EquipHead(owner, entity, Bonus);
|
yield return new EquipHead {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Head
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.HEAD) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.HEAD) != 0)
|
||||||
{
|
{
|
||||||
yield return new PickUpHead(owner, entity, Bonus);
|
yield return new PickUpHead() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.OuterClothing
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.OUTERCLOTHING) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.OUTERCLOTHING) != 0)
|
||||||
{
|
{
|
||||||
yield return new EquipOuterClothing(owner, entity, Bonus);
|
yield return new EquipOuterClothing() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.OuterClothing
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.OUTERCLOTHING) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.OUTERCLOTHING) != 0)
|
||||||
{
|
{
|
||||||
yield return new PickUpOuterClothing(owner, entity, Bonus);
|
yield return new PickUpOuterClothing() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Shoes
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.SHOES) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.SHOES) != 0)
|
||||||
{
|
{
|
||||||
yield return new EquipShoes(owner, entity, Bonus);
|
yield return new EquipShoes() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Shoes
|
|||||||
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
if (entity.TryGetComponent(out ClothingComponent clothing) &&
|
||||||
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.SHOES) != 0)
|
(clothing.SlotFlags & EquipmentSlotDefines.SlotFlags.SHOES) != 0)
|
||||||
{
|
{
|
||||||
yield return new PickUpShoes(owner, entity, Bonus);
|
yield return new PickUpShoes {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new EquipMelee(owner, entity, Bonus);
|
yield return new EquipMelee() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
|
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
|
||||||
.GetNearbyHostiles(owner, controller.VisionRadius))
|
.GetNearbyHostiles(owner, controller.VisionRadius))
|
||||||
{
|
{
|
||||||
yield return new MeleeWeaponAttackEntity(owner, target, Bonus);
|
yield return new MeleeWeaponAttackEntity() {Owner = owner, Target = target, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
|
|
||||||
foreach (var entity in context.GetState<NearbyMeleeWeapons>().GetValue())
|
foreach (var entity in context.GetState<NearbyMeleeWeapons>().GetValue())
|
||||||
{
|
{
|
||||||
yield return new PickUpMeleeWeapon(owner, entity, Bonus);
|
yield return new PickUpMeleeWeapon() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using Robust.Shared.IoC;
|
|||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
||||||
{
|
{
|
||||||
public sealed class UnarmedAttackNearbyPlayerExp : ExpandableUtilityAction
|
public sealed class UnarmedAttackNearbyHostilesExp : ExpandableUtilityAction
|
||||||
{
|
{
|
||||||
public override float Bonus => UtilityAction.CombatBonus;
|
public override float Bonus => UtilityAction.CombatBonus;
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
|
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
|
||||||
.GetNearbyHostiles(owner, controller.VisionRadius))
|
.GetNearbyHostiles(owner, controller.VisionRadius))
|
||||||
{
|
{
|
||||||
yield return new UnarmedAttackEntity(owner, target, Bonus);
|
yield return new UnarmedAttackEntity() {Owner = owner, Target = target, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions
|
namespace Content.Server.AI.Utility.ExpandableActions
|
||||||
{
|
{
|
||||||
@@ -11,6 +12,8 @@ namespace Content.Server.AI.Utility.ExpandableActions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ExpandableUtilityAction : IAiUtility
|
public abstract class ExpandableUtilityAction : IAiUtility
|
||||||
{
|
{
|
||||||
|
public IEntity Owner { get; set; }
|
||||||
|
|
||||||
public abstract float Bonus { get; }
|
public abstract float Bonus { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Nutrition
|
|||||||
|
|
||||||
foreach (var entity in context.GetState<NearbyDrinkState>().GetValue())
|
foreach (var entity in context.GetState<NearbyDrinkState>().GetValue())
|
||||||
{
|
{
|
||||||
yield return new PickUpDrink(owner, entity, Bonus);
|
yield return new PickUpDrink() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Nutrition
|
|||||||
|
|
||||||
foreach (var entity in context.GetState<NearbyFoodState>().GetValue())
|
foreach (var entity in context.GetState<NearbyFoodState>().GetValue())
|
||||||
{
|
{
|
||||||
yield return new PickUpFood(owner, entity, Bonus);
|
yield return new PickUpFood {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Nutrition
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new UseDrinkInInventory(owner, entity, Bonus);
|
yield return new UseDrinkInInventory {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Nutrition
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new UseFoodInInventory(owner, entity, Bonus);
|
yield return new UseFoodInInventory() {Owner = owner, Target = entity, Bonus = Bonus};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
172
Content.Server/AI/Utility/NpcBehaviorManager.cs
Normal file
172
Content.Server/AI/Utility/NpcBehaviorManager.cs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.AiLogic;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.AI;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Reflection;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility
|
||||||
|
{
|
||||||
|
internal interface INpcBehaviorManager
|
||||||
|
{
|
||||||
|
void Initialize();
|
||||||
|
|
||||||
|
void AddBehaviorSet(UtilityAi npc, string behaviorSet, bool rebuild = true);
|
||||||
|
|
||||||
|
void RemoveBehaviorSet(UtilityAi npc, string behaviorSet, bool rebuild = true);
|
||||||
|
|
||||||
|
void RebuildActions(UtilityAi npc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles BehaviorSets and adding / removing behaviors to NPCs
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class NpcBehaviorManager : INpcBehaviorManager
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IDynamicTypeFactory _typeFactory = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
|
private readonly NpcActionComparer _comparer = new();
|
||||||
|
|
||||||
|
private Dictionary<string, List<Type>> _behaviorSets = new();
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||||
|
|
||||||
|
foreach (var bSet in protoManager.EnumeratePrototypes<BehaviorSetPrototype>())
|
||||||
|
{
|
||||||
|
var actions = new List<Type>();
|
||||||
|
|
||||||
|
foreach (var act in bSet.Actions)
|
||||||
|
{
|
||||||
|
if (!reflectionManager.TryLooseGetType(act, out var parsedType) ||
|
||||||
|
!typeof(IAiUtility).IsAssignableFrom(parsedType))
|
||||||
|
{
|
||||||
|
Logger.Error($"Unable to parse AI action for {act}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actions.Add(parsedType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_behaviorSets[bSet.ID] = actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the BehaviorSet to the NPC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="npc"></param>
|
||||||
|
/// <param name="behaviorSet"></param>
|
||||||
|
/// <param name="rebuild">Set to false if you want to manually rebuild it after bulk updates.</param>
|
||||||
|
public void AddBehaviorSet(UtilityAi npc, string behaviorSet, bool rebuild = true)
|
||||||
|
{
|
||||||
|
if (!_behaviorSets.ContainsKey(behaviorSet))
|
||||||
|
{
|
||||||
|
Logger.Error($"Tried to add BehaviorSet {behaviorSet} to {npc} but no such BehaviorSet found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!npc.BehaviorSets.Add(behaviorSet))
|
||||||
|
{
|
||||||
|
Logger.Error($"Tried to add BehaviorSet {behaviorSet} to {npc} which already has the BehaviorSet!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebuild)
|
||||||
|
RebuildActions(npc);
|
||||||
|
|
||||||
|
if (npc.BehaviorSets.Count == 1 && !EntitySystem.Get<AiSystem>().IsAwake(npc))
|
||||||
|
{
|
||||||
|
_entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(npc, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the BehaviorSet from the NPC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="npc"></param>
|
||||||
|
/// <param name="behaviorSet"></param>
|
||||||
|
/// <param name="rebuild">Set to false if yo uwant to manually rebuild it after bulk updates.</param>
|
||||||
|
public void RemoveBehaviorSet(UtilityAi npc, string behaviorSet, bool rebuild = true)
|
||||||
|
{
|
||||||
|
if (!_behaviorSets.TryGetValue(behaviorSet, out var actions))
|
||||||
|
{
|
||||||
|
Logger.Error($"Tried to remove BehaviorSet {behaviorSet} from {npc} but no such BehaviorSet found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!npc.BehaviorSets.Remove(behaviorSet))
|
||||||
|
{
|
||||||
|
Logger.Error($"Tried to remove BehaviorSet {behaviorSet} from {npc} but it doesn't have that BehaviorSet!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebuild)
|
||||||
|
RebuildActions(npc);
|
||||||
|
|
||||||
|
if (npc.BehaviorSets.Count == 0 && EntitySystem.Get<AiSystem>().IsAwake(npc))
|
||||||
|
{
|
||||||
|
_entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(npc, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear our actions and re-instantiate them from our BehaviorSets.
|
||||||
|
/// Will ensure each action is unique.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="npc"></param>
|
||||||
|
public void RebuildActions(UtilityAi npc)
|
||||||
|
{
|
||||||
|
npc.AvailableActions.Clear();
|
||||||
|
foreach (var bSet in npc.BehaviorSets)
|
||||||
|
{
|
||||||
|
foreach (var action in GetActions(bSet))
|
||||||
|
{
|
||||||
|
if (npc.AvailableActions.Contains(action)) continue;
|
||||||
|
// Setup
|
||||||
|
action.Owner = npc.Owner;
|
||||||
|
|
||||||
|
// Ad to actions.
|
||||||
|
npc.AvailableActions.Add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortActions(npc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<IAiUtility> GetActions(string behaviorSet)
|
||||||
|
{
|
||||||
|
foreach (var action in _behaviorSets[behaviorSet])
|
||||||
|
{
|
||||||
|
yield return (IAiUtility) _typeFactory.CreateInstance(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whenever the behavior sets are changed we'll re-sort the actions by bonus
|
||||||
|
/// </summary>
|
||||||
|
private void SortActions(UtilityAi npc)
|
||||||
|
{
|
||||||
|
npc.AvailableActions.Sort(_comparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NpcActionComparer : Comparer<IAiUtility>
|
||||||
|
{
|
||||||
|
public override int Compare(IAiUtility? x, IAiUtility? y)
|
||||||
|
{
|
||||||
|
if (x == null || y == null) return 0;
|
||||||
|
return y.Bonus.CompareTo(x.Bonus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ namespace Content.Server.AI.Utility
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiControllerComponent.Processor is UtilityAi utilityAi)
|
if (aiControllerComponent is UtilityAi utilityAi)
|
||||||
{
|
{
|
||||||
return utilityAi.Blackboard;
|
return utilityAi.Blackboard;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.AI.Utility;
|
||||||
|
using Content.Server.AI.Utility.AiLogic;
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI;
|
using Content.Server.GameObjects.EntitySystems.AI;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
@@ -15,41 +17,48 @@ namespace Content.Server.Commands.AI
|
|||||||
{
|
{
|
||||||
public string Command => "addai";
|
public string Command => "addai";
|
||||||
public string Description => "Add an ai component with a given processor to an entity.";
|
public string Description => "Add an ai component with a given processor to an entity.";
|
||||||
public string Help => "Usage: addai <processorId> <entityId>"
|
public string Help => "Usage: addai <entityId> <behaviorSet1> <behaviorSet2>..."
|
||||||
+ "\n processorId: Class that inherits AiLogicProcessor and has an AiLogicProcessor attribute."
|
+ "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this."
|
||||||
+ "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this.";
|
+ "\n behaviorSet: Name of a behaviorset to add to the component on initialize.";
|
||||||
|
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
{
|
{
|
||||||
if(args.Length != 2)
|
if(args.Length < 1)
|
||||||
{
|
{
|
||||||
shell.WriteLine("Wrong number of args.");
|
shell.WriteLine("Wrong number of args.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var processorId = args[0];
|
var entId = new EntityUid(int.Parse(args[0]));
|
||||||
var entId = new EntityUid(int.Parse(args[1]));
|
|
||||||
var ent = IoCManager.Resolve<IEntityManager>().GetEntity(entId);
|
|
||||||
var aiSystem = EntitySystem.Get<AiSystem>();
|
|
||||||
|
|
||||||
if (!aiSystem.ProcessorTypeExists(processorId))
|
if (!IoCManager.Resolve<IEntityManager>().TryGetEntity(entId, out var ent))
|
||||||
{
|
{
|
||||||
shell.WriteLine("Invalid processor type. Processor must inherit AiLogicProcessor and have an AiLogicProcessor attribute.");
|
shell.WriteLine($"Unable to find entity with uid {entId}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ent.HasComponent<AiControllerComponent>())
|
if (ent.HasComponent<AiControllerComponent>())
|
||||||
{
|
{
|
||||||
shell.WriteLine("Entity already has an AI component.");
|
shell.WriteLine("Entity already has an AI component.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: IMover refffaaccctttooorrr
|
||||||
if (ent.HasComponent<IMoverComponent>())
|
if (ent.HasComponent<IMoverComponent>())
|
||||||
{
|
{
|
||||||
ent.RemoveComponent<IMoverComponent>();
|
ent.RemoveComponent<IMoverComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var comp = ent.AddComponent<AiControllerComponent>();
|
var comp = ent.AddComponent<UtilityAi>();
|
||||||
comp.LogicName = processorId;
|
var behaviorManager = IoCManager.Resolve<INpcBehaviorManager>();
|
||||||
|
|
||||||
|
for (var i = 1; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
var bSet = args[i];
|
||||||
|
behaviorManager.AddBehaviorSet(comp, bSet, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
behaviorManager.RebuildActions(comp);
|
||||||
shell.WriteLine("AI component added.");
|
shell.WriteLine("AI component added.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.AI.Utility;
|
||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
@@ -92,6 +93,7 @@ namespace Content.Server
|
|||||||
IoCManager.Resolve<ConsiderationsManager>().Initialize();
|
IoCManager.Resolve<ConsiderationsManager>().Initialize();
|
||||||
IoCManager.Resolve<IPDAUplinkManager>().Initialize();
|
IoCManager.Resolve<IPDAUplinkManager>().Initialize();
|
||||||
IoCManager.Resolve<IAdminManager>().Initialize();
|
IoCManager.Resolve<IAdminManager>().Initialize();
|
||||||
|
IoCManager.Resolve<INpcBehaviorManager>().Initialize();
|
||||||
_euiManager.Initialize();
|
_euiManager.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,27 +17,10 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
[RegisterComponent, ComponentReference(typeof(IMoverComponent))]
|
[RegisterComponent, ComponentReference(typeof(IMoverComponent))]
|
||||||
public class AiControllerComponent : Component, IMoverComponent
|
public class AiControllerComponent : Component, IMoverComponent
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
|
||||||
|
|
||||||
private string? _logicName;
|
|
||||||
private float _visionRadius;
|
private float _visionRadius;
|
||||||
|
|
||||||
public override string Name => "AiController";
|
public override string Name => "AiController";
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public string? LogicName
|
|
||||||
{
|
|
||||||
get => _logicName;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_logicName = value;
|
|
||||||
Processor = null!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UtilityAi? Processor { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public string? StartingGearPrototype { get; set; }
|
public string? StartingGearPrototype { get; set; }
|
||||||
|
|
||||||
@@ -55,8 +38,6 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
|
|
||||||
// This component requires a physics component.
|
// This component requires a physics component.
|
||||||
Owner.EnsureComponent<PhysicsComponent>();
|
Owner.EnsureComponent<PhysicsComponent>();
|
||||||
|
|
||||||
EntitySystem.Get<AiSystem>().ProcessorInitialize(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Startup()
|
protected override void Startup()
|
||||||
@@ -65,10 +46,12 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
|
|
||||||
if (StartingGearPrototype != null)
|
if (StartingGearPrototype != null)
|
||||||
{
|
{
|
||||||
var startingGear = _prototypeManager.Index<StartingGearPrototype>(StartingGearPrototype);
|
var gameTicker = IoCManager.Resolve<IGameTicker>();
|
||||||
_gameTicker.EquipStartingGear(Owner, startingGear, null);
|
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
}
|
|
||||||
|
|
||||||
|
var startingGear = protoManager.Index<StartingGearPrototype>(StartingGearPrototype);
|
||||||
|
gameTicker.EquipStartingGear(Owner, startingGear, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -76,7 +59,6 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
serializer.DataField(ref _logicName, "logic", null);
|
|
||||||
serializer.DataReadWriteFunction(
|
serializer.DataReadWriteFunction(
|
||||||
"startingGear",
|
"startingGear",
|
||||||
null,
|
null,
|
||||||
@@ -85,12 +67,6 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
serializer.DataField(ref _visionRadius, "vision", 8.0f);
|
serializer.DataField(ref _visionRadius, "vision", 8.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Shutdown()
|
|
||||||
{
|
|
||||||
base.Shutdown();
|
|
||||||
Processor?.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Movement speed (m/s) that the entity walks, after modifiers
|
/// Movement speed (m/s) that the entity walks, after modifiers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -155,5 +131,7 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
|
|
||||||
public void SetVelocityDirection(Direction direction, ushort subTick, bool enabled) { }
|
public void SetVelocityDirection(Direction direction, ushort subTick, bool enabled) { }
|
||||||
public void SetSprinting(ushort subTick, bool walking) { }
|
public void SetSprinting(ushort subTick, bool walking) { }
|
||||||
|
|
||||||
|
public virtual void Update(float frameTime) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.Utility.AiLogic;
|
using Content.Server.AI.Utility.AiLogic;
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
using Content.Shared;
|
using Content.Shared;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.AI;
|
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Reflection;
|
using Robust.Shared.Reflection;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI
|
namespace Content.Server.GameObjects.EntitySystems.AI
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles NPCs running every tick.
|
||||||
|
/// </summary>
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
internal class AiSystem : EntitySystem
|
internal class AiSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||||
[Dependency] private readonly IDynamicTypeFactory _typeFactory = default!;
|
|
||||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
|
||||||
|
|
||||||
private readonly Dictionary<string, Type> _processorTypes = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary.
|
/// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly HashSet<AiLogicProcessor> _awakeAi = new();
|
private readonly HashSet<AiControllerComponent> _awakeAi = new();
|
||||||
|
|
||||||
// To avoid modifying awakeAi while iterating over it.
|
// To avoid modifying awakeAi while iterating over it.
|
||||||
private readonly List<SleepAiMessage> _queuedSleepMessages = new();
|
private readonly List<SleepAiMessage> _queuedSleepMessages = new();
|
||||||
|
|
||||||
private readonly List<MobStateChangedMessage> _queuedMobStateMessages = new();
|
private readonly List<MobStateChangedMessage> _queuedMobStateMessages = new();
|
||||||
|
|
||||||
public bool IsAwake(AiLogicProcessor processor) => _awakeAi.Contains(processor);
|
public bool IsAwake(AiControllerComponent npc) => _awakeAi.Contains(npc);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -43,15 +43,6 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<SleepAiMessage>(HandleAiSleep);
|
SubscribeLocalEvent<SleepAiMessage>(HandleAiSleep);
|
||||||
SubscribeLocalEvent<MobStateChangedMessage>(MobStateChanged);
|
SubscribeLocalEvent<MobStateChangedMessage>(MobStateChanged);
|
||||||
|
|
||||||
var processors = _reflectionManager.GetAllChildren<UtilityAi>();
|
|
||||||
foreach (var processor in processors)
|
|
||||||
{
|
|
||||||
var att = (AiLogicProcessorAttribute) Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute))!;
|
|
||||||
// Tests should pick this up
|
|
||||||
DebugTools.AssertNotNull(att);
|
|
||||||
_processorTypes.Add(att.SerializeName, processor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -63,13 +54,14 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
|||||||
|
|
||||||
foreach (var message in _queuedMobStateMessages)
|
foreach (var message in _queuedMobStateMessages)
|
||||||
{
|
{
|
||||||
|
// TODO: Need to generecise this but that will be part of a larger cleanup later anyway.
|
||||||
if (message.Entity.Deleted ||
|
if (message.Entity.Deleted ||
|
||||||
!message.Entity.TryGetComponent(out AiControllerComponent? controller))
|
!message.Entity.TryGetComponent(out UtilityAi? controller))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.Processor?.MobStateChanged(message);
|
controller.MobStateChanged(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_queuedMobStateMessages.Clear();
|
_queuedMobStateMessages.Clear();
|
||||||
@@ -79,14 +71,14 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
|||||||
switch (message.Sleep)
|
switch (message.Sleep)
|
||||||
{
|
{
|
||||||
case true:
|
case true:
|
||||||
if (_awakeAi.Count == cvarMaxUpdates && _awakeAi.Contains(message.Processor))
|
if (_awakeAi.Count == cvarMaxUpdates && _awakeAi.Contains(message.Component))
|
||||||
{
|
{
|
||||||
Logger.Warning($"Under AI limit again: {_awakeAi.Count - 1} / {cvarMaxUpdates}");
|
Logger.Warning($"Under AI limit again: {_awakeAi.Count - 1} / {cvarMaxUpdates}");
|
||||||
}
|
}
|
||||||
_awakeAi.Remove(message.Processor);
|
_awakeAi.Remove(message.Component);
|
||||||
break;
|
break;
|
||||||
case false:
|
case false:
|
||||||
_awakeAi.Add(message.Processor);
|
_awakeAi.Add(message.Component);
|
||||||
|
|
||||||
if (_awakeAi.Count > cvarMaxUpdates)
|
if (_awakeAi.Count > cvarMaxUpdates)
|
||||||
{
|
{
|
||||||
@@ -97,16 +89,17 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
|||||||
}
|
}
|
||||||
|
|
||||||
_queuedSleepMessages.Clear();
|
_queuedSleepMessages.Clear();
|
||||||
var toRemove = new List<AiLogicProcessor>();
|
var toRemove = new List<AiControllerComponent>();
|
||||||
var maxUpdates = Math.Min(_awakeAi.Count, cvarMaxUpdates);
|
var maxUpdates = Math.Min(_awakeAi.Count, cvarMaxUpdates);
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
||||||
foreach (var processor in _awakeAi)
|
foreach (var npc in _awakeAi)
|
||||||
{
|
{
|
||||||
if (processor.SelfEntity.Deleted ||
|
if (npc.Paused) continue;
|
||||||
!processor.SelfEntity.HasComponent<AiControllerComponent>())
|
|
||||||
|
if (npc.Deleted)
|
||||||
{
|
{
|
||||||
toRemove.Add(processor);
|
toRemove.Add(npc);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +108,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processor.Update(frameTime);
|
npc.Update(frameTime);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,32 +132,5 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
|||||||
|
|
||||||
_queuedMobStateMessages.Add(message);
|
_queuedMobStateMessages.Add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Will start up the controller's processor if not already done so.
|
|
||||||
/// Also add them to the awakeAi for updates.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="controller"></param>
|
|
||||||
public void ProcessorInitialize(AiControllerComponent controller)
|
|
||||||
{
|
|
||||||
if (controller.Processor != null || controller.LogicName == null) return;
|
|
||||||
controller.Processor = CreateProcessor(controller.LogicName);
|
|
||||||
controller.Processor.SelfEntity = controller.Owner;
|
|
||||||
controller.Processor.Setup();
|
|
||||||
_awakeAi.Add(controller.Processor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UtilityAi CreateProcessor(string name)
|
|
||||||
{
|
|
||||||
if (_processorTypes.TryGetValue(name, out var type))
|
|
||||||
{
|
|
||||||
return (UtilityAi)_typeFactory.CreateInstance(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// processor needs to inherit AiLogicProcessor, and needs an AiLogicProcessorAttribute to define the YAML name
|
|
||||||
throw new ArgumentException($"Processor type {name} could not be found.", nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProcessorTypeExists(string name) => _processorTypes.ContainsKey(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Access;
|
using Content.Server.GameObjects.Components.Access;
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -26,7 +27,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
|
|||||||
public static ReachableArgs GetArgs(IEntity entity)
|
public static ReachableArgs GetArgs(IEntity entity)
|
||||||
{
|
{
|
||||||
var collisionMask = 0;
|
var collisionMask = 0;
|
||||||
if (entity.TryGetComponent(out IPhysicsComponent physics))
|
if (entity.TryGetComponent(out IPhysicsComponent? physics))
|
||||||
{
|
{
|
||||||
collisionMask = physics.CollisionMask;
|
collisionMask = physics.CollisionMask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Robust.Server.AI;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI
|
namespace Content.Server.GameObjects.EntitySystems.AI
|
||||||
@@ -13,11 +13,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
|||||||
/// Sleep or awake.
|
/// Sleep or awake.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Sleep { get; }
|
public bool Sleep { get; }
|
||||||
public AiLogicProcessor Processor { get; }
|
public AiControllerComponent Component { get; }
|
||||||
|
|
||||||
public SleepAiMessage(AiLogicProcessor processor, bool sleep)
|
public SleepAiMessage(AiControllerComponent component, bool sleep)
|
||||||
{
|
{
|
||||||
Processor = processor;
|
Component = component;
|
||||||
Sleep = sleep;
|
Sleep = sleep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.AI.Utility;
|
||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.Chat;
|
using Content.Server.Chat;
|
||||||
@@ -60,6 +61,7 @@ namespace Content.Server
|
|||||||
IoCManager.Register<EuiManager, EuiManager>();
|
IoCManager.Register<EuiManager, EuiManager>();
|
||||||
IoCManager.Register<IHolidayManager, HolidayManager>();
|
IoCManager.Register<IHolidayManager, HolidayManager>();
|
||||||
IoCManager.Register<IVoteManager, VoteManager>();
|
IoCManager.Register<IVoteManager, VoteManager>();
|
||||||
|
IoCManager.Register<INpcBehaviorManager, NpcBehaviorManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
- type: AiController
|
- type: UtilityAI
|
||||||
logic: Xeno
|
behaviorSets:
|
||||||
|
- Idle
|
||||||
|
- UnarmedAttackHostiles
|
||||||
- type: AiFactionTag
|
- type: AiFactionTag
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
|
|||||||
@@ -7,5 +7,6 @@
|
|||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
- type: AiController
|
- type: UtilityAI
|
||||||
logic: PathingDummy
|
behaviorSets:
|
||||||
|
- PathingDummy
|
||||||
|
|||||||
@@ -7,8 +7,12 @@
|
|||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
- type: AiController
|
- type: UtilityAI
|
||||||
logic: Civilian
|
behaviorSets:
|
||||||
|
- Clothing
|
||||||
|
- Hunger
|
||||||
|
- Thirst
|
||||||
|
- Idle
|
||||||
startingGear: AssistantGear
|
startingGear: AssistantGear
|
||||||
- type: AiFactionTag
|
- type: AiFactionTag
|
||||||
factions:
|
factions:
|
||||||
@@ -22,5 +26,10 @@
|
|||||||
description: Yarr!
|
description: Yarr!
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
- type: AiController
|
- type: UtilityAI
|
||||||
logic: Spirate
|
behaviorSets:
|
||||||
|
- Clothing
|
||||||
|
- Hunger
|
||||||
|
- Thirst
|
||||||
|
- Idle
|
||||||
|
- Spirate
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
- type: AiController
|
- type: UtilityAI
|
||||||
logic: Mimic
|
behaviorSets:
|
||||||
|
- UnarmedAttackHostiles
|
||||||
- type: AiFactionTag
|
- type: AiFactionTag
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
- type: AiController
|
- type: UtilityAI
|
||||||
logic: Civilian
|
behaviorSets:
|
||||||
|
- Clothing
|
||||||
|
- Hunger
|
||||||
|
- Thirst
|
||||||
|
- Idle
|
||||||
- type: AiFactionTag
|
- type: AiFactionTag
|
||||||
factions:
|
factions:
|
||||||
- SimpleNeutral
|
- SimpleNeutral
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
- type: AiController
|
- type: UtilityAI
|
||||||
logic: Xeno
|
behaviorSets:
|
||||||
|
- Idle
|
||||||
|
- UnarmedAttackHostiles
|
||||||
- type: AiFactionTag
|
- type: AiFactionTag
|
||||||
factions:
|
factions:
|
||||||
- Xeno
|
- Xeno
|
||||||
|
|||||||
46
Resources/Prototypes/NPCs/behavior_sets.yml
Normal file
46
Resources/Prototypes/NPCs/behavior_sets.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
- type: behaviorSet
|
||||||
|
id: Clothing
|
||||||
|
actions:
|
||||||
|
- EquipAnyHeadExp
|
||||||
|
- EquipAnyOuterClothingExp
|
||||||
|
- EquipAnyGlovesExp
|
||||||
|
- EquipAnyShoesExp
|
||||||
|
- PickUpAnyNearbyHeadExp
|
||||||
|
- PickUpAnyNearbyOuterClothingExp
|
||||||
|
- PickUpAnyNearbyGlovesExp
|
||||||
|
- PickUpAnyNearbyShoesExp
|
||||||
|
|
||||||
|
- type: behaviorSet
|
||||||
|
id: Hunger
|
||||||
|
actions:
|
||||||
|
- PickUpNearbyFoodExp
|
||||||
|
- UseFoodInInventoryExp
|
||||||
|
|
||||||
|
- type: behaviorSet
|
||||||
|
id: Idle
|
||||||
|
actions:
|
||||||
|
- CloseLastEntityStorage
|
||||||
|
- WanderAndWait
|
||||||
|
|
||||||
|
- type: behaviorSet
|
||||||
|
id: PathingDummy
|
||||||
|
actions:
|
||||||
|
- MoveRightAndLeftTen
|
||||||
|
|
||||||
|
- type: behaviorSet
|
||||||
|
id: Spirate
|
||||||
|
actions:
|
||||||
|
- EquipMeleeExp
|
||||||
|
- PickUpMeleeWeaponExp
|
||||||
|
- MeleeAttackNearbyExp
|
||||||
|
|
||||||
|
- type: behaviorSet
|
||||||
|
id: Thirst
|
||||||
|
actions:
|
||||||
|
- PickUpNearbyDrinkExp
|
||||||
|
- UseDrinkInInventoryExp
|
||||||
|
|
||||||
|
- type: behaviorSet
|
||||||
|
id: UnarmedAttackHostiles
|
||||||
|
actions:
|
||||||
|
- UnarmedAttackNearbyHostilesExp
|
||||||
Submodule RobustToolbox updated: e2a4dcdff1...eb3a815d48
Reference in New Issue
Block a user