Generating actions through code where player is not the actor

I’m using Puny Inform V 4.6 to create a Ver 3 z-file.

I want to generate an action like <<Jump>> but where the actor is an NPC, not the player.

Setting ‘actor= NPC’ before generating the action doesn’t seem to work. Is this possible?

It is not clear what you want to achieve here (to me at least).
Do you want:

  1. that the JUMP action is available to NPCs only?
  2. to have a JUMP command so that when the player types “jump” (or, conventionally, PERSON, JUMP) you have the NPC jump?

Consider that actions are universal and they work for all living creatures you may create. Also, a jump action is already coded in both I6 and Puny.

Neither of the above.

Inform 6 allows actions to be generated through code (rather than through typed commands and the parser) by enclosing the action in angle brackets, e.g. <PutOn Bread Table>;, which generates an action as if the player had typed ‘put the bread on the table’.

If the player types ‘Bob, put the bread on the table’, the action will (potentially at least) be performed by Bob, not by the player. The question is how to generate such an action through code like <PutOn Bread Table>; but where the action will be performed by Bob, not by the player.

EDIT so that for example:

Object -> Bob "Bob"
    with
        name 'bob',
        life [;
            Attack: <<Jump>>;
        ],
    has animate,
;

where it is Bob not the player who does the jumping.

Oh I completely misunderstood your request.

I would do it (provided it’s economically clever to do so: as in it happens more than once) by creating a custom routine.

Something in the lines of:

[ MyRoutine;
    CODE;
    CODE;
    etc... ;
];

and call it where you need the action:

Attack: MyRoutine();

In your case, I would simply write (but I guess you are just using it as an example):

print "Bob jumps on the spot, fruitlessly"; rtrue;

OK, that’s certainly a quick and dirty option for situations where a simple line of printed text is all that’s required, but it doesn’t deal with more complex actions such as ##PutOn without starting to reinvent the full action machinery for the action (with all its built-in ifs and buts).

The Inform compiler supports a syntax for this:

<<Jump, npcname>>;

However, using the extra argument is up to the library. (The library’s R_Process() function has to accept four arguments instead of three.) The standard 6/12 library does this, but PunyLib does not.

2 Likes

Changing the equivalents for the core routines that zarf pointed out is easy enough:

Replace R_Process;
Replace PerformAction;

Include "globals";
Include "puny"; ! v4.6.1

[ R_Process p_action p_noun p_second p_actor _s1 _s2 _s3;
	    _s1 = inp1; _s2 = inp2; _s3 = actor;
	    inp1 = p_noun; inp2 = p_second; actor = p_actor;
	    PerformAction(p_action, p_noun, p_second, p_actor);
	    inp1 = _s1; inp2 = _s2; actor = _s3;
];

[ PerformAction p_action p_noun p_second p_actor _sa _sn _ss _sact _sdi _sd _sinp1 _sinp2 _result;
	    _sa = action; _sn = noun; _ss = second; _sinp1 = inp1; _sinp2 = inp2; _sdi = selected_direction_index; _sd = selected_direction; _sact = actor;
	    action = p_action; noun = p_noun; second = p_second; inp1 = p_noun; inp2 = p_second; actor = p_actor;
	    selected_direction_index = 0; selected_direction = 0;
	    _SetDirectionIfIsFakeDir(noun, 1);
	    _SetDirectionIfIsFakeDir(second, 2);
	    _result = PerformPreparedAction();
	    action = _sa; noun = _sn; second = _ss; selected_direction_index = _sdi; selected_direction = _sd; inp1 = _sinp1; inp2 = _sinp2; actor = _sact;
	    return _result;
];

but the Puny library seems to be very PC-centric, so by default there is no change in output based on the identity of the actor. You’ll have to modify *Sub() routines and/or other logic as appropriate. For a quick test:

Replace JumpSub;

...

[ JumpSub;
	    if (actor)
	        print_ret (The) actor, " jumps.";
	    else
	        PrintMsg(MSG_JUMP);
];

...

Object toy_duck "toy duck" selfobj
	with    name 'toy' 'duck',
	before  [;
	            Push: <<jump, Bob>>;
	            Pull: <<jump>>;
	        ];

The interaction looks like this:

> PUSH DUCK
Bob jumps.

> PULL DUCK
You jump on the spot, fruitlessly.

EDIT: Note that, as pointed out by fredrik below, this will not work if compiling for Z3, due to a limitation on the number of arguments for a function call in that version of the Z-Machine.

2 Likes

You won’t find this in the DM4, as it was introduced in version 6.33 of the compiler. It’s documented in the Inform 6 Reference Addendum. However, as @zarf pointed out, it doesn’t look like it’s supported by PunyInform.

The standard library has a ChangePlayer routine, so my initial reaction was to try:

ChangePlayer(NPC);
<Jump>;
ChangePlayer(selfobj);

Unfortunately, PunyInform doesn’t support this routine either, but it does show you how to define your own in the change_player.inf example in the howto folder. That might be another alternative to try.

If you change player, lIbrary messages will still presume it’s the player acting.

If I needed to support an NPC performing certain actions in a PunyInform game, I would just write custom code for the actions and objects I need to support.

If I needed the ability for an actor to perform pretty much any action, I would switch to a library that supports this.

PunyInform aims to make all parts of the library accessible in z3 format as well as z5 and z8 format. Procedure calls can’t have four parameters, since it’s not supported in z3. Also, adapting all library messages and actions so they work for any actor would make the library a lot bigger.

Bear in mind that PunyInform was designed with one particular goal - to allow authors to write games in Inform 6 that work well on 8-bit platforms. While we’ve managed to squeeze in a lot of functionality into the library, we count every byte and millisecond spent by a new feature, and ask ourselves if the library is still small and fast enough. Just as with Glulx support, I’m afraid this feature can’t be implemented in a way that aligns with the project goals.

Ooh, yeah, forgot about that.

Thanks for all the comments. In the meantime I’d worked through a prototype approach based on temporarily switching the NPC to be the player and recoding ActionSub routines and library messages to support specific actions, as has been suggested here.

Coming at this from an I7 background, the out-of-the-box PunyInform paradigm is that actions with NPC-as-actor are expected to be re-implemented in their entirety (perhaps in a much-simplified form) within early before stage rules e.g. in the I7 context First before the gnome giving... instead.; this in the PunyInform context being roughly equivalent to Object gnome...with orders [; Give: ... ],'
This is easy enough to implement for the many actions which are unlikely to require much consideration of other before rules, check rules and after rules, and have a simple carry out stage, but there may come a point for specific actions where spoofing the player object instead, to run the NPC action through the full usual action machinery, becomes simpler, and preferable to trying to reproduce all the salient parts within an orders routine.

EDIT I’ll post an example once I’m confident that I’ve ironed out the practical complications of this hack.

1 Like