Mocha Tutorial: venom_sac


Purpose

In designing the venom_sac function, I wanted to develop a type of poison that affects the victim more dramatically and rapidly than the usual type found on needles or through spells.


Behavior and handler selection

The first step in designing any function is to write down a brief sketch of how the function should alter the normal course of events. The venom comes in a container, and takes affect when the victim either drinks or quaffs the contents. The venom acts quickly and does a significant amount of damage, usually enough to kill the victim. While under the influence of the venom, the victim is subject to a magnified form of the usual strength penalty for poison. Finally, the cure poison spell nullifies the effects of the venom.

With the sketch in mind, we begin to think about how the function might work. When someone drinks something in the presence of the venom, including the venom itself, the server first fires a command_drink event. While in many cases we might have to satisfy ourselves with handling this event, in the case of the drink command, a better option exists: the do_drink event. The server sends this event only to the object being drunk, allowing us to leave the parsing of the command up to the regular code. Quaffing the venom is similar: we need merely handle the do_quaff event.

Once the venom begins to take affect, we must periodically damage the victim. We manage this activity by adding our own events and using the do_event handler to apply the damage to the victim. The strength penalty is easily added, but has the added benefit of serving as a marker for the curing of the victim--we know that someone has cured the victim when the strength penalty vanishes.

In summary, we need implement the following handlers for the desired behavior:


Laying out the first handlers

Rather than making two copies of the same code to provide the same behavior when someone drinks or quaffs the potion, we write a single copy of the code in a Mocha routine, then have the two handlers make use of the routine. Recall that a routine is much like a handler--it has a name and a set of pieces of information (the interface), and it returns a single piece of information. The interfaces for do_drink and do_quaff are the same: the server passes the venom (an object) and the person who drank or quaffed the venom (a person). As with all handlers, each returns type special. We call our routine drink_venom and make the interface and return type match those of do_drink and do_quaff:

special 
drink_venom (object obj, person pc)
{
    // We will insert the code here.
}

The object obj here is the venom, and the person pc is the one who drank or quaffed the venom. Before we tackle the body of the routine, we can write the "glue" that ties the two handlers into our routine. The handlers must appear after the routine in the function's file--nothing can call a routine until it has been defined. We merely pass the arguments to the routine and return the value returned by the routine:

special
do_drink (object obj, person pc)
{
    return drink_venom (obj, pc);
}

special
do_quaff (object obj, person pc)
{
    return drink_venom (obj, pc);
}

object {
    public:
        action[PO] drink_pc, 
		   drink_room;
        action[P]  suffer_pc,
		   suffer_room;
    instance:
        person victim;
}

special 
drink_venom (object obj, person pc)
{
    // First, show the person drinking the venom.
    show_action (drink_pc, to_player, pc, obj);
    show_action (drink_room, to_room, pc, obj);

    // We can't destroy the object until we move it off of the nohassled
    // person.
    move (obj, find_room_by_vnum (0), inside_of);

    // If a nohassled person drinks the venom, just send the drinking
    // message and quit.  We return handled to ensure that the normal
    // messages are not sent, particularly because we have already
    // destroyed the venom.
    if (pc.nohassled) {
        obj.destroy;
        return handled;
    }

    // Store a pointer to the pc.  We might simply attach the pc to the
    // suffering event (see below), but if the pc died to something besides
    // the venom, the event would be removed and the venom would never be
    // destroyed.
    victim = pc;

    // We now add an event for damaging the pc. */
    add_event (none, none, obj, none, 1, number (15, 25));

    // Add a strength penalty to the pc.  This marker also allows us to
    // detect the use of cure poison on the pc--if the marker is gone,
    // then either 100 ticks have passed (the duration), or someone cast
    // cure poison on the pc.
    add_modifier (pc, spell_poison, 100, modify_strength, -5, false, false);

    // Return handled to avoid normal drink/quaff messages.
    return handled;
}

special
do_event (room rm, person mob, object obj, entity ent, integer type)
{
    person pc;
    boolean saved;
    integer damage;

    if (victim == none) {
        obj.destroy;
        return handled;
    }

    pc = victim;
    if (!pc.poisoned ||
        ((saved = save_against (pc, save_poison)) &&
         number (1, 100) < save_chance (pc, save_poison) / 10)) {
        obj.destroy;
        return handled;
    }

    damage = make_dice (type, 8, 5).roll;
    if (saved)
        damage /= 2;
    else
        type++;

    show_action (suffer_pc, to_player, pc);
    show_action (suffer_room, to_room, sight, if_visible, pc);
    do_damage (pc, pc, type_none, damage);
    if (!pc.valid)
        obj.destroy;
    else
        add_event (none, none, obj, none, type, number (25, 75));

    return handled;
}

Return to Mocha's Main Page