I want you to act as a Senior C# developer.
Design a Systems where: NPC can interact with each other and world using Fluid HTN, Quests Sys where NPC can create, take and complete quests, Advanced Combat Sys for NPC, Inventory and Item Sys for NPC where they can pick up items, drop items, equip items, trade items, Dialogue Sys where NPC can talk with each other and learn information about the world. Write only the name of the class, what it does and how it interacts with other classes. Try to use SOLID principles
Fluid HTN is a forward decomposition planner that uses partial planning and domain splicing. It includes a Domain Builder for easy design of HTN domains, and supports run-time splicing through domain slots. Replanning only occurs when plans complete/fail or when the world state changes, with early rejection of replanning that is unlikely to succeed. It can be easily extended using the extension library, and uses a Factory interface for memory management. Decomposition logging is also available for debugging purposes.
Compound Tasks
Compound tasks are the basis of HTN's hierarchical structure. They consist of high-level tasks that can be accomplished in multiple ways. There are two types: Selectors and Sequencers. Selectors break down a single sub-task, while Sequencers break down all sub-tasks for success. Additional task types like RandomSelect and UtilitySelect can be added. These tasks are broken down until only Primitive Tasks remain, which make up the final plan. Compound tasks include subtasks and conditions.
Primitive Tasks
Primitive tasks represent a single step that can be performed by our AI. A set of primitive tasks is the plan that we are ultimately getting out of the HTN. Primitive tasks are comprised of an operator, a set of effects, a set of conditions and a set of executing conditions.
Conditions
Conditions are boolean validators that can be used to validate the decomposition of a compound task, or the validity of a primitive task. Primitive Tasks also have Executing Conditions, which we validate before every update to the primary task's operator during execution of a plan.
Executing Conditions
Advanced: Executing Conditions are evaluated every planner tick and are useful when you need to re-evaluate the validity of your conditions after planning or setting a new task as the current task during execution. If the operator returns "continue" and the condition becomes invalid, the planner may not automatically know. Using Executing Conditions or having the logic in the Operator return "Failure" can both trigger a replan and ensure that the planner recognizes the task is invalid.
Operators
Operators are the logic operation a primitive task should perform during plan execution. Every time an operator updates, it returns a status whether it succeeded, failed or need to continue next tick.
Effects
Effects change the world state during planning and execution, but only apply to primitive tasks. There are three types: PlanOnly effects are temporary and removed before execution; PlanAndExecute effects are re-applied after successful execution; and Permanent effects are applied during planning and persist during execution. Permanent effects are useful when changing state only during planning or when making changes in runtime code.
Coding with Fluid HTN
To set up the blackboard used by the planner during planning, we create a WorldState enum and a Context class that extends BaseContext from FluidHTN. The WorldState enum contains the possible states that the planner can access, while the Context class contains the actual state values and methods for manipulating them. We also define specialized methods for manipulating the world state in the Context class.

We then create a DomainBuilder to define the HTN domain, which consists of a tree of selectors and sequences. Each node in the tree represents a task that the planner can perform, and its conditions and effects on the world state. We use the DomainBuilder to construct a domain by chaining together the appropriate methods.

To generate a plan, we instantiate a Planner and a Context, initialize the Context, and repeatedly call the planner's Tick method with the domain and context until the desired goal is achieved. In this example, the goal is to set the "Done" flag in the context to true. When the goal is achieved, we should see the console output "Get A", "Get B", "Get C", and "Done".
Partial planning
We can easily integrate the concept of partial planning into our domains. We call it a Pause Plan, and it must be set inside a sequence to be valid. It allows the planner to only plan up to a certain point, then continue from there once the partial plan has been completed.
.Sequence("A")
.Action("1")
//...
.End()
.PausePlan()
.Action("2")
//...
.End().End()
Sub-domains and domain splicing
We can define sub-domains and splice them together to form new domains, but they must share the same context type to be compatible. This can be quite handy for re-use of sub-domains and prevent a single domain definition from growing too large. Specially if we want to form recursive-style behavior this is needed.
var subDomain=new DomainBuilder<MyContext>("SubDomain")
.Select("B")
//...
.End()
.Build();
var myDomain=new DomainBuilder<MyContext>("MyDomain")
.Select("A")
//...
.End()
.Splice(subDomain)
.Select("C")
//...
.End().Build();
Sub-domains and slots
We can define slots in our domain definitions, and mark them with slot ids. This allow us to hook up sub-domains to these slots at run-time. This can be useful together with Smart Objects that extend the behavior of an agent when interacted with, for example.
var subDomain=new DomainBuilder<MyContext>("SubDomain")
.Select("B")
//...
.End()
.Build();
var myDomain=new DomainBuilder<MyContext>("MyDomain")
.Slot(1)
.Build();
myDomain.TrySetSlotDomain(1,subDomain);
//...
myDomain.ClearSlot(1);
Extending the Domain Builder
Fluid HTN makes it easy to extend the domain builder with specialized task types for a project's problem domain. Generic implementations of Condition, Operator and Effect are bundled with the library, allowing for lambda-styled domain definitions. Custom conditions, operators and effects can also be added to make domain definitions more readable and manageable.
var domain=new MyDomainBuilder("Trunk Thumper").Sequence("Attack enemy").IfEnemy().MoveTo(Location.Enemy, Speed.Sprint).SetLocation(Location.Enemy).SetIsTired().End().TrunkSlam().IfLocation(Location.Enemy).End().End().Sequence("Patrol next bridge").FindBridge().End().MoveTo(Location.Bridge, Speed.Walk).SetLocation(Location.Bridge).End().CheckBridge().IfLocation(Location.Bridge).SetBored().End().End().Build();
Let us look at how parts of this was made. First, we write our custom Domain Builder class. We instantiate a DefaultFactory into base, but we'll cover custom factory implementations later.
public class MyDomainBuilder : BaseDomainBuilder<MyDomainBuilder, MyContext>
{public MyDomainBuilder(string domainName):base(domainName,new DefaultFactory()){}}
Custom condition in domain builder
To add a custom condition, we need to override the ICondition interface.
public class IfEnemyCondition:ICondition{
public string Name{get;}="If Enemy";
public bool IsValid(IContext ctx){if(ctx is MyContext c){return c.HasState(WorldState.HasEnemy);}throw new Exception("Unexpected context type!");}}
Next, we can extend our MyDomainBuilder with a new function that expose this condition
public MyDomainBuilder IfEnemy(){var condition=new IfEnemyCondition();Pointer.AddCondition(condition);return this;}
Custom effect in domain builder
To add a custom effect, we need to override the IEffect interface.
public class SetLocationEffect:IEffect
{private Location _location;
public SetLocation(Location location)
{_location = location;}
public string Name{get;}="Set Location";
public EffectType Type{get;}=EffectType.PlanOnly;
public void Apply(IContext ctx){if (ctx is T c)c.SetState(WorldState.Location,_location);else throw new Exception("Unexpected context type!");}}
Next, we can extend our MyDomainBuilder with a new function that expose this effect
public MyDomainBuilder SetLocation(Location location)
{if(Pointer is IPrimitiveTask task)
{var effect = new SetLocationEffect(location);
task.AddEffect(effect);}
else throw new Exception("Tried to add an Effect, but the Pointer is not a Primitive Task!");return this;}
Custom operator in domain builder
To add a custom operator, we need to override the IOperator interface.
public class MoveToOperator:IOperator
{private Location _location;
private Speed _speed;
public MoveToOperator(Location location, Speed speed){_location = location;_speed = speed;}
public TaskStatus Update(IContext ctx)
{if(ctx is MyContext c){if(c.NavAgent.isStopped)return InitiateMovement(c);
return TickMovement(c);}
throw new Exception("Unexpected context type!");}
public void Stop(IContext ctx)
{if(ctx is MyContext c) {c.NavAgent.isStopped=true;return;}
throw new Exception("Unexpected context type!");}
private TaskStatus InitiateMovement(MyContext c)
{c.NavAgent.speed=_speed==Speed.Walk?WalkSpeed:RunSpeed;
switch(_location)
{case Location.Enemy:
if (ctx.NavAgent.SetDestination(ctx.BridgeLocation)){
ctx.NavAgent.isStopped = false;
return TaskStatus.Continue;}
else return TaskStatus.Failure;
case Location.Bridge:
//...}
return TaskStatus.Failure;}
private TaskStatus TickMovement(MyContext c)
{if(c.NavAgent.remainingDistance>c.NavAgent.stoppingDistance)
return TaskStatus.Continue;
c.NavAgent.isStopped=true;
return TaskStatus.Success;}}
Next, we can extend our MyDomainBuilder with a new function that expose this operator.
public MyDomainBuilder MoveTo(Location location,Speed speed)
{Action($"MoveTo({location},{speed})");
if(Pointer is IPrimitiveTask task)
{var op = new MoveToOperator(location, speed);
task.SetOperator(op);}
else throw new Exception("Tried to add an Operator, but the Pointer is not a Primitive Task!");
return this;}
Note that we both called Action(...), which sets the Pointer, and the SetOperator(...), but we didn't call End() to close the Pointer. This is so that we could be free to add Effects and Conditions to the action, but it means that the user must remember to call End() manually.
Custom selectors in domain builder
We're not limited to extend the domain builder with just conditions, effects and operators. We can also extend the capabilities of our selectors and sequences. Let's implement a Random Selector that will decompose into a random sub-task.
using FluidHTN.PrimitiveTasks;
namespace FluidHTN.Compounds
{public class RandomSelector : Selector{
protected Random _random = new Random();
DecompositionStatus OnDecompose(IContext ctx,int startIndex,out Queue<ITask> result)
{Plan.Clear();
var taskIndex=_random.Next(startIndex, Subtasks.Count);
var task=Subtasks[taskIndex];
return OnDecomposeTask(ctx,task,taskIndex,null,out result);}}}
We can now extend our MyDomainBuilder with this new type of selector.
public DB RandomSelect(string name){return CompoundTask<RandomSelector>(name);}
Tips for domain builder usage
We can use the domain builder to create complex sub-domains with auto-indentation, in addition to simple tasks, conditions, and effects. However, we cannot add conditions and effects outside of the function. This approach is suitable for specialized sub-domains and improves the readability of the domain definition. For example, we can modify the domain definition as follows:
var domai =new MyDomainBuilder("Trunk Thumper").AttackEnemySequence().PatrolNextBridgeSequence().Build();
Let's extend out MyDomainBuild with these new sequences.
public DB AttackEnemySequence()
{Sequence("Attack enemy");{
IfEnemy();
MoveTo(Location.Enemy, Speed.Sprint);
{SetLocation(Location.Enemy);
SetIsTired();}
End();
TrunkSlam();
{IfLocation(Location.Enemy);}
End();}
End();
return this;}
public DB PatrolNextBridgeSequence()
{Sequence("Patrol next bridge");
{FindBridge();
{}
End();
MoveTo(Location.Bridge,Speed.Walk);
{SetLocation(Location.Bridge);}
End();
CheckBridge();
{IfLocation(Location.Bridge);
SetBored();}
End();
}End();return this;}
Custom factory
In implementing MyContext, we overrode Factory with DefaultFactory, which was also passed to BaseDomainBuilder when extending domain builders. You can create your own factory methods like PooledFactory and use it through the IFactory interface. DefaultFactory uses normal new operations and sets the collection reference to null when Free* API is called. The IFactory's Create* and Free* API supports pooling but it's up to the user to decide how they prefer to implement it.
Debugging the planner
To debug the planner, set the LogDecomposition flag to true in the context definition to store information about condition success and failure during decomposition. Use the DecompositionLog property to read out the logs, and apply context log calls to custom extensions. Set DebugMTR to true to track the method traversal record, and LastMTRDebug to track the previous traversal record when a plan was replaced. These debug properties are abstract in BaseContext to allow for flexibility in different environments.
Callback hooks in the planner
Planner callback hooks are available for additional insight and other purposes. These include OnNewPlan, OnReplacePlan, OnNewTask, OnNewTaskConditionFailed, OnStopCurrentTask, OnCurrentTaskCompletedSuccessfully, OnApplyEffect, and OnCurrentTaskFailed.