Unifying NPC and Player action handling: A sketch

Hi!

Right now NPCs are handled very differently than the player, which has a privileged place in the standard library. I had the thought of adapting the “refuse-before-prevent-perform-after” pipeline for NPCs. This turned out to be very easy (with the caveat that I haven’t tested it extensively)! Here’s the code:

($Actor try $Action)
	~{ ($Actor refuse $Action) }
	(exhaust) *($Actor before $Action)
	~{ ($Actor refuse $Action) }
	($Actor instead of $Action)

($ try $)

($Actor refuse $Action)
	*($Obj is one of $Action)
	(object $Obj)
	~(direction $Obj)
	~(relation $Obj)
	{
		(when $Obj is not here for $Actor)
		(or) (when $Obj is out of reach for $Actor)
	}

($ before $)
	(fail)

($Actor instead of $Action)
	~{ ($Actor prevent $Action) }
	($Actor perform $Action)
	(exhaust) *($Actor after $Action)

($ prevent $)
	(fail)

($ perform $)

($ after $)
	(fail)

There are two (when $ is _ for $Actor) rules needed by the default refuse rules:

(when $Obj is not here for $Actor)
        ($Actor is in room $Room)
        ~($Obj is in room $Room)

(when $Obj is out of reach for $Actor)
        ~($Obj is reachable by $Actor)

Now lets define rules for an NPC cursing (no new understand rule is needed because they are already included for the curse action in the standard library):

($Actor perform [curse])
        (if) (player can see $Actor) (then)
                (The $Actor) mumbles a few well-chosen words to (itself $Actor).
        (endif)

Finally, a test:

#room
(room *)

#player
(current player *)
(* is #in #room)

#npc
(name *)
        NPC
(singleton *)
(animate *)
(* is #in #room)

(on every tick)
        (#npc try [curse])

And we see that it works!

> take inventory

You have no possessions.
The NPC mumbles a few well-chosen words to itself.

> examine NPC

It seems to be harmless.

The NPC mumbles a few well-chosen words to itself.

I think this is a promising direction to go, as it allows NPC rules to be written almost the same way that player rules are written. I also suspect that, with appropriate checks for if the $Actor is the current player, the code for NPC and Player actions could be unified. Curious to get some thoughts on this.

1 Like

Here’s an example with an action that takes an object, showing refuse modification and before and after rules:

#room
(room *)

#player
(current player *)
(* is #in #room)

#npc
(name *)
        NPC
(singleton *)
(animate *)
(* is #in #room)

#rose
(name *)
        rose
(item *)
(* is #in #room)

(on every tick)
        (#npc try [smell #rose])

($Actor refuse [smell $Obj])
        (just) (when $Obj is not here for $Actor)

($Actor before [smell $Obj])
        (if) (player can see $Actor) (then)
                (The $Actor) (is $Actor) about to smell (the $Obj).
        (endif)

($Actor perform [smell $Obj])
        (if) (player can see $Actor) (then)
                (The $Actor) smell(s $Actor) (the $Obj).
        (endif)

($Actor after [smell $Obj])
        (if) (player can see $Actor) (then)
                (The $Actor) (is $Actor) infatuated with (the $Obj).
        (endif)

> wait
A moment slips away. The NPC is about to smell the rose. The NPC smells the rose. The NPC is infatuated with the rose.

I’ve begun writing a library based on these ideas. It lets you write things such as:

(on every tick)
        (if) (#hat is #wornby #npc) (then)
                (try [remove #hat] by #npc)
        (elseif) (#hat is #heldby #npc) (then)
                (select)
                        (try [drop #hat] by #npc)
                (or)
                        (try [wear #hat] by #npc)
                (at random)
        (else)
                (try [take #hat] by #npc)
        (endif)

#room
(room *)

#player
(current player *)
(* is #in #room)

#npc
(name *)
        NPC
(singleton *)
(animate *)
(* is #in #room)

#hat
(name *)
        hat
(wearable *)
(* is #wornby #npc)

Here is output; I take the hat away from the NPC for a turn to show that nothing happens as expected when (when $Obj is held by someone other than $Actor) is true in (prevent [take $Obj] by $Actor).

> wait
A moment slips away. The NPC takes off the hat.
> wait
A moment slips away. The NPC puts on the hat.
> wait
A moment slips away. The NPC takes off the hat.
> wait
A moment slips away. The NPC drops the hat.
take hat
You take the hat.
> wait
A moment slips away.
> drop hat
The hat falls to the ground. The NPC takes the hat.

Here is the library so far, supporting actions take, wear, remove and drop. I plan to eventually cover the actions provided by the standard library.

%% take

(prevent [take $Obj from $Parent] by $Actor)
	~($Obj has ancestor $Parent)
	(if) (current player $Player) ($Actor = $Player) (then)
		(if) (animate $Parent) (then)
			(The $Parent) (does $Parent) not have (the $Obj).
		(elseif) (container $Parent) (then)
			(The $Obj) (isn't $Obj) in (the $Parent).
		(elseif) (supporter $Parent) (then)
			(The $Obj) (isn't $Obj) on (the $Parent).
		(else)
			That's not where (the $Obj) (is $Obj).
		(endif)
	(endif)

(perform [take $Obj from $Parent] by $Actor)
	($Obj is recursively worn by $Actor)
	{
		($Parent = $Actor)
	(or)
		($Parent has ancestor $Player)
	}
	(try [remove $Obj] by $Actor)

(perform [take $Obj from $] by $Actor)
	(try [take $Obj] by $Actor)

(prevent [take $Obj] by $Actor)
	(when $Obj is already held by $Actor)
	(or) (when $Obj is fine where it is for $Actor)
	(or) (when $Obj is part of something for $Actor)
	(or) (when $Obj is held by someone other than $Actor)
	(or) (when $Obj is worn by someone other than $Actor)
	(or) (when $Obj can't be taken by $Actor)

(narrate $Actor taking $Obj)
	(if) (current player $Player) ($Actor = $Player) (then)
		You take (the $Obj)
		(if)
			($Obj is $Rel $Parent)
			~($Actor has ancestor $Parent)
		(then)
			(reverse-name $Rel) (the $Parent)
		(endif)
		.
	(elseif) (player can see $Actor) (then)
		(The $Actor) take(s $Actor) (the $Obj).
	(endif)

(perform [take $Obj] by $Actor)
	(narrate $Actor taking $Obj)
	(now) ($Obj is #heldby $Actor)
	(now) ($Obj is handled)

%% remove

(instead of [remove $Obj] by $Actor)
	~(wearable $Obj)
	~($Obj has relation #partof)
	(item $Obj)
	(try [take $Obj] by $Actor)

(prevent [remove $Obj] by $Actor)
	(when $Obj is part of something for $Actor)

(prevent [remove $Obj] by $Actor)
	~($Obj is recursively worn by $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		But you aren't wearing (the $Obj).
	(endif)

(narrate $Actor removing $Obj)
	(if) (current player $Player) ($Actor = $Player) (then)
		You take off (the $Obj).
	(elseif) (player can see $Actor) (then)
		(The $Actor) take(s $Actor) off (the $Obj).
	(endif)

(perform [remove $Obj] by $Actor)
	(narrate $Actor removing $Obj)
	(now) ($Obj is #heldby $Actor)
	(now) ($Obj is handled)	

%% wear

(before [wear $Obj] by $Actor)
	(wearable $Obj)
	~($Obj is nested #wornby $Actor)
	(ensure $Obj is held by $Actor)

(prevent [wear $Obj] by $Actor)
	($Obj is recursively worn by $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		You are already wearing (the $Obj).
	(endif)

(prevent [wear $Obj] by $Actor)
	~(wearable $Obj)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj) can't be worn.
	(endif)

(prevent [wear $Obj] by $Actor)
	(when $Obj isn't directly held by $Actor)

(narrate $Actor wearing $Obj)
	(if) (current player $Player) ($Actor = $Player) (then)
		You put on (the $Obj).
	(elseif) (player can see $Actor) (then)
		(The $Actor) put(s $Actor) on (the $Obj).
	(endif)

(perform [wear $Obj] by $Actor)
	(narrate $Actor wearing $Obj)
	(now) ($Obj is #wornby $Actor)
	(now) ($Obj is handled)

%% drop

(before [drop $Obj] by $Actor)
	($Obj has ancestor $Actor)
	~($Obj has relation #partof)
	(ensure $Obj is held by $Actor)

(prevent [drop $Obj] by $Actor)
	(when $Obj is part of something for $Actor)
	(or) (when $Obj isn't directly held by $Actor)

(narrate $Actor dropping $Obj)
	($Actor is $Rel $Loc)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj) fall(s $Obj)
		(if) ($Rel = #on) (then)
			onto (the $Loc).
		(else)
			to the ground.
		(endif)
	(elseif) (player can see $Actor) (then)
		(The $Actor) drop(s $Actor) (the $Obj).
	(endif)

(perform [drop $Obj] by $Actor)
	(narrate $Actor dropping $Obj)
	($Actor is $Rel $Loc)
	(now) ($Obj is $Rel $Loc)
	(now) ($Obj is handled)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

(ensure $Obj is held by $Actor)
	(if) ($Obj is recursively worn by $Actor) (then)
		(first try [remove $Obj] by $Actor)
	(elseif) (item $Obj) ~($Obj is #heldby $Actor) (then)
		(first try [take $Obj] by $Actor)
	(endif)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

(when $Obj is out of sight for $Actor)
	~($Actor can see $Obj)
	(if) (current player $Player) ($Actor = $Player) (then)
		(if) (player can see) (then)
			You can't see any such thing.
		(else)
			You can't see well enough to do that in the darkness.
		(endif)
	(endif)

(when $Obj is already held by $Actor)
	{
		($Obj is #heldby $Actor)
	(or)
		($Obj is recursively worn by $Actor)
	}
	(if) (current player $Player) ($Actor = $Player) (then)
		{
			($Obj is #heldby $Actor)
			You're already holding (the $Obj).
		(or)
			($Obj is recursively worn by $Actor)
			You're already wearing (the $Obj).
		}
	(endif)

(when $Obj isn't directly held by $Actor)
	~($Obj is #heldby $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		You're not holding (the $Obj).
	(endif)

(when $Obj is not here for $Actor)
	{
		(not here $Obj)
	(or)
		($Actor is in room $Room)
		~($Obj is in room $Room)
	}
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj) (isn't $Obj) here.
	(endif)

(when $Obj is out of reach for $Actor)
	~($Obj is reachable by $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		(if) (player can see $Obj) (then)
			You can't reach (the $Obj).
		(else)
			(The $Obj) (isn't $Obj) here.
		(endif)
	(endif)

(when (intangible $Obj) is out of reach for $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj is) intangible.
	(endif)

(when $Obj is part of something for $Actor)
	($Obj is #partof $Parent)
	(if) (current player $Player) ($Actor = $Player) (then)
		(That's $Obj) part of (the $Parent).
	(endif)

(when $Obj is held by someone other than $Actor)
	($Obj is #heldby $Parent)
	~($Actor = $Parent)
	(if) (current player $Player) ($Actor = $Player) (then)
		(That $Obj) belong(s $Obj) to (the $Parent).
	(endif)

(when $Obj is worn by someone other than $Actor)
	($Obj is #wornby $Parent)
	~($Actor = $Parent)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Parent is) wearing (that $Obj).
	(endif)

(when $Obj is fine where it is for $Actor)
	(fine where it is $Obj)
	(if) (current player $Player) ($Actor = $Player) (then)
		(if) (animate $Obj) (then)
			(uppercase) (it $Obj is)
		(else)
			(That's $Obj)
		(endif)
		fine where (it $Obj) (is $Obj).
	(endif)

(when ~(item $Obj) can't be taken by $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		You can't take (the $Obj).
	(endif)

~(when (supporter $) won't accept #on from $)

~(when (container $) won't accept #in from $)

(when $Obj won't accept $Rel from $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		(if) ($Rel is one of [#under #behind]) (then)
			Putting things (name $Rel) (the $Obj)
			would achieve little.
		(else)
			You can't put things (name $Rel) (the $Obj).
		(endif)
	(endif)

~(when (actor supporter $) won't accept $ actor #on)

~(when (actor container $) won't accept $ actor #in)

(when $Obj won't accept $Actor actor $Rel)
	(if) (current player $Player) ($Actor = $Player) (then)
		(if) ($Rel is one of [#under #behind]) (then)
			Going (name $Rel) (the $Obj) would achieve little.
		(else)
			It's not possible to get
			(towards-name $Rel) (the $Obj).
		(endif)
	(endif)

(when $Obj is already $Rel $Dest for $Actor)
	($Obj is $Rel $Dest)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj is) already (name $Rel) (the $Dest).
	(endif)

(when $Obj is $Rel Parent for $Actor)
	($Obj is $Rel $Parent)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj) will have to get
		(reverse-name $Rel) (the $Parent) first.
	(endif)

(when $Obj is closed for $Actor)
	($Obj is closed)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj is) closed.
	(endif)

(when $Obj blocks passage for $Actor)
	($Obj blocks passage)
	(if) (current player $Player) ($Actor = $Player) (then)
		(The $Obj is) closed.
	(else)
		(The $Obj) (doesn't $Obj) allow you to pass.
	(endif)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

($Actor can see)
	(visibility ceiling of $Actor is $Ceil)
	(light reaches ceiling $Ceil)

($Actor can see $Obj)
	(visibility ceiling of $Actor is $Ceil)
	(if) (visibility ceiling of $Obj is $Ceil) (then)
	{
		($Actor can see)
	(or)
		($Obj has ancestor $Actor)
	}
	(else)
		(room $Obj)
		(room $Ceil)
		($Actor can see)
		{
			(from $Ciel go $ to $Obj)
		(or)
			(from $Ciel through $Door to $Obj)
			~($Door blocks light)
		}
	(endif)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

(try $Action by $Actor)
	~{ (refuse $Action by $Actor) (stop) }
	(exhaust) *(before $Action by $Actor)
	~{ (refuse $Action by $Actor) (stop) }
	(instead of $Action by $Actor)

(try $ by $)

(first try $Action by $Actor)
	(if) (current player $Player) ($Actor = $Player) (then)
		(line)
		\( first attempting to (describe action $Action) \)
		(line)
		(try $Action by $Actor)
		(tick)
		(par)
	(else)
		(try $Action by $Actor)
	(endif)

(refuse $Action by $Actor)
	*($Obj is one of $Action)
	(object $Obj)
	~(direction $Obj)
	~(relation $Obj)
	{
		(when $Obj is not here for $Actor)
		(or)
		(when $Obj is out of reach for $Actor)
	}

(before $ by $)
	(fail)

(instead of $Action by $Actor)
	~{
		(prevent $Action by $Actor)
		(if) (current player $Player) ($Actor = $Player) (then)
			(tick)
		(endif)
		(stop)
	 }
	(perform $Action by $Actor)
	(exhaust) *(after $Action by $Actor)

(prevent $ by $)
	(fail)

(perform $ by $)

(after $ by $)
	(fail)

One thing that is less than satisfactory is that I don’t know of any way to queue up actions for an NPC for later ticks; for this reason NPCs can currently perform actions that would take multiple ticks for the player to perform in a single tick. Any thoughts?

I think it’s great that you are exploring this design space, and I follow your progress with interest.

However, there was a time when I considered having a unified model for PC and NPC actions, similar to what you’re working on here. I decided against it, because it seemed like abstraction for the sake of abstraction, with few practical benefits.

Actions undertaken by the PC will always have to be treated as special cases. This is already evident in your code, e.g. in the when-rules, where you consistently check whether the actor is the current player. What’s the point of unifying two cases into a common interface, if you consistently separate the two cases again on the other side of the interface?

I could be wrong of course, and I’ll try to keep an open mind. I’m just rationalizing why I stopped exploring these ideas at one point. If you persevere, you might find something that I missed.

For this, you could store a list of actions in a global variable, or a per-object variable attached to the NPC.

Thank you very much for your feedback!

You make a good point that a lot of code goes into separate NPC and player cases; the vast majority of these special cases have to do with printed output, so I’m going to put some thought into whether there might be some way to do this better, maybe in the form of (narrate ...) type delegations.

I’m intrigued by the idea of per-NPC lists storing actions; I will experiment.

Here is my first attempt at queueing NPC actions. For every animate NPC other than the player, if the NPC’s action queue is empty, the ($Actor behavior) predicate is called:

(* behavior)
        (The *) is planning...
        (* plan [take #rose])
        (* plan [drop #rose])

The result:

> wait
A moment slips away. The NPC is planning… The NPC takes the rose.
> wait
A moment slips away. The NPC drops the rose.
> wait
A moment slips away. The NPC is planning… The NPC takes the rose.
> wait
A moment slips away. The NPC drops the rose.
> take rose
You take the rose. The NPC is planning… The NPC fails to take something!

When an action fails, the NPC’s action queue is cleared and it replans using the behavior predicate; I felt that was the most natural thing to do. There are no before, after or instead rules, as I assume that the coder handles this logic in the behavior predicate and ($Actor do $Action) definitions. Here is the library code so far:

($ behavior)

%%

($Actor plan $Action)
	(if) ~($Actor action queue is $) (then)
		(now) ($Actor action queue is [])
	(endif)
	($Actor action queue is $OldQueue)
	(append $OldQueue [$Action] $NewQueue)
	(now) ($Actor action queue is $NewQueue)

($Actor clear plans)
	(now) ($Actor action queue is [])

%%

(on every tick)
	(current player $Player)
	(collect $X)
		*(animate $X)
		~($X = $Player)
	(into $Actors)
	(exhaust)
	{
		*($Actor is one of $Actors)
		(if)
			~($Actor action queue is $)
		(then)
			(now) ($Actor action queue is [])
		(endif)
		(if)
			($Actor action queue is [])
		(then)
			($Actor behavior)
		(endif)
		($Actor action queue is $Queue)
		(if)
			~(empty $Queue)
		(then)
			([$Action | $Rest] = $Queue)
			(now) ($Actor action queue is $Rest)
			($Actor do $Action)
		(endif)
	}

%%

($Actor do [take $Obj])
	(if)
		(item $Obj)
		~(not here $Obj)
		~(out of reach $Obj)
		~(fine where it is $Obj)
		~($Obj is #partof $)
		~($Obj is #heldby $)
		~($Obj is #wornby $)
		($Actor check $Obj shares visibility ceiling)
		($Actor check $Obj shares reachability ceiling)
	(then)
		(if)
			(player can see $Actor)
		(then)
			(The $Actor) take(s $Actor) (the $Obj).
		(endif)
		(now) ($Obj is #heldby $Actor)
		(now) ($Obj is handled)
	(else)
		(The $Actor) fail(s $Actor) to take something!
		($Actor clear plans)
	(endif)

($Actor do [drop $Obj])
	(if)
		~($Obj is #partof $)
		($Obj is #heldby $Actor)
		($Actor check $Obj shares visibility ceiling)
		($Actor check $Obj shares reachability ceiling)
	(then)
		(if)
			(player can see $Actor)
		(then)
			(The $Actor) drop(s $Actor) (the $Obj).
		(endif)
		($Actor is $Rel $Loc)
		(now) ($Obj is $Rel $Loc)
		(now) ($Obj is handled)
	(else)
		(The $Actor) fail(s $Actor) to drop something!
		($Actor clear plans)
	(endif)

($ do $)

%%

($Actor check $Obj shares visibility ceiling)
	(visibility ceiling of $Actor is $Ceil)
	(visibility ceiling of $Obj is $Ceil)

($Actor check $Obj shares reachability ceiling)
	(reachability ceiling of $Actor is $Ceil)
	(reachability ceiling of $Obj is $Ceil)

The code starts feeling heavy when I add code for narrating NPCs failing to act:

($Actor do [take $Obj])
	(if)
		(visibility ceiling of $Actor is $VCeil)
		~(visibility ceiling of $Obj is $VCeil)
	(then)
		(narrate $Actor not taking non-visible $Obj)
		($Actor clear plans)
	(elseif)
		(not here $Obj)
	(then)
		(narrate $Actor not taking not here $Obj)
		($Actor clear plans)
	(elseif)
		{
			(out of reach $Obj)
		(or)
			(reachability ceiling of $Actor is $RCeil)
			~(reachability ceiling of $Obj is $RCeil)
		}
	(then)
		(narrate $Actor not taking out of reach $Obj)
		($Actor clear plans)
	(elseif)
		~(item $Obj)
	(then)
		(narrate $Actor not taking item $Obj)
		($Actor clear plans)
	(elseif)
		(fine where it is $Obj)
	(then)
		(narrate $Actor not taking fine where it is $Obj)
		($Actor clear plans)
	(elseif)
		($Obj is #partof $Something)
	(then)
		(narrate $Actor not taking $Obj which is part of $Something)
		($Actor clear plans)
	(elseif)
		($Obj is #heldby $Someone)
		~($Actor = $Someone)
	(then)
		(narrate $Actor not taking $Obj which is held by $Someone)
		($Actor clear plans)
	(elseif)
		($Obj is #wornby $Someone)
		~($Actor = $Someone)
	(then)
		(narrate $Actor not taking $Obj which is worn by $Someone)
		($Actor clear plans)
	(elseif)
		($Obj is #wornby $Actor)
	(then)
		($Actor do [remove $Obj])
	(elseif)
		($Obj is #heldby $Actor)
	(then)
		(narrate $Actor not taking held $Obj)
		($Actor clear plans)
	(else)
		(narrate $Actor taking $Obj)
		(now) ($Obj is #heldby $Actor)
		(now) ($Obj is handled)
	(endif)

(narrate $Actor not taking non-visible $)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) grasp(s $Actor) blindly at something.
	(endif)

(narrate $Actor not taking not here $)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) grasp(s $Actor) at something not here.
	(endif)

(narrate $Actor not taking out of reach $Obj)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) grasp(s $Actor) at (the $Obj)
		but can't reach (them $Obj).
	(endif)

(narrate $Actor not taking item $Obj)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) can't take (the $Obj), try as (it $Actor) might.
	(endif)

(narrate $Actor not taking fine where it is $Obj)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) decide(s $Actor) not to take (the $Obj):
		(it $Obj is) fine where (it $Obj is).
	(endif)

(narrate $Actor not taking $Obj which is part of $Something)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) can't take (the $Obj):
		(it $Obj) is part of (the $Something)!
	(endif)

(narrate $Actor not taking $Obj which is held by $Someone)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) decide(s $Actor) not to take (the $Obj)
		being held by (the $Somone).
	(endif)

(narrate $Actor not taking $Obj which is worn by $Someone)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) decide(s $Actor) not to take (the $Obj)
		being worn by (the $Somone).
	(endif)

(narrate $Actor not taking held $Obj)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) attempt(s $Actor) to take (the $Obj),
		only to realize (it $Actor) (is $Actor)
		already holding (it $Obj).
	(endif)

(narrate $Actor taking $Obj)
	(if)
		(player can see $Actor)
	(then)
		(The $Actor) take(s $Actor) (the $Obj).
	(endif)

I feel like having something similar to prevent rules might be a good idea even if I don’t do the whole refuse-before-prevent-perform-after pipeline. I’ll need to think how to do this in a way that works well with NPC action queueing.