Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Visual AI (β) #190

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MSBuild/OpenNefia.Engine.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<LangVersion>10</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CodeAnalysisRuleSet>..\MSBuild\OpenNefia.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRuleSet>..\MSBuild\OpenNefia.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<Import Project="OpenNefia.Engine.Version.props" />
</Project>
2 changes: 2 additions & 0 deletions MSBuild/OpenNefia.Properties.targets
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@
<Python>python3</Python>
<Python Condition="'$(ActualOS)' == 'Windows'">py -3</Python>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public sealed class QuickStartScenariosSystem : EntitySystem
[Dependency] private readonly IWeatherSystem _weathers = default!;
[Dependency] private readonly ILevelSystem _levels = default!;
[Dependency] private readonly ISkillsSystem _skills = default!;
[Dependency] private readonly ISpellSystem _spells = default!;

[EngineVariable("LecchoTorte.QuickstartPlayer")]
private QuickstartChara _quickstartPlayer { get; } = new();
Expand Down Expand Up @@ -180,7 +181,7 @@ private void MapInitializer(ResourcePath mapFile, P_ScenarioOnGameStartEvent ev,
EnsureComp<FameComponent>(player).Fame.Base = 50000;

foreach (var spell in _protos.EnumeratePrototypes<SpellPrototype>())
_skills.GainSkill(player, spell.SkillID, new LevelAndPotential() { Level = new(50) });
_spells.GainSpell(player, spell.GetStrongID(), 2000, new LevelAndPotential() { Level = new(50) });
foreach (var action in _protos.EnumeratePrototypes<ActionPrototype>())
_skills.GainSkill(player, action.SkillID);

Expand Down
296 changes: 296 additions & 0 deletions Mods/OpenNefia.VisualAI/Block/IVisualAIAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
using OpenNefia.Core.GameObjects;
using OpenNefia.Core.IoC;
using OpenNefia.Core.Serialization.Manager.Attributes;
using OpenNefia.VisualAI.Engine;
using OpenNefia.Core.Maps;
using OpenNefia.Core.Directions;
using OpenNefia.Core.Maths;
using OpenNefia.Content.VanillaAI;
using Love;
using OpenNefia.Core.Utility;
using static OpenNefia.Content.Prototypes.Protos;
using OpenNefia.Content.Combat;
using OpenNefia.Core.Prototypes;
using OpenNefia.Content.Spells;
using OpenNefia.Content.Actions;
using OpenNefia.Content.Pickable;
using OpenNefia.Content.Inventory;
using OpenNefia.Content.Prototypes;

namespace OpenNefia.VisualAI.Block
{
[ImplicitDataDefinitionForInheritors]
public interface IVisualAIAction
{
bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target);
}


public abstract class BaseAction : IVisualAIAction
{
[Dependency] protected readonly IEntityManager EntityManager = default!;

public abstract bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target);
}

public sealed class DoNothingAction : BaseAction
{
public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
return true;
}
}

public sealed class WanderAction : BaseAction
{
[Dependency] private readonly IMapManager _mapMan = default!;
[Dependency] private readonly IVanillaAISystem _vai = default!;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
_vai.Wander(state.AIEntity);
return true;
}
}

public sealed class MoveWithinDistanceAction : BaseAction
{
[Dependency] private readonly IMapManager _mapMan = default!;
[Dependency] private readonly IVanillaAISystem _vai = default!;

[DataField]
[VisualAIVariable(minValue: 0)]
public int Threshold { get; set; } = 0;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
var spatial = EntityManager.GetComponent<SpatialComponent>(state.AIEntity);
var map = state.Map;

var minDist = Threshold;
if (!map.CanAccess(target.Coordinates))
minDist = int.Max(Threshold, 1);

if (!spatial.MapPosition.TryDistanceFractional(target.Coordinates, out var dist)
|| dist <= minDist)
{
return false;
}

if (EntityManager.IsAlive(target.Entity))
{
if (state.AIEntity == target.Entity.Value)
return false;
return _vai.MoveTowardsTarget(state.AIEntity, target.Entity);
}
else
{
return _vai.StayNearPosition(state.AIEntity, target.Coordinates);
}
}
}

public sealed class RetreatFurthestAction : BaseAction
{
[Dependency] private readonly IVanillaAISystem _vai = default!;
[Dependency] private readonly IMoveableSystem _moveables = default!;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
var spatial = EntityManager.GetComponent<SpatialComponent>(state.AIEntity);

if (EntityManager.IsAlive(target.Entity))
{
if (state.AIEntity == target.Entity.Value)
return false;

return _vai.MoveTowardsTarget(state.AIEntity, target.Entity, retreat: true);
}
else
{
if (!DirectionUtility.TryDirectionTowards(spatial.MapPosition, target.Coordinates, out var dir))
return false;

var newPos = new MapCoordinates(spatial.MapPosition.MapId, spatial.MapPosition.Position - dir.ToIntVec());

var map = state.Map;

if (map.CanAccess(newPos))
{
_moveables.MoveEntity(state.AIEntity, newPos);
return true;
}

return false;
}
}
}

public sealed class RetreatUntilDistanceAction : BaseAction
{
[Dependency] private readonly IVanillaAISystem _vai = default!;
[Dependency] private readonly IMoveableSystem _moveables = default!;

[DataField]
[VisualAIVariable(minValue: 0)]
public int Threshold { get; set; } = 3;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
var spatial = EntityManager.GetComponent<SpatialComponent>(state.AIEntity);
var map = state.Map;

if (!spatial.MapPosition.TryDistanceFractional(target.Coordinates, out var dist)
|| dist >= Threshold)
{
return false;
}

if (EntityManager.IsAlive(target.Entity))
{
if (state.AIEntity == target.Entity.Value)
return false;

return _vai.MoveTowardsTarget(state.AIEntity, target.Entity, retreat: true);
}
else
{
if (!DirectionUtility.TryDirectionTowards(spatial.MapPosition, target.Coordinates, out var dir))
return false;

var newPos = new MapCoordinates(spatial.MapPosition.MapId, spatial.MapPosition.Position - dir.ToIntVec());

if (map.CanAccess(newPos))
{
_moveables.MoveEntity(state.AIEntity, newPos);
return true;
}

return false;
}
}
}

public sealed class MeleeAttackAction : BaseAction
{
[Dependency] private readonly ICombatSystem _combat = default!;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
if (!EntityManager.IsAlive(target.Entity))
return false;

var spatial = EntityManager.GetComponent<SpatialComponent>(state.AIEntity);

if (!spatial.MapPosition.TryDistanceTiled(target.Coordinates, out var dist)
|| dist > 1)
{
return false;
}

return _combat.MeleeAttack(state.AIEntity, target.Entity.Value) == TurnResult.Succeeded;
}
}

public sealed class RangedAttackAction : BaseAction
{
[Dependency] private readonly ICombatSystem _combat = default!;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
if (!EntityManager.IsAlive(target.Entity))
return false;

if (!_combat.TryGetRangedWeaponAndAmmo(state.AIEntity, out var rangedWeapon, out _))
return false;

var spatial = EntityManager.GetComponent<SpatialComponent>(state.AIEntity);

if (!spatial.MapPosition.TryDistanceTiled(target.Coordinates, out var dist)
|| dist >= VanillaAISystem.AIRangedAttackThreshold)
{
return false;
}

return _combat.RangedAttack(state.AIEntity, target.Entity.Value, rangedWeapon.Value) == TurnResult.Succeeded;
}
}

public sealed class CastSpellAction : BaseAction
{
[Dependency] private readonly IVanillaAISystem _vai = default!;
[Dependency] private readonly ISpellSystem _spells = default!;

[DataField]
[VisualAIVariable]
public PrototypeId<SpellPrototype> SpellID { get; set; } = Protos.Spell.MagicDart;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
if (!EntityManager.IsAlive(target.Entity))
return false;

if (!_spells.HasSpell(state.AIEntity, SpellID))
return false;

_vai.SetTarget(state.AIEntity, target.Entity.Value);
return _spells.Cast(state.AIEntity, SpellID, target.Entity, alwaysUseMP: true) == TurnResult.Succeeded;
}
}

public sealed class InvokeActionAction : BaseAction
{
[Dependency] private readonly IVanillaAISystem _vai = default!;
[Dependency] private readonly ICombatSystem _combat = default!;
[Dependency] private readonly IActionSystem _actions = default!;

[DataField]
[VisualAIVariable]
public PrototypeId<ActionPrototype> ActionID { get; set; } = Protos.Action.Curse;

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
if (!EntityManager.IsAlive(target.Entity))
return false;

if (!_actions.HasAction(state.AIEntity, ActionID))
return false;

_vai.SetTarget(state.AIEntity, target.Entity.Value);
return _actions.Invoke(state.AIEntity, ActionID, target.Entity) == TurnResult.Succeeded;
}
}

public abstract class BaseVerbAction : BaseAction
{
[Dependency] private readonly IVerbSystem _verbs = default!;

protected abstract string VerbType { get; }

public override bool Apply(VisualAIState state, VisualAIBlock block, IVisualAITargetValue target)
{
if (!EntityManager.IsAlive(target.Entity))
return false;

var spatialSource = EntityManager.GetComponent<SpatialComponent>(state.AIEntity);
if (spatialSource.MapPosition != target.Coordinates)
return false;

var result = TurnResult.Failed;
if (_verbs.TryGetVerb(state.AIEntity, target.Entity.Value, VerbType, out var verb))
result = verb.Act();

return result == TurnResult.Succeeded;
}
}

public sealed class PickUpAction : BaseVerbAction
{
protected override string VerbType => PickableSystem.VerbTypePickUp;
}

public sealed class DropAction : BaseVerbAction
{
protected override string VerbType => PickableSystem.VerbTypeDrop;
}
}
Loading
Loading