This is a rework of Skylar's event snippet (code/utils/events.txt). I have a very strong preference for Skylar's over all the other event systems for one reason only: I found it to be the easiest one to use. Note, that's just a matter of personal preference, so no flame wars please! But, in looking at Skylar's snippet, he(? she?) left off a number of important details, and didn't give any examples. So, I'm filling in those details and giving examples :-) Plus I added in some extra goodies I felt were sorely missing (the command interpreter stuff). This is for version 3.1, but should work easily enough with recent betas (e.g. 3.0bpl21). If you use it, please credit Skylar as well as myself (Edward Glamkowski ). Although I expect everybody probably uses DG Events, eh? Ah well, like I said - this system is quite a bit simpler, so for those who feel overwhelmed by the DG system, check this one out. If you already have DG Events installed and try to add this in concurrently, I recommend you rename the data structures and events for one of the two systems in order to avoid confusion. ---------- end README ---------- ---------- begin snippet ---------- In general, I have tried to use the diff notation of + for a line to add and ! for a line that changes. If you just copy and paste, you'll obviously need to remove them before compiling. ---------- In structs.h, add: struct event_data { struct char_data *ch; /* char this event happens to */ int cmd; /* command number */ char *args; /* typed in args */ int count; /* pulses to event completion */ struct event_data *next; /* next command event in list */ }; Also, in struct char_special_data add a member variable: struct char_special_data { struct char_data *fighting; /* Opponent */ struct char_data *hunting; /* Char hunted by this char */ byte position; /* Standing, fighting, sleeping, etc. */ int carry_weight; /* Carried weight */ byte carry_items; /* Number of items carried */ int timer; /* Timer for update */ + int pending_command; /* For use with the event system */ struct char_special_data_saved saved; /* constants saved in plrfile */ }; ---------- in interpreter.h, add: /* For the event system - see event_activity() in comm.c */ #define SCMD_DELAY 0 #define SCMD_EXECUTE 1 ---------- in comm.h, in the local globals section, add: struct event_data *pending_events = NULL; int num_events = 0; void add_event(struct char_data *ch, char *argument, int cmd, int subcmd, int delay); void remove_event(struct event_data *event); void event_activity(void); in heartbeat(), I added the event processing in PULSE_VIOLENCE. I figured that most time sensitive events are probably combat related anyways! But, you can put it wherever you want, or make a new pulse for it. PLUSE_VIOLENCE is currently every two seconds. ! if (!(pulse % PULSE_VIOLENCE)) { perform_violence(); + event_activity(); + } Then you need to add these functions somewhere in comm.c: void add_event(struct char_data *ch, char *argument, int cmd, int subcmd, int delay) { struct event_data *event = NULL; if (subcmd == SCMD_DELAY) { CREATE(event, struct event_data, 1); event->ch = ch; if (argument) event->args = strdup(argument); else event->args = strdup(""); event->cmd = cmd; event->count = delay; if (pending_events) event->next = pending_events; pending_events = event; num_events += 1; } } void remove_event(struct event_data *event) { struct event_data *temp; if (event->args) free(event->args); REMOVE_FROM_LIST(event, pending_events, next); num_events -= 1; } void event_activity(void) { struct event_data *event; int i; for (event = pending_events, i = 0; i < num_events; i++) { event->count -= 1; if (event->count < 1) { ((*cmd_info[event->cmd].command_pointer) (event->ch, event->args, event->cmd, SCMD_EXECUTE)); remove_event(event); } event = event->next; } } ---------- Now, we don't want people wandering around with queued events - if they're doing something, they shouldn't be able to do anything else. Let's give them a warning before actually interrupting a currently queued event. In short, if they have an event queued and type in some other command, make them type in the new command twice - first time to warn them it will interrupt the queued event, second time to cancel the queued event and do the new command. Note that using the '!' command is equivalent to actually typing in the command, so it will work as we want it to! So, in interpreter.c, in command_interpreter(), add in the following: else if (IS_NPC(ch) && cmd_info[cmd].minimum_level >= LVL_IMMORT) send_to_char(ch, "You can't use immortal commands while switched.\r\n"); ! else if (GET_POS(ch) < cmd_info[cmd].minimum_position) { switch (GET_POS(ch)) { case POS_DEAD: send_to_char(ch, "Lie still; you are DEAD!!! :-(\r\n"); break; case POS_INCAP: case POS_MORTALLYW: send_to_char(ch, "You are in a pretty bad shape, unable to do anything!\r\n"); break; case POS_STUNNED: send_to_char(ch, "All you can do right now is think about the stars!\r\n"); break; case POS_SLEEPING: send_to_char(ch, "In your dreams, or what?\r\n"); break; case POS_RESTING: send_to_char(ch, "Nah... You feel too relaxed to do that..\r\n"); break; case POS_SITTING: send_to_char(ch, "Maybe you should get on your feet first?\r\n"); break; case POS_FIGHTING: send_to_char(ch, "No way! You're fighting for your life!\r\n"); break; + } ! } else if (no_specials || !special(ch, cmd, line)) { + if (pending_events) { + struct event_data *event; + int i; + + for (event = pending_events, i = 0; i < num_events; i++) { + if (event->ch == ch) { + if (ch->char_specials.pending_command != cmd) { + send_to_char(ch, "You are busy! Retype command to interrupt.\r\n"); + ch->char_specials.pending_command = cmd; + return; + } else { + send_to_char(ch, "You stop.\r\n"); + remove_event(event); + } + } /* if (event->ch == ch) */ + } /* for (event = pending_events, i = 0; i < num_events; i++) */ + } /* if (pending_events) */ + ch->char_specials.pending_command = -1; ((*cmd_info[cmd].command_pointer) (ch, line, cmd, cmd_info[cmd].subcmd)); + } Be sure to add forward declarations for remove_event, pending_events and num_events in the appropriate extern sections at the top of interpreter.c. ---------- That's it! Now, the pay-off question: how to use it? Probably one of the first things most people would want to switch to being event driven is spell casting. Unfortunately, it's one of the more complex things to convert. So, here's my example for adding a delay to spell casting. Actually, this is simpler then what's in my own code, since I added a variable to the spell_info_type struct that dictates the delay, but this should get you started: In spell_parser.c, first add a forward declaration for add_event at the top of the file: /* extern functions */ void add_event(struct char_data *ch, char *argument, int cmd, int subcmd, int delay); Then replace your do_cast with this one (or follow the ! and + symbols to hand patch it): ACMD(do_cast) { struct char_data *tch = NULL; struct obj_data *tobj = NULL; ! char *s, *t, *orig = NULL; ! int mana, spellnum, i, target = 0, delay; if (IS_NPC(ch)) return; + if (argument) + orig = strdup(argument) /* get: blank, spell name, target name */ s = strtok(argument, "'"); if (s == NULL) { send_to_char(ch, "Cast what where?\r\n"); + if (orig) + free(orig); return; } s = strtok(NULL, "'"); if (s == NULL) { send_to_char(ch, "Spell names must be enclosed in the Holy Magic Symbols: '\r\n"); + if (orig) + free(orig); return; } t = strtok(NULL, "\0"); /* spellnum = search_block(s, spells, 0); */ spellnum = find_skill_num(s); if ((spellnum < 1) || (spellnum > MAX_SPELLS)) { send_to_char(ch, "Cast what?!?\r\n"); + if (orig) + free(orig); return; } if (GET_LEVEL(ch) < SINFO.min_level[(int) GET_CLASS(ch)]) { send_to_char(ch, "You do not know that spell!\r\n"); + if (orig) + free(orig); return; } if (GET_SKILL(ch, spellnum) == 0) { send_to_char(ch, "You are unfamiliar with that spell.\r\n"); + if (orig) + free(orig); return; } + mana = mag_manacost(ch, spellnum); + if (subcmd == SCMD_DELAY) { + send_to_char(ch, "You begin casting...\r\n"); + act("$n begins casting a spell!", TRUE, ch, 0, 0, TO_ROOM); + + if (GET_LEVEL(ch) >= LVL_IMMORT) /* Immorts always fast cast! */ + delay = 1; + else { + delay = mana / 10; /* Or whatever */ + if (delay < 1) + delay = 1; + } + + add_event(ch, orig, cmd, SCMD_DELAY, delay); + if (orig) + free(orig); + return; + } /* Find the target */ if (t != NULL) { char arg[MAX_INPUT_LENGTH]; strlcpy(arg, t, sizeof(arg)); one_argument(arg, t); skip_spaces(&t); } if (IS_SET(SINFO.targets, TAR_IGNORE)) { target = TRUE; } else if (t != NULL && *t) { if (!target && (IS_SET(SINFO.targets, TAR_CHAR_ROOM))) { if ((tch = get_char_vis(ch, t, NULL, FIND_CHAR_ROOM)) != NULL) target = TRUE; } if (!target && IS_SET(SINFO.targets, TAR_CHAR_WORLD)) if ((tch = get_char_vis(ch, t, NULL, FIND_CHAR_WORLD)) != NULL) target = TRUE; if (!target && IS_SET(SINFO.targets, TAR_OBJ_INV)) if ((tobj = get_obj_in_list_vis(ch, t, NULL, ch->carrying)) != NULL) target = TRUE; if (!target && IS_SET(SINFO.targets, TAR_OBJ_EQUIP)) { for (i = 0; !target && i < NUM_WEARS; i++) if (GET_EQ(ch, i) && isname(t, GET_EQ(ch, i)->name)) { tobj = GET_EQ(ch, i); target = TRUE; } } if (!target && IS_SET(SINFO.targets, TAR_OBJ_ROOM)) if ((tobj = get_obj_in_list_vis(ch, t, NULL, world[IN_ROOM(ch)].contents)) != NULL) target = TRUE; if (!target && IS_SET(SINFO.targets, TAR_OBJ_WORLD)) if ((tobj = get_obj_vis(ch, t, NULL)) != NULL) target = TRUE; } else { /* if target string is empty */ if (!target && IS_SET(SINFO.targets, TAR_FIGHT_SELF)) if (FIGHTING(ch) != NULL) { tch = ch; target = TRUE; } if (!target && IS_SET(SINFO.targets, TAR_FIGHT_VICT)) if (FIGHTING(ch) != NULL) { tch = FIGHTING(ch); target = TRUE; } /* if no target specified, and the spell isn't violent, default to self */ if (!target && IS_SET(SINFO.targets, TAR_CHAR_ROOM) && !SINFO.violent) { tch = ch; target = TRUE; } if (!target) { send_to_char(ch, "Upon %s should the spell be cast?\r\n", IS_SET(SINFO.targets, TAR_OBJ_ROOM | TAR_OBJ_INV | TAR_OBJ_WORLD | TAR_OBJ_EQUIP) ? "what" : "who"); + if (orig) + free(orig); return; } } if (target && (tch == ch) && SINFO.violent) { send_to_char(ch, "You shouldn't cast that on yourself -- could be bad for your health!\r\n"); + if (orig) + free(orig); return; } if (!target) { send_to_char(ch, "Cannot find the target of your spell!\r\n"); + if (orig) + free(orig); return; } if ((mana > 0) && (GET_MANA(ch) < mana) && (GET_LEVEL(ch) < LVL_IMMORT)) { send_to_char(ch, "You haven't the energy to cast that spell!\r\n"); + if (orig) + free(orig); return; } /* You throws the dice and you takes your chances.. 101% is total failure */ if (rand_number(0, 101) > GET_SKILL(ch, spellnum)) { WAIT_STATE(ch, PULSE_VIOLENCE); if (!tch || !skill_message(0, ch, tch, spellnum)) send_to_char(ch, "You lost your concentration!\r\n"); if (mana > 0) GET_MANA(ch) = MAX(0, MIN(GET_MAX_MANA(ch), GET_MANA(ch) - (mana / 2))); if (SINFO.violent && tch && IS_NPC(tch)) hit(tch, ch, TYPE_UNDEFINED); } else { /* cast spell returns 1 on success; subtract mana & set waitstate */ if (cast_spell(ch, tch, tobj, spellnum)) { WAIT_STATE(ch, PULSE_VIOLENCE); if (mana > 0) GET_MANA(ch) = MAX(0, MIN(GET_MAX_MANA(ch), GET_MANA(ch) - mana)); } } + if (orig) + free(orig); } ---------- Ok, here's a much simpler example, if somewhat contrived. If you wanted to have the do_kick command be delayed, you would add the following into do_kick: ACMD(do_kick) { char arg[MAX_INPUT_LENGTH]; struct char_data *vict; int percent, prob; if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_KICK)) { send_to_char(ch, "You have no idea how.\r\n"); return; } if (ROOM_FLAGGED(IN_ROOM(ch), ROOM_PEACEFUL)) { send_to_char(ch, "This room just has such a peaceful, easy feeling...\r\n"); return; } + if (subcmd == SCMD_DELAY) { + send_to_char(ch, "You prepare to kick...\r\n"); + add_event(ch, argument, cmd, SCMD_DELAY, 2); + return; + } one_argument(argument, arg); if (!(vict = get_char_vis(ch, arg, NULL, FIND_CHAR_ROOM))) { if (FIGHTING(ch) && IN_ROOM(ch) == IN_ROOM(FIGHTING(ch))) { vict = FIGHTING(ch); } else { send_to_char(ch, "Kick who?\r\n"); return; } } if (vict == ch) { send_to_char(ch, "Aren't we funny today...\r\n"); return; } /* 101% is a complete failure */ percent = ((10 - (compute_armor_class(vict) / 10)) * 2) + rand_number(1, 101); prob = GET_SKILL(ch, SKILL_KICK); if (percent > prob) { damage(ch, vict, 0, SKILL_KICK); } else damage(ch, vict, GET_LEVEL(ch) / 2, SKILL_KICK); WAIT_STATE(ch, PULSE_VIOLENCE * 3); } Pretty simple, huh? :-) ---------- Note that this event system doesn't work with those commands that actually use the subcmd argument (e.g. do_gen_door which is used for picking locks). Such is the trade off for the system's ease of use. ---------- end snippet ----------