'For now, this is a scratchpad while I toy with the options, once I get everything sorted out, I'll make this into a real tutorial'

The function to extend an OLC command via Python is called extend and it's in the olc module.

olc.extend takes 7 arguments:

olc.extend(type, opt, menu, chooser, parser, from_proto, to_proto)

type is the OLC menu you want to extend, it seems only redit, oedit, and medit are currently supported.

opt is the option letter. Make sure to pick a letter that isn't currently used in the command you're trying to extend, or else you won't be able to use it.

menu should be a callable which will be called when the user selects the option

chooser is a callable that will be called when that option is printed in the menu. It is passed two arguments, the character object that is using the menu, and the object that the menu is used on. If you are extending redit, for example, the second argument will be the room object they are editing. For instance, the following example:

import mud, olc

def myolc_chooser(ch, room):
    ch.send('MyOLC Menu')

olc.extend('redit', 'O', None, myolc_chooser, None, None, None)

Sticking that into a file such as myolc.py in lib/pymodules/ and then loading the module (either via restarting the mud or the pyload myolc command in-game) will make it so that if you use redit, you will see the following addition:

O) MyOLC Menu

Apparently, when you select the option in the OLC menu, it calls the chooser function again, and this time it only passes one argument, the object that is being edited.

from_proto is called when the OLC is opened, and the function is passed one argument, the room object. Use this opportunity to pull whatever data from the room prototype that you need.

to_proto is the opposite of from_proto, it's called if the user opts to save the edits. It's passed one argument, the room object. In here, you can save whatever data you need so that it's available. to_proto is expected to return a valid Python string. This string will be written into the room's prototype. For example, if you want to set a variable on the room prototype named 'my_data' and set it to True, you should return me.my_data = True, you can then access that information inside of the room's extra code if you'd like via me.my_data.


Bug Found

Research leads me to believe there's an issue in src/olc2/olc_extender.c on lines 126 and 156. Line 126 is inside of the extendDoMenu function, which checks to see if it's working with a C or Python extender. If it's working with a C extender it calls, on line 124, edata->menu(sock, data), whereas if it's a Python extender, it calls the Python function edata->pychoose

If we change line 126 to call edata->pymenu to be consistent with the way C extenders are handled, then what happens is that when the OLC menu is printed out, the framework calls our menu function and passes it two arguments, the socket and the object. If you just want to display a label for the option, you can do: ch.send('Room Flags') for instance. There is no expected return, since the extendDoMenu function doesn't capture it even if you return something.

The other issue with olc_extend.c is on line 156. This is the function that calls the extender chooser. For a C extender, on line 153, the function calls edata->choose_exec(sock, data);, whereas on line 156, for a Python extender, it doesn't pass the socket, only the data:

PyObject *ret = 
    PyObject_CallFunction(edata->pychoose, "O", ext->borrow_py(data));

I'm assuming this is an oversight, since the C extenders get the socket when the chooser function is called, so you can change lines 155-156 to read:

PyObject *ret = 
    PyObject_CallFunction(edata->pychoose, "OO", socketGetPyFormBorrowed(sock), ext->borrow_py(data));

This way, when the chooser function is called, it'll also have access to the socket.


The chooser should return -2, -1, or 0, available as olc.MENU_CHOICE_OK, olc.MENU_CHOICE_INVALID, and olc.MENU_NOCHOICE, respectively.

If your extender just provides a toggle switch or launches a new OLC, return MENU_NOCHOICE If your extender doesn't do anything, which would be dumb, return MENU_CHOICE_INVALID Otherwise, return MENU_CHOICE_OK. Anything else is ignored by the extender framework.

If you are going to provide a toggle, then perform the toggle in your chooser then return MENU_NOCHOICE, ie:

def myolc_chooser(ch, room):
    room.my_toggle = False if room.my_toggle == True else True
    return olc.MENU_NOCHOICE

If you are going to prompt the user to enter a value, send the character the prompt and then return olc.MENU_CHOICE_OK:

def myolc_chooser(ch, room):
    ch.send('Enter the value to store in room.my_value, must be an integer: ')
    return olc.MENU_CHOICE_OK

After the user is prompted, your parser function is called and passed the object and the value they entered. Return True in your parser if the value is valid, otherwise return False

def myolc_parser(room, arg):
    try:
        val = int(arg)
    except ValueError:
        return False
    return True

You will also want to save the value in the parser if it's valid, because after the parser returns, the menu is loaded again and you don't have the chance elsewhere.


Another Bug Found

This bug is encountered if you try to modify the room object passed to one of your OLC extension functions, such as your parser. If you attempt to modify the room (ex: using room.setvar, .getvar, or aux data), or read data from the room (via getvar or aux data) you'll get a traceback stating that you are trying to read/change data for a non-existent room.

In order to fix it you'll need to add two lines to olc2/redit.c to put the room into the room_table and take it back out.

After line 777-779 of redit.c:

// build it from the prototype
olc_from_proto(proto, roomOLCGetExtraCode(data), room, roomGetPyFormBorrowed,
               room_exist, room_unexist);

Add this line:

room_exist(room);

And add the following line to the top of the function deleteRoomOLC(ROOM_OLC *data):

room_unexist(data->room);