Add AI factions (#1807)
* Add NPC faction tags Some stuff isn't easy to represent by the existence of components so tags are intended to provide that functionality for AI usage. I was 50/50 on having all tags in the 1 component or splitting it into 2. I'm leaning towards 2. This would be for stuff like say "CanMimic" so the mimic knows it's allowed to look like a specific prototype, or something like "smg" on a gun so it can say smg-specific barks for instance (as currently smgs and pistols look the same from a component perspective). This also means combat behaviors aren't hardcoded per faction, plus it makes it easy to update faction relations during events. * Factions command Update faction relationships via commands. * Remove command TODO * Woops Forgot to commit these items * Serializer writing and parsing * linq me up fam Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -162,6 +162,7 @@
|
||||
"GasVapor",
|
||||
"MobStateManager",
|
||||
"Metabolism",
|
||||
"AiFactionTag",
|
||||
"PressureProtection",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Content.Server.AI.Utility.BehaviorSets
|
||||
// TODO: Ideally long-term we should just store the weapons in backpack
|
||||
new EquipMeleeExp(),
|
||||
new PickUpMeleeWeaponExp(),
|
||||
new MeleeAttackNearbyPlayerExp(),
|
||||
new MeleeAttackNearbyExp(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@ using Content.Server.AI.Utility.Actions;
|
||||
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.GameObjects.Components.AI;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Server.GameObjects.EntitySystems.AI;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
||||
{
|
||||
public sealed class MeleeAttackNearbyPlayerExp : ExpandableUtilityAction
|
||||
public sealed class MeleeAttackNearbyExp : ExpandableUtilityAction
|
||||
{
|
||||
public override float Bonus => UtilityAction.CombatBonus;
|
||||
|
||||
@@ -37,13 +39,10 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IDamageableComponent),
|
||||
controller.VisionRadius))
|
||||
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
|
||||
.GetNearbyHostiles(owner, controller.VisionRadius))
|
||||
{
|
||||
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
||||
{
|
||||
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
|
||||
}
|
||||
yield return new MeleeWeaponAttackEntity(owner, target, Bonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utility.Actions;
|
||||
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Mobs;
|
||||
|
||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
||||
{
|
||||
public sealed class MeleeAttackNearbySpeciesExp : ExpandableUtilityAction
|
||||
{
|
||||
public override float Bonus => UtilityAction.CombatBonus;
|
||||
|
||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
||||
{
|
||||
var owner = context.GetState<SelfState>().GetValue();
|
||||
|
||||
foreach (var entity in context.GetState<NearbyBodiesState>().GetValue())
|
||||
{
|
||||
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@ using Content.Server.AI.Utils;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.EntitySystems.AI;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
||||
@@ -37,13 +39,10 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IBodyManagerComponent),
|
||||
controller.VisionRadius))
|
||||
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
|
||||
.GetNearbyHostiles(owner, controller.VisionRadius))
|
||||
{
|
||||
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
||||
{
|
||||
yield return new UnarmedAttackEntity(owner, entity, Bonus);
|
||||
}
|
||||
yield return new UnarmedAttackEntity(owner, target, Bonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.AI
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AiFactionTagComponent : Component
|
||||
{
|
||||
public override string Name => "AiFactionTag";
|
||||
|
||||
public Faction Factions { get; private set; } = Faction.None;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"factions",
|
||||
new List<Faction>(),
|
||||
factions => factions.ForEach(faction => Factions |= faction),
|
||||
() =>
|
||||
{
|
||||
var writeFactions = new List<Faction>();
|
||||
foreach (Faction fac in Enum.GetValues(typeof(Faction)))
|
||||
{
|
||||
if ((Factions & fac) != 0)
|
||||
{
|
||||
writeFactions.Add(fac);
|
||||
}
|
||||
}
|
||||
|
||||
return writeFactions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Faction
|
||||
{
|
||||
None = 0,
|
||||
NanoTransen = 1 << 0,
|
||||
SimpleHostile = 1 << 1,
|
||||
SimpleNeutral = 1 << 2,
|
||||
Syndicate = 1 << 3,
|
||||
Xeno = 1 << 4,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Content.Server.GameObjects.Components.AI;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// Outlines faction relationships with each other for AI.
|
||||
/// </summary>
|
||||
public sealed class AiFactionTagSystem : EntitySystem
|
||||
{
|
||||
/*
|
||||
* Currently factions are implicitly friendly if they are not hostile.
|
||||
* This may change where specified friendly factions are listed. (e.g. to get number of friendlies in area).
|
||||
*/
|
||||
|
||||
public Faction GetHostileFactions(Faction faction) => _hostileFactions.TryGetValue(faction, out var hostiles) ? hostiles : Faction.None;
|
||||
|
||||
private Dictionary<Faction, Faction> _hostileFactions = new Dictionary<Faction, Faction>
|
||||
{
|
||||
{Faction.NanoTransen,
|
||||
Faction.SimpleHostile | Faction.Syndicate | Faction.Xeno},
|
||||
{Faction.SimpleHostile,
|
||||
Faction.NanoTransen | Faction.Syndicate
|
||||
},
|
||||
// What makes a man turn neutral?
|
||||
{Faction.SimpleNeutral,
|
||||
Faction.None
|
||||
},
|
||||
{Faction.Syndicate,
|
||||
Faction.NanoTransen | Faction.SimpleHostile | Faction.Xeno},
|
||||
{Faction.Xeno,
|
||||
Faction.NanoTransen | Faction.Syndicate},
|
||||
};
|
||||
|
||||
public Faction GetFactions(IEntity entity) =>
|
||||
entity.TryGetComponent(out AiFactionTagComponent factionTags)
|
||||
? factionTags.Factions
|
||||
: Faction.None;
|
||||
|
||||
public IEnumerable<IEntity> GetNearbyHostiles(IEntity entity, float range)
|
||||
{
|
||||
var ourFaction = GetFactions(entity);
|
||||
var hostile = GetHostileFactions(ourFaction);
|
||||
if (ourFaction == Faction.None || hostile == Faction.None)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var component in ComponentManager.EntityQuery<AiFactionTagComponent>())
|
||||
{
|
||||
if ((component.Factions & hostile) == 0)
|
||||
continue;
|
||||
if (component.Owner.Transform.MapID != entity.Transform.MapID)
|
||||
continue;
|
||||
if (!component.Owner.Transform.MapPosition.InRange(entity.Transform.MapPosition, range))
|
||||
continue;
|
||||
|
||||
yield return component.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeFriendly(Faction source, Faction target)
|
||||
{
|
||||
if (!_hostileFactions.TryGetValue(source, out var hostileFactions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hostileFactions &= ~target;
|
||||
_hostileFactions[source] = hostileFactions;
|
||||
}
|
||||
|
||||
public void MakeHostile(Faction source, Faction target)
|
||||
{
|
||||
if (!_hostileFactions.TryGetValue(source, out var hostileFactions))
|
||||
{
|
||||
_hostileFactions[source] = target;
|
||||
return;
|
||||
}
|
||||
|
||||
hostileFactions |= target;
|
||||
_hostileFactions[source] = hostileFactions;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FactionCommand : IClientCommand
|
||||
{
|
||||
public string Command => "factions";
|
||||
public string Description => "Update / list factional relationships for NPCs.";
|
||||
public string Help => "faction <source> <friendly/hostile> target\n" +
|
||||
"faction <source> list: hostile factions";
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
foreach (Faction value in Enum.GetValues(typeof(Faction)))
|
||||
{
|
||||
if (value == Faction.None)
|
||||
continue;
|
||||
result.Append(value + "\n");
|
||||
}
|
||||
|
||||
shell.SendText(player, result.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.SendText(player, Loc.GetString("Need more args"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(args[0], true, out Faction faction))
|
||||
{
|
||||
shell.SendText(player, Loc.GetString("Invalid faction"));
|
||||
return;
|
||||
}
|
||||
|
||||
Faction targetFaction;
|
||||
|
||||
switch (args[1])
|
||||
{
|
||||
case "friendly":
|
||||
if (args.Length < 3)
|
||||
{
|
||||
shell.SendText(player, Loc.GetString("Need to supply a target faction"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(args[2], true, out targetFaction))
|
||||
{
|
||||
shell.SendText(player, Loc.GetString("Invalid target faction"));
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<AiFactionTagSystem>().MakeFriendly(faction, targetFaction);
|
||||
shell.SendText(player, Loc.GetString("Command successful"));
|
||||
break;
|
||||
case "hostile":
|
||||
if (args.Length < 3)
|
||||
{
|
||||
shell.SendText(player, Loc.GetString("Need to supply a target faction"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(args[2], true, out targetFaction))
|
||||
{
|
||||
shell.SendText(player, Loc.GetString("Invalid target faction"));
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<AiFactionTagSystem>().MakeHostile(faction, targetFaction);
|
||||
shell.SendText(player, Loc.GetString("Command successful"));
|
||||
break;
|
||||
case "list":
|
||||
shell.SendText(player, EntitySystem.Get<AiFactionTagSystem>().GetHostileFactions(faction).ToString());
|
||||
break;
|
||||
default:
|
||||
shell.SendText(player, Loc.GetString("Unknown faction arg"));
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
- loc
|
||||
- hostlogin
|
||||
- events
|
||||
- factions
|
||||
|
||||
- Index: 100
|
||||
Name: Administrator
|
||||
@@ -97,6 +98,7 @@
|
||||
- events
|
||||
- destroymechanism
|
||||
- readyall
|
||||
- factions
|
||||
CanViewVar: true
|
||||
CanAdminPlace: true
|
||||
|
||||
@@ -188,6 +190,7 @@
|
||||
- events
|
||||
- destroymechanism
|
||||
- readyall
|
||||
- factions
|
||||
CanViewVar: true
|
||||
CanAdminPlace: true
|
||||
CanScript: true
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
components:
|
||||
- type: AiController
|
||||
logic: Civilian
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- SimpleNeutral
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 4
|
||||
baseSprintSpeed : 4
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
components:
|
||||
- type: AiController
|
||||
logic: Xeno
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- SimpleHostile
|
||||
- type: MovementSpeedModifier
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
components:
|
||||
- type: AiController
|
||||
logic: Civilian
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- NanoTransen
|
||||
|
||||
|
||||
- type: entity
|
||||
save: false
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
components:
|
||||
- type: AiController
|
||||
logic: Mimic
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- SimpleHostile
|
||||
- type: Hands
|
||||
- type: MovementSpeedModifier
|
||||
- type: InteractionOutline
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
components:
|
||||
- type: AiController
|
||||
logic: Civilian
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- SimpleNeutral
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 5
|
||||
baseSprintSpeed : 5
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
components:
|
||||
- type: AiController
|
||||
logic: Xeno
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- Xeno
|
||||
- type: Hands
|
||||
- type: MovementSpeedModifier
|
||||
- type: InteractionOutline
|
||||
|
||||
@@ -17,3 +17,6 @@
|
||||
- type: CameraRecoil
|
||||
- type: Examiner
|
||||
- type: HumanInventoryController
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- NanoTransen
|
||||
|
||||
Reference in New Issue
Block a user