From a1976f92dda37da878c0d1ad4f7fc7a7b149b12c Mon Sep 17 00:00:00 2001 From: kinther Date: Sun, 1 Feb 2026 08:05:04 -0800 Subject: [PATCH] Remove DG scripts in favor of Python scripting --- CMakeLists.txt | 5 +- THIRD-PARTY-LICENSES.md | 9 + changelog | 1 + doc/dg_events.txt | 301 --- lib/scripts/README.md | 162 ++ lib/scripts/sample_echo.py | 14 + lib/scripts/sample_fountain.py | 26 + lib/scripts/sample_patrol.py | 39 + lib/world/mob/1.toml | 1 + lib/world/rsv/1.toml | 13 +- lib/world/trg/0.toml | 5 +- lib/world/trg/1.toml | 9 +- lib/world/wld/1.toml | 142 +- src/CMakeLists.txt | 2 + src/Makefile | 10 +- src/Makefile.amiga | 2 +- src/Makefile.arc | 4 +- src/Makefile.in | 10 +- src/Makefile.lcc | 1 - src/Makefile.msvc | 7 +- src/Makefile.os2 | 2 +- src/Smakefile | 2 +- src/act.comm.c | 2 +- src/act.informative.c | 2 +- src/act.item.c | 2 +- src/act.movement.c | 2 +- src/act.other.c | 2 +- src/act.wizard.c | 7 +- src/asciimap.c | 2 +- src/comm.c | 9 +- src/db.c | 46 +- src/db.h | 4 + src/dg_variables.c | 1646 -------------- src/fight.c | 2 +- src/genmob.c | 8 +- src/genobj.c | 2 +- src/genolc.c | 2 +- src/genwld.c | 5 +- src/genzon.c | 2 +- src/graph.c | 57 + src/graph.h | 1 + src/handler.c | 2 +- src/improved-edit.c | 2 +- src/interpreter.c | 3 +- src/limits.c | 2 +- src/lists.c | 2 +- src/magic.c | 2 +- src/medit.c | 2 +- src/modify.c | 2 +- src/mud_event.c | 2 +- src/mud_event.h | 2 +- src/oasis.c | 2 +- src/oasis.h | 1 + src/oasis_copy.c | 2 +- src/oasis_list.c | 123 +- src/oedit.c | 2 +- src/players.c | 4 +- src/protocol.c | 2 +- src/{dg_comm.c => py_comm.c} | 20 +- src/{dg_db_scripts.c => py_db_scripts.c} | 25 +- src/{dg_event.c => py_event.c} | 18 +- src/{dg_event.h => py_event.h} | 22 +- src/{dg_handler.c => py_handler.c} | 27 +- src/{dg_misc.c => py_misc.c} | 27 +- src/{dg_mobcmd.c => py_mobcmd.c} | 15 +- src/{dg_objcmd.c => py_objcmd.c} | 15 +- src/{dg_olc.c => py_olc.c} | 17 +- src/{dg_olc.h => py_olc.h} | 21 +- src/{dg_scripts.c => py_script_driver.c} | 317 +-- src/py_scripts.c | 2594 ++++++++++++++++++++++ src/py_scripts.h | 17 + src/{dg_triggers.c => py_triggers.c} | 34 +- src/{dg_scripts.h => py_triggers.h} | 33 +- src/py_variables.c | 37 + src/{dg_wldcmd.c => py_wldcmd.c} | 15 +- src/redit.c | 2 +- src/set.c | 773 ++++++- src/spell_parser.c | 2 +- src/spells.c | 2 +- src/zedit.c | 2 +- 80 files changed, 4332 insertions(+), 2430 deletions(-) delete mode 100644 doc/dg_events.txt create mode 100644 lib/scripts/README.md create mode 100644 lib/scripts/sample_echo.py create mode 100644 lib/scripts/sample_fountain.py create mode 100644 lib/scripts/sample_patrol.py delete mode 100644 src/dg_variables.c rename src/{dg_comm.c => py_comm.c} (82%) rename src/{dg_db_scripts.c => py_db_scripts.c} (89%) rename src/{dg_event.c => py_event.c} (92%) rename src/{dg_event.h => py_event.h} (77%) rename src/{dg_handler.c => py_handler.c} (85%) rename src/{dg_misc.c => py_misc.c} (93%) rename src/{dg_mobcmd.c => py_mobcmd.c} (97%) rename src/{dg_objcmd.c => py_objcmd.c} (97%) rename src/{dg_olc.c => py_olc.c} (98%) rename src/{dg_olc.h => py_olc.h} (62%) rename src/{dg_scripts.c => py_script_driver.c} (92%) create mode 100644 src/py_scripts.c create mode 100644 src/py_scripts.h rename src/{dg_triggers.c => py_triggers.c} (97%) rename src/{dg_scripts.h => py_triggers.h} (93%) create mode 100644 src/py_variables.c rename src/{dg_wldcmd.c => py_wldcmd.c} (96%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b9087b..9db077d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -391,6 +391,8 @@ configure_file( ) +# ========== Python ========== +find_package(Python3 3.12 REQUIRED COMPONENTS Development) # ========== Source-filer ========== file(GLOB SRC_FILES src/*.c) @@ -398,8 +400,9 @@ list(APPEND SRC_FILES third_party/tomlc99/toml.c) # ========== Bygg kjørbar ========== add_executable(circle ${SRC_FILES}) -target_link_libraries(circle ${EXTRA_LIBS}) +target_link_libraries(circle ${EXTRA_LIBS} Python3::Python) target_include_directories(circle PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlc99) +target_include_directories(circle PRIVATE ${Python3_INCLUDE_DIRS}) add_subdirectory(src/util) diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md index a100632..e08d60b 100644 --- a/THIRD-PARTY-LICENSES.md +++ b/THIRD-PARTY-LICENSES.md @@ -24,3 +24,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Python 3.12 (CPython) + +Copyright (c) 2001-2024 Python Software Foundation + +Python Software Foundation License Version 2 (PSF-2.0) + +This project embeds CPython and is subject to the PSF-2.0 license. See the +Python source distribution or documentation for the full license text. diff --git a/changelog b/changelog index edbff1b..0961e4d 100644 --- a/changelog +++ b/changelog @@ -64,6 +64,7 @@ Changes in v1.3.0-alpha: * Migration away from OLC with new commands "qcreate", "qset", and "qsave" for builders to create quests * Updated mset, oset, and rset commands with missing parameters * Added NPC extra descriptions for future support + * Introduced Python scripting as DG Scripts replacement, using "pylist" and storing scripts in lib/scripts Features that will be implemented in the next few releases: diff --git a/doc/dg_events.txt b/doc/dg_events.txt deleted file mode 100644 index a70f644..0000000 --- a/doc/dg_events.txt +++ /dev/null @@ -1,301 +0,0 @@ -/* -************************************************************************ -* File: events.doc * -* * -* Usage: An explanation of how to use mud events * -* Written by Joseph Arnusch (Vatiken) (Joseph.Arnusch@gmail.com) * -* -* Usage: An explanation of how to use events * -* Written by Eric Green (ejg3@cornell.edu) * -************************************************************************ -*/ - -Vatiken's MUD event system --------------------------- - -Table of Contents ------------------ -1. Purpose -2. Functions Related to MUD Events -3. Steps to Create a New MUD Event -4. Differences between the two systems - -1. PURPOSE - -I scribed a "MUD" event system using the "Death Gate Event" system already -in place to allow for increased ease, and maintainability for both rookie -and experienced programmers. - -2. FUNCTIONS RELATED TO MUD EVENTS - - a) See EVENTFUNC() in the Death Gate Events documentation below - b) void init_events(void) - "init_events()" creates the global events list and is the allocated location - for placing any global events, these may include things like AI, Weather, - and Combat. - c) struct mud_event_data * char_has_mud_event(struct char_data * ch, event_id iId) - "char_has_mud_event()" returns an event in the characters event list that matches - the supplied "event_id", or NULL if none exists. - d) NEW_EVENT(event_id, struct, var, time) - "NEW_EVENT" creates a new event of the "event_id" type, with the supplied structure - (ch, desc, object, etc..), any addtional "var"s, and is set to activate in - this amount of "time". - e) struct mud_event_list[] - The mud_event_list[] is an array of all the events you've designed into your MUD. - The reason for this excessive step is primarily for organization and troubleshooting, - and it takes a mere couple seconds to add to mud_events.c. - -3. STEPS TO CREATE A NEW MUD EVENT - - a) Add the new event_id to enum list in mud_events.h - - typedef enum { - eNULL, - ePROTOCOLS, /* The Protocol Detection Event */ - eWHIRLWIND, /* The Whirlwind Attack */ - - eNEWEVENT /* A NEW EVENT */ - } event_id; - - b) Create the event - - EVENTFUNC(new_event) - { - struct char_data *ch, *tch; - struct mud_event_data *pMudEvent; - struct list_data *room_list; - int count; - - /* This is just a dummy check, but we'll do it anyway */ - if (event_obj == NULL) - return 0; - - ... - - return 0; - } - - c) Add the event_id data to mud_event_list[] - - struct mud_event_list mud_event_index[] = { - { "Null" , NULL , -1 }, /* eNULL */ - { "Protocol" , get_protocols , EVENT_DESC }, /* ePROTOCOLS */ - { "Whirlwind" , event_whirlwind, EVENT_CHAR } /* eWHIRLWIND */ - - { "A New Event" , new_event , EVENT_CHAR } /* eNEWEVENT */ - }; - - d) Place a call for the new event - - if (variableX > variableY) - NEW_EVENT(eNEWEVENT, ch, NULL, 60 * PASSES_PER_SEC); - - e) Sit back and enjoy your event triggering in 60 seconds. - -4. DIFFERENCES BETWEEN THE TWO SYSTEMS - -The biggest differences between the two systems is that the MUD Event System -employs certain functions to make it more "dummy proof" without limiting -functionality. - -For example: - a) all memory allocated for a MUD event will be freed when the event - is no longer in use. - b) the mud_event_index[] can/will log when events are called, allowing for an - easy way to debug any problems. - c) the mud_event structure allows for easy handling of memory that shouldn't - be freed with the event like the character him/herself. - d) when a character leaves the game, all mud_events will be cleared without - manually having to place checks in free_char(). - -The "MUD Event" system should be adequate for 99% of all events that a developer -may be interested in creating, and still allows access the dg_event event system -for anything requiring more complicated procedures. - -======================================================================== - - Death Gate Events - - -Table of Contents ------------------ -1. Event Functions and Event Objects -2. Functions for Manipulating Events -3. Steps to Create a New Event Type -4. An Example Event Type -5. Tips for Create Events - ------------------------------------------------------------------------------ -1. Event Functions and Event Objects - - -Each event type needs an: - - Event function: - - An event function is a function with a prototype of: - - long (event_function)(void *event_obj) - - This function is called when the event occurs, and is passed the event - object (see below). If the function returns a positive value, the event - is reenqueued using the return value as the number of pulses in the - future the event will reoccur. If the function returns 0 or a negative - number, the event is not reenqueued. If the event is not to be - reenqueued, the event function is responsible for freeing the event - object. There is a define: - - #define EVENTFUNC(name) long (name)(void *event_obj) - - to be used when declaring an event function. - - Event object: - - The event object is any structure with the fields to store any data - needed by the event function. The event object should not be a game - object (such as a struct char_data), since this object is freed when - events are canceled (see cancel_event() below). All unique data - contained by the object should be freed by a call of free(event_obj). - In other words, don't have event_obj include pointers to other structures - which aren't pointed to elsewhere. It is also not advisable to have - pointers in the event object unless the thing they point to has a pointer - to this event and cancels the event when it is freed. Passing NULL as an - event object is valid (providing the event function doesn't need any - data). - ----------------------------------------------------------------------------- -2. Functions for Manipulating Events - - -The prototypes for the interface functions for events are provided in -events.h. They are: - -void event_init(void); - This function initializes the event queue for all events. It is only - called once, at the initialization of the game. - -struct event *event_create(EVENTFUNC(*func), void *event_obj, long when); - This function creates a new event. At the current time plus 'when', - the function call - - func(event_obj); - - will be made. A pointer to the created event is returned. Never free() - the event returned. Use event_cancel instead if you want to get rid of it - prematurely. - -void event_cancel(struct event *event); - This function cancels an event currently in the queue. The event and the - event_obj are freed by this call. - -void event_process(void); - This function is called once each pulse to process any pending events. - It should not be used outside of the main loop. - -long event_time(struct event *event); - Given event, this function returns the number of pulses until the event - occurs. One example of a place it is used is to get the pulses left before - an object timer expires, so its current state can be saved and restored - later. - ------------------------------------------------------------------------------ -3. Steps to Create a New Event Type - -To add a new event type, you do not need to know anything about what's in -events.c, queue.c, or queue.h, the core of the event code. To create an -event type: - - 1. Declare an event object structure. - - 2. Create your event function. - - 3. Construct your event object, and call event_create() where needed. - - 4. Any place that the 'owner' of the event can be destroyed, call - event_cancel(). - ------------------------------------------------------------------------------- -4. An Example Event Type - - -Example event type: - - /* the event object for the sniff event */ - struct sniff_event_obj { - struct char_data *ch; - byte type; - }; - - - EVENTFUNC(sniff_event) - { - struct sniff_event_obj *seo = (struct sniff_event_obj *) event_obj; - struct char_data *ch, *victim; - - ch = seo->ch; - - GET_CHAR_SNIFF(ch) = NULL; - - if (type == SNIFF_COLD) - act("$n sniffs loudly.", FALSE, ch, NULL, NULL, TO_ROOM); - else - act("$n sniffs some cocaine.", FALSE, ch, NULL, NULL, TO_ROOM); - - act("You sniff.", FALSE, ch, NULL, NULL, TO_CHAR); - - if (--seo->severity <= 0) { - /* we're done with sniffing */ - free(event_obj); - } - else - return PULSE_SNIFF; - } - - - ACMD(do_sniff) - { - struct sniff_event_obj *sniff; - - CREATE(sniff, struct sniff_event_obj, 1); - sniff->ch = ch; - sniff->severity = 5; - if (GET_CLASS(ch) != CLASS_THIEF) - sniff->type = SNIFF_COLD; - else - sniff->type = SNIFF_COCAINE; - - GET_CHAR_SNIFF(ch) = event_create(sniff_event, sniff, PULSE_SNIFF); - - send_to_char(OK, ch); - } - - - void extract_char(struct char_data *ch) - { - - ... - - if (GET_CHAR_SNIFF(ch)) { - event_cancel(GET_CHAR_SNIFF(ch)); - GET_CHAR_SNIFF(ch) = NULL; - } - - ... - } - ------------------------------------------------------------------------------ -5. Tips for Create Events - - -Tips for creating events: - - o event_obj should always be freed (or reused) in the EVENTFUNC() - - o Any game object pointed to by event_obj should have a pointer to the - the event so it can cancel the event if it is extracted. - - o Any game object with pointers to an event should have the event pointer - set to NULL in EVENTFUNC and immediately following event_cancel(). - - o Any place a game object is extracted from the game, any events it points to - should be canceled and its pointer to the events set to NULL. diff --git a/lib/scripts/README.md b/lib/scripts/README.md new file mode 100644 index 0000000..753518d --- /dev/null +++ b/lib/scripts/README.md @@ -0,0 +1,162 @@ +Scripts In lib/scripts +====================== + +This directory contains Python and DSL-style scripts used by the game. Scripts +are attached to rooms, npc's, and objects via builder commands (e.g. `rset add script`, +`mset add script`, `oset add script`). The script file itself is the data format. + +Two Script Styles +----------------- + +1) DSL (no function definitions) + - If the file does NOT contain `def on_trigger`, it is treated as the DSL. + - The DSL supports Python-style `if/elif/else`, `for`, and `while` blocks. + - Actions are written as simple verbs or function-style calls: + - `move("n")` or `move("north")` + - `move(1200)` (room vnum path step, no closed doors) + - `emote("looks around warily")` + - `sleep(3)` + - `log("text with {npc.vnum}")` + - `do("say hello")` + - Command-style is also supported: + - `move "n"` + - `sleep 3` + - `log "hello {npc.vnum}"` + - NOTE: The DSL does not support custom variable assignments yet. Use the + full Python style if you need variables. + +2) Full Python (with `def on_trigger(event):`) + - If the file contains `def on_trigger`, it runs as normal Python. + - Use `mud.sleep(seconds)` to pause and resume later. + - You can use local variables, helpers, and full Python expressions. + +Event Context (Full Python) +--------------------------- + +`on_trigger(event)` receives a dict with: +- `event["self"]`: the entity this trigger is attached to (mob/object/room) +- `event["trigger"]`: info about the trigger (vnum, name, type, narg, etc.) +- `event["vars"]`: trigger variables (if any) + +Entity Data Model (DSL and Python) +---------------------------------- + +Every entity has these common properties: +- `entity.kind` -> kind id (mob/obj/room) +- `entity.uid` -> unique runtime id +- `entity.vnum` -> vnum (mob/obj/room) +- `entity.name` -> name/short for display + +Mob (NPC or PC) properties: +- `npc.health` / `npc.max_health` +- `npc.mana` / `npc.max_mana` +- `npc.stamina` / `npc.max_stamina` +- `npc.move` / `npc.max_move` (alias for stamina) +- `npc.class` / `npc.class_id` +- `npc.species` / `npc.species_id` +- `npc.is_pc` / `npc.is_npc` +- `npc.keyword` (NPC keywords; for PCs, name) +- `npc.room` -> room entity where the mob is located + - `npc.room.vnum` -> room vnum + - `npc.room.name` -> room name/short + +Object properties: +- `object.keyword` -> object keywords +- `object.oval` -> list of object values (oval[0] .. oval[NUM_OBJ_VAL_POSITIONS-1]) +- `object.room` -> room entity where the object is located + - `object.room.vnum` -> room vnum + - `object.room.name` -> room name/short + +Room properties: +- `room.contents` -> list of objects in the room +- `room.people` -> list of characters in the room +- `room.vnum` -> room vnum +- `room.name` -> room name/short + +Entity comparisons: +- `entity == "rat"` checks keyword match for mobs/objects. +- `entity == 123456` checks entity uid. +- `entity_a == entity_b` matches if kind+uid are equal. + +Available Methods +----------------- + +These are available in both DSL and Python (via the `mud` module). + +Core methods: +- `mud.do(command, actor=None)` + Execute a game command. If `actor` is a mob, it runs as that mob. If `actor` + is a room, it runs in that room context. +- `mud.emote(message, actor=None)` + Emote as a mob (actor is optional if the trigger is on a mob). +- `mud.move(direction, actor=None)` + Move a mob in a direction (e.g. "n", "south", "ne"). + You can also pass a room vnum to step toward a destination, but if it is blocked (eg. with a door), this will not work. +- `mud.sleep(seconds)` + Pause the script and resume later. +- `mud.roll("1d6")` + Roll dice. Supported: d4/d6/d8/d10/d12/d20/d100. +- `mud.log(message)` + Write to `log/script.log`. +- `mud.echo_room(room, message)` + Echo a message to a specific room. +- `mud.send_char(character, message)` + Send a message to a character. +- `mud.call_later(seconds, func, *args, **kwargs)` + Call a function later (Python scripts only). + +Convenience names in full Python scripts: +- `log("...")` is available as a shortcut for `mud.log(...)`. +- Direction strings are available as `mud.n`, `mud.s`, `mud.e`, `mud.w`, etc. + +Formatting / Expressions +------------------------ + +DSL supports Python-style blocks: +``` +while True: + if npc.stamina < 20: + rest + else: + stand +``` + +DSL logging supports f-string style with braces: +``` +log "rat {npc.vnum} in room {npc.room.vnum}" +``` + +In full Python scripts, use normal f-strings: +``` +log(f"rat {event['self'].vnum} in room {event['self'].room.vnum}") +``` + +Variables (Full Python) +----------------------- + +Use normal Python variables: +``` +def on_trigger(event): + room = event["self"] + count = 0 + while True: + count += 1 + mud.echo_room(room, f"tick {count}") + mud.sleep(30) +``` + +The DSL currently does not support custom variable assignment. If you need +variables, use full Python with `def on_trigger(event):`. + +Best Practices +-------------- +- Keep scripts small and focused. +- Use `log(...)` during development and remove or reduce noise later. +- Avoid infinite tight loops without `sleep`. +- Use comments generously (`# ...`). +- Prefer readable direction strings: `"north"`, `"west"`, etc. + +Sample: Room Echo Script +------------------------ + +See `sample_echo.py` in this directory for a ready-to-attach room script. diff --git a/lib/scripts/sample_echo.py b/lib/scripts/sample_echo.py new file mode 100644 index 0000000..e6f3566 --- /dev/null +++ b/lib/scripts/sample_echo.py @@ -0,0 +1,14 @@ +# Room echo script example (attach to a room trigger) + +def on_trigger(event): + # Room entity the script is attached to. + room = event["self"] + + while True: + # Pick between two messages using a dice roll. + if mud.roll("1d2") == 1: + mud.echo_room(room, "someone spills a drink at the bar") + else: + mud.echo_room(room, "a game of cards gets rowdy as a dwarf slams a fist on a table") + # Wait before the next echo. + mud.sleep(60) diff --git a/lib/scripts/sample_fountain.py b/lib/scripts/sample_fountain.py new file mode 100644 index 0000000..ffbc796 --- /dev/null +++ b/lib/scripts/sample_fountain.py @@ -0,0 +1,26 @@ +# Fountain refill script example (attach to an object trigger) + +def on_trigger(event): + # Object entity the script is attached to. + fountain = event["self"] + # Minimum refill amount per tick. + refill_amount = 1 + + while True: + # Ensure the fountain has object values to read. + if fountain and fountain.oval: + # oval[0] is capacity, oval[1] is current contents. + capacity = fountain.oval[0] + contains = fountain.oval[1] + if capacity > 0 and contains < capacity: + # Calculate the new fill amount without exceeding capacity. + new_amount = contains + refill_amount + if new_amount > capacity: + new_amount = capacity + # Update the fountain contents via osetval. + mud.do(f"osetval 1 {new_amount}", actor=fountain) + # Echo a drip message to the room when refilling. + if fountain.room: + mud.echo_room(fountain.room, f"water drips from the ceiling into {fountain.name}") + # Wait before attempting another refill. + mud.sleep(300) diff --git a/lib/scripts/sample_patrol.py b/lib/scripts/sample_patrol.py new file mode 100644 index 0000000..401411b --- /dev/null +++ b/lib/scripts/sample_patrol.py @@ -0,0 +1,39 @@ +# NPC patrol script example (attach to a mob trigger) + +# Track per-NPC patrol state by uid. +patrol_state = {} +# Ordered list of target room vnums for the patrol route. +patrol_route = [103, 105, 153, 149, 100] + +def on_trigger(event): + # The mob this trigger is attached to. + npc = event["self"] + # Unique runtime id used to track this NPC's state. + uid = npc.uid + # Lookup or create state for this NPC. + state = patrol_state.get(uid) + if state is None: + # Start at the first patrol target. + state = {"target": 0} + patrol_state[uid] = state + + # Current target room vnum. + target = patrol_route[state["target"]] + + # If already at target, advance to the next. + if npc.room.vnum == target: + state["target"] = (state["target"] + 1) % len(patrol_route) + target = patrol_route[state["target"]] + + # Move one step toward the target room vnum. + if not mud.move(target, actor=npc): + mud.log(f"sample_patrol: no path to {target}", actor=npc) + + # Optional flavor emotes at specific rooms. + if npc.room.vnum == 153: + mud.emote("looks around carefully", actor=npc) + elif npc.room.vnum == 149: + mud.emote("sniffs the air", actor=npc) + + # Sleep to schedule the next patrol tick. + mud.sleep(3) diff --git a/lib/world/mob/1.toml b/lib/world/mob/1.toml index 5095073..1597b59 100644 --- a/lib/world/mob/1.toml +++ b/lib/world/mob/1.toml @@ -434,6 +434,7 @@ flags = [8, 0, 0, 0] aff_flags = [0, 0, 0, 0] alignment = 0 mob_type = "enhanced" +triggers = [100] [mob.simple] level = 1 diff --git a/lib/world/rsv/1.toml b/lib/world/rsv/1.toml index 6b08619..653269c 100644 --- a/lib/world/rsv/1.toml +++ b/lib/world/rsv/1.toml @@ -1,6 +1,9 @@ [[room]] vnum = 100 -saved_at = 1759514843 +saved_at = 1769960339 + +[[room.mob]] +vnum = 104 [[room.mob]] vnum = 100 @@ -285,3 +288,11 @@ contents = [] vnum = 101 saved_at = 1767296996 +[[room]] +vnum = 132 +saved_at = 1769720941 + +[[room]] +vnum = 102 +saved_at = 1769960333 + diff --git a/lib/world/trg/0.toml b/lib/world/trg/0.toml index c125925..78738b7 100644 --- a/lib/world/trg/0.toml +++ b/lib/world/trg/0.toml @@ -5,7 +5,4 @@ attach_type = 0 flags = 65536 narg = 1 arglist = "" -commands = [ - "* No Script", -] - +script = "" diff --git a/lib/world/trg/1.toml b/lib/world/trg/1.toml index 60a49b1..a969c3b 100644 --- a/lib/world/trg/1.toml +++ b/lib/world/trg/1.toml @@ -1 +1,8 @@ -trigger = [] +[[trigger]] +vnum = 100 +name = "sample_patrol.py" +attach_type = 0 +flags = 8192 +narg = 100 +arglist = "" +script = "sample_patrol.py" diff --git a/lib/world/wld/1.toml b/lib/world/wld/1.toml index b4a4a19..61505f8 100644 --- a/lib/world/wld/1.toml +++ b/lib/world/wld/1.toml @@ -2,7 +2,7 @@ vnum = 100 name = "Caravan Gate" description = " Two large doors, each at least ten feet in height, make up the massive gate\nset into the walls that surround the city. Made of agafari wood, mekillot hide,\nrusted old studs and iron hinges, they provide access into the city for\ntravelers and traders alike. A massive stone block sits off to the side of the\nroad, which seems to have been carved to the exact size and shape of the gate\nitself. Caravans and wagons can be seen passing through at all hours of the day\nwhen the gates are open, with some pausing just inside the gate to unload while\nothers continue on their way to the inner city.\n To the north and south, Wall Road continues onward. To the west, the main\nthoroughfare of Caravan Way passes by several buildings.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 1 [[room.exit]] @@ -12,6 +12,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 146 + [[room.exit]] dir = 2 description = "" @@ -19,6 +20,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 135 + [[room.exit]] dir = 3 description = "" @@ -31,7 +33,7 @@ to_room = 101 vnum = 101 name = "Caravan Way" description = " This broad avenue is wide enough for several mounted riders to easily\nnavigate, or even a pair of wagons to pass by without incident. Buildings line\nthe road offering services to travelers and visitors of the city. Street\nvendors line the sides, with an occasional shout to draw attention to their\nwares. The route through the city itself is made up of brick, though several\nappear cracked and broken when given a thorough inspection.\n Caravan Way continues to the west and east. A placard with a stylized\neyeball, ear, and lips hangs as a standard above a building to the north,\nwhile the smell of a stable wafts from the south.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -41,6 +43,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 131 + [[room.exit]] dir = 1 description = "" @@ -48,6 +51,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 100 + [[room.exit]] dir = 2 description = "" @@ -55,6 +59,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 136 + [[room.exit]] dir = 3 description = "" @@ -67,7 +72,7 @@ to_room = 102 vnum = 102 name = "Caravan Way" description = " This broad avenue is wide enough for several mounted riders to easily\nnavigate, or even a pair of wagons to pass by without incident. Buildings line\nthe road offering services to travelers and visitors of the city. Street\nvendors line the sides, with an occasional shout to draw attention to their\nwares. The route through the city itself is made up of brick, though several\nappear cracked and broken when given a thorough inspection.\n Caravan Way continues to the west and east. To the south is a two-story\nbuilding with a sign hanging above the entrance of crossed swords.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -77,6 +82,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 101 + [[room.exit]] dir = 2 description = "" @@ -84,6 +90,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 132 + [[room.exit]] dir = 3 description = "" @@ -96,7 +103,7 @@ to_room = 103 vnum = 103 name = "Caravan Way" description = " This broad avenue is wide enough for several mounted riders to easily\nnavigate, or even a pair of wagons to pass by without incident. Buildings line\nthe road offering services to travelers and visitors of the city. Street\nvendors line the sides, with an occasional shout to draw attention to their\nwares. The route through the city itself is made up of brick, though several\nappear cracked and broken when given a thorough inspection.\n Caravan Way continues to the north and east. To the south, a side street\npasses between some buildings.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -106,6 +113,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 104 + [[room.exit]] dir = 1 description = "" @@ -113,6 +121,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 102 + [[room.exit]] dir = 2 description = "" @@ -135,6 +144,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 103 + [[room.exit]] dir = 3 description = "" @@ -157,6 +167,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 155 + [[room.exit]] dir = 1 description = "" @@ -164,6 +175,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 104 + [[room.exit]] dir = 3 description = "" @@ -186,6 +198,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 105 + [[room.exit]] dir = 3 description = "" @@ -208,6 +221,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 106 + [[room.exit]] dir = 2 description = "" @@ -215,6 +229,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 108 + [[room.exit]] dir = 3 description = "" @@ -237,6 +252,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 107 + [[room.exit]] dir = 2 description = "" @@ -244,6 +260,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 134 + [[room.exit]] dir = 3 description = "" @@ -266,6 +283,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 133 + [[room.exit]] dir = 1 description = "" @@ -273,6 +291,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 108 + [[room.exit]] dir = 3 description = "" @@ -295,6 +314,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 109 + [[room.exit]] dir = 3 description = "" @@ -317,6 +337,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 122 + [[room.exit]] dir = 1 description = "" @@ -324,6 +345,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 110 + [[room.exit]] dir = 2 description = "" @@ -346,6 +368,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 111 + [[room.exit]] dir = 1 description = "" @@ -353,6 +376,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 128 + [[room.exit]] dir = 3 description = "" @@ -375,6 +399,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 123 + [[room.exit]] dir = 1 description = "" @@ -382,6 +407,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 112 + [[room.exit]] dir = 2 description = "" @@ -389,6 +415,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 160 + [[room.exit]] dir = 3 description = "" @@ -411,6 +438,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 124 + [[room.exit]] dir = 1 description = "" @@ -418,6 +446,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 113 + [[room.exit]] dir = 3 description = "" @@ -440,6 +469,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 116 + [[room.exit]] dir = 1 description = "" @@ -447,6 +477,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 114 + [[room.exit]] dir = 3 description = "" @@ -469,6 +500,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 117 + [[room.exit]] dir = 2 description = "" @@ -476,6 +508,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 115 + [[room.exit]] dir = 3 description = "" @@ -498,6 +531,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 118 + [[room.exit]] dir = 1 description = "" @@ -505,6 +539,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 125 + [[room.exit]] dir = 2 description = "" @@ -512,6 +547,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 116 + [[room.exit]] dir = 3 description = "" @@ -534,6 +570,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 182 + [[room.exit]] dir = 1 description = "" @@ -541,6 +578,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 119 + [[room.exit]] dir = 2 description = "" @@ -563,6 +601,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 120 + [[room.exit]] dir = 3 description = "" @@ -585,6 +624,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 127 + [[room.exit]] dir = 1 description = "" @@ -592,6 +632,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 121 + [[room.exit]] dir = 2 description = "" @@ -599,6 +640,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 126 + [[room.exit]] dir = 3 description = "" @@ -621,6 +663,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 198 + [[room.exit]] dir = 2 description = "" @@ -628,6 +671,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 122 + [[room.exit]] dir = 3 description = "" @@ -650,6 +694,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 121 + [[room.exit]] dir = 2 description = "" @@ -782,7 +827,7 @@ to_room = 117 vnum = 131 name = "Draqoman Station" description = " This large building has a variety of tables and seats available in it, and\nseems to be a meeting point for those new to the city. The walls are lined with\nsconces to provide light throughout the evening, and barrels of water can be\nseen in the northern side of the room. Conversations can be heard in hushed\ntones, while others may be more boisterous as if between old friends. A crude\nmap of the city can be seen upon one wall, giving the impression this would be a\ngood place to learn more about where certain landmarks are.\n To the south lies Caravan Way, while to the east Wall Road can be seen.\n" -flags = [131080, 0, 0, 0] +flags = [163848, 0, 0, 0] sector = 0 [[room.exit]] @@ -792,6 +837,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 146 + [[room.exit]] dir = 2 description = "" @@ -804,7 +850,7 @@ to_room = 101 vnum = 132 name = "Grik's Weapons" description = " The walls of this building have been made out of uneven blocks of sandstone.\nPiled haphazardly on top of each other, they somehow remain stable despite\nlooking precarious and ready to fall anytime. Upon the walls are hooks that\nhave weapons placed on them, as well as trophies from gladiator tournaments. A\ncounter sits in the southwest corner of the room, and a stairway behind it leads\nupward to a trapdoor in the ceiling.\n Outside to the north lies Caravan Way.\n" -flags = [0, 0, 0, 0] +flags = [32776, 0, 0, 0] sector = 0 [[room.exit]] @@ -829,6 +875,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 107 + [[room.exit]] dir = 2 description = "" @@ -851,6 +898,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 108 + [[room.exit]] dir = 4 description = "" @@ -863,7 +911,7 @@ to_room = 200 vnum = 135 name = "Wall Road" description = " Following along the walls of the city is a small road only large enough for a\npair of mounted riders to traverse side by side. The walls are thick and\nprimarily made of adobe brick, rising at least ten feet. The occasional figure\ncan be seen walking the walls, keeping an eye on the horizon. The buildings\nnear the gate are small and unassuming. Most have no windows and a simple\nwooden door.\n Wall Road continues to the south, while Caravan Gate can be seen to the \nnorth. To the west the smell of a stables wafts out onto the road.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -873,6 +921,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 100 + [[room.exit]] dir = 2 description = "" @@ -880,6 +929,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 137 + [[room.exit]] dir = 3 description = "" @@ -892,7 +942,7 @@ to_room = 136 vnum = 136 name = "Messenger's Mount" description = " This two story building eminates a smell from the multitude of beasts within\nthat is all that is needed to knowing that this is a stable. Various pens have\nbeen set up here, holding kanks, erdlus, crodlus, and occasionally other more\nuncommon livestock. Traders and caravans come and go, leaving and retrieving\nmounts. Animal handlers can be seen moving about with food and water, as well\nas shoveling dung to be taken away.\n A small staircase is upon the southern wall and heads upward to the\nsecond floor. To the north lies Caravan Way, and to the east Wall Road can\nbe seen.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -902,6 +952,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 101 + [[room.exit]] dir = 1 description = "" @@ -914,7 +965,7 @@ to_room = 135 vnum = 137 name = "Wall Road" description = " Following along the walls of the city is a small road only large enough for a\npair of mounted riders to traverse side by side. The walls are thick and\nprimarily made of adobe brick, rising at least ten feet. The occasional figure\ncan be seen walking the walls, keeping an eye on the horizon. The buildings\nnear the gate are small and unassuming. Most have no windows and a simple\nwooden door.\n Wall Road continues to the north and south. Buildings rise up to the west,\nwhile the city walls are to the east.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -924,6 +975,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 135 + [[room.exit]] dir = 2 description = "" @@ -946,6 +998,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 137 + [[room.exit]] dir = 2 description = "" @@ -968,6 +1021,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 138 + [[room.exit]] dir = 3 description = "" @@ -990,6 +1044,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 139 + [[room.exit]] dir = 3 description = "" @@ -1012,6 +1067,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 140 + [[room.exit]] dir = 3 description = "" @@ -1034,6 +1090,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 143 + [[room.exit]] dir = 1 description = "" @@ -1056,6 +1113,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 144 + [[room.exit]] dir = 2 description = "" @@ -1078,6 +1136,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 145 + [[room.exit]] dir = 2 description = "" @@ -1085,6 +1144,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 143 + [[room.exit]] dir = 3 description = "" @@ -1107,6 +1167,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 103 + [[room.exit]] dir = 2 description = "" @@ -1119,7 +1180,7 @@ to_room = 144 vnum = 146 name = "Wall Road" description = " Following along the walls of the city is a small road only large enough for a\npair of mounted riders to traverse side by side. The walls are thick and\nprimarily made of adobe brick, rising at least ten feet. The occasional figure\ncan be seen walking the walls, keeping an eye on the horizon. The buildings\nnear the gate are small and unassuming. Most have no windows and a simple\nwooden door.\n Wall Road continues to the north, while the Caravan Gate can be seen to\nthe south. A placard with a stylized eyeball, ears, and lips hangs as a\nstandard above a building to the west.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -1129,6 +1190,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 147 + [[room.exit]] dir = 2 description = "" @@ -1136,6 +1198,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 100 + [[room.exit]] dir = 3 description = "" @@ -1148,7 +1211,7 @@ to_room = 131 vnum = 147 name = "Wall Road" description = " Following along the walls of the city is a small road only large enough for a\npair of mounted riders to traverse side by side. The walls are thick and\nprimarily made of adobe brick, rising at least ten feet. The occasional figure\ncan be seen walking the walls, keeping an eye on the horizon. The buildings\nnear the gate are small and unassuming. Most have no windows and a simple\nwooden door.\n Wall Road continues to the north and south. Buildings rise up to the west,\nwhile the city walls are to the east.\n" -flags = [0, 0, 0, 0] +flags = [32768, 0, 0, 0] sector = 0 [[room.exit]] @@ -1158,6 +1221,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 148 + [[room.exit]] dir = 2 description = "" @@ -1180,6 +1244,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 149 + [[room.exit]] dir = 2 description = "" @@ -1202,6 +1267,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 148 + [[room.exit]] dir = 3 description = "" @@ -1224,6 +1290,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 149 + [[room.exit]] dir = 2 description = "" @@ -1231,6 +1298,7 @@ keyword = "door" exit_info = 1 key = 0 to_room = 157 + [[room.exit]] dir = 3 description = "" @@ -1253,6 +1321,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 150 + [[room.exit]] dir = 3 description = "" @@ -1275,6 +1344,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 151 + [[room.exit]] dir = 2 description = "" @@ -1282,6 +1352,7 @@ keyword = "door" exit_info = 9 key = 0 to_room = 156 + [[room.exit]] dir = 3 description = "" @@ -1304,6 +1375,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 152 + [[room.exit]] dir = 2 description = "" @@ -1326,6 +1398,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 153 + [[room.exit]] dir = 2 description = "" @@ -1333,6 +1406,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 155 + [[room.exit]] dir = 3 description = "" @@ -1355,6 +1429,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 154 + [[room.exit]] dir = 2 description = "" @@ -1452,6 +1527,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 115 + [[room.exit]] dir = 3 description = "" @@ -1474,6 +1550,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 163 + [[room.exit]] dir = 1 description = "" @@ -1481,6 +1558,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 161 + [[room.exit]] dir = 2 description = "" @@ -1503,6 +1581,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 164 + [[room.exit]] dir = 2 description = "" @@ -1525,6 +1604,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 165 + [[room.exit]] dir = 2 description = "" @@ -1547,6 +1627,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 178 + [[room.exit]] dir = 2 description = "" @@ -1554,6 +1635,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 164 + [[room.exit]] dir = 3 description = "" @@ -1576,6 +1658,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 165 + [[room.exit]] dir = 3 description = "" @@ -1598,6 +1681,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 166 + [[room.exit]] dir = 3 description = "" @@ -1620,6 +1704,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 167 + [[room.exit]] dir = 3 description = "" @@ -1642,6 +1727,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 168 + [[room.exit]] dir = 2 description = "" @@ -1664,6 +1750,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 169 + [[room.exit]] dir = 2 description = "" @@ -1686,6 +1773,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 170 + [[room.exit]] dir = 2 description = "" @@ -1708,6 +1796,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 171 + [[room.exit]] dir = 2 description = "" @@ -1730,6 +1819,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 172 + [[room.exit]] dir = 1 description = "" @@ -1752,6 +1842,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 175 + [[room.exit]] dir = 3 description = "" @@ -1774,6 +1865,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 176 + [[room.exit]] dir = 3 description = "" @@ -1796,6 +1888,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 177 + [[room.exit]] dir = 3 description = "" @@ -1818,6 +1911,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 162 + [[room.exit]] dir = 3 description = "" @@ -1840,6 +1934,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 179 + [[room.exit]] dir = 2 description = "" @@ -1862,6 +1957,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 180 + [[room.exit]] dir = 2 description = "" @@ -1869,6 +1965,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 178 + [[room.exit]] dir = 3 description = "" @@ -1891,6 +1988,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 181 + [[room.exit]] dir = 3 description = "" @@ -1913,6 +2011,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 183 + [[room.exit]] dir = 2 description = "" @@ -1920,6 +2019,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 182 + [[room.exit]] dir = 3 description = "" @@ -1942,6 +2042,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 181 + [[room.exit]] dir = 2 description = "" @@ -1964,6 +2065,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 194 + [[room.exit]] dir = 2 description = "" @@ -1986,6 +2088,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 179 + [[room.exit]] dir = 3 description = "" @@ -2008,6 +2111,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 186 + [[room.exit]] dir = 1 description = "" @@ -2015,6 +2119,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 184 + [[room.exit]] dir = 3 description = "" @@ -2037,6 +2142,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 192 + [[room.exit]] dir = 1 description = "" @@ -2044,6 +2150,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 193 + [[room.exit]] dir = 2 description = "" @@ -2051,6 +2158,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 185 + [[room.exit]] dir = 3 description = "" @@ -2073,6 +2181,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 191 + [[room.exit]] dir = 1 description = "" @@ -2080,6 +2189,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 186 + [[room.exit]] dir = 2 description = "" @@ -2087,6 +2197,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 188 + [[room.exit]] dir = 3 description = "" @@ -2109,6 +2220,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 187 + [[room.exit]] dir = 1 description = "" @@ -2116,6 +2228,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 185 + [[room.exit]] dir = 3 description = "" @@ -2198,6 +2311,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 186 + [[room.exit]] dir = 4 description = "A trapdoor in the ceiling can be passed through by climbing a set of stairs.\n" @@ -2220,6 +2334,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 195 + [[room.exit]] dir = 3 description = "" @@ -2242,6 +2357,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 196 + [[room.exit]] dir = 3 description = "" @@ -2264,6 +2380,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 199 + [[room.exit]] dir = 2 description = "" @@ -2271,6 +2388,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 197 + [[room.exit]] dir = 3 description = "" @@ -2293,6 +2411,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 196 + [[room.exit]] dir = 2 description = "" @@ -2315,6 +2434,7 @@ keyword = "" exit_info = 0 key = 0 to_room = 197 + [[room.exit]] dir = 2 description = "" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2869edc..8f5b205 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,8 @@ file(GLOB CIRCLE_SOURCES "*.c" ) +list(FILTER CIRCLE_SOURCES EXCLUDE REGEX ".*/dg_.*\\.c$") + add_executable(circle ${CIRCLE_SOURCES}) target_sources(circle PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlc99/toml.c) target_include_directories(circle PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlc99) diff --git a/src/Makefile b/src/Makefile index bbf440b..d50b87c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -17,11 +17,15 @@ PROFILE = BINDIR = ../bin -CFLAGS = -g -O2 $(MYFLAGS) $(PROFILE) -I../third_party/tomlc99 +PYTHON_CONFIG ?= python3.12-config +PY_CFLAGS := $(shell $(PYTHON_CONFIG) --includes 2>/dev/null | tr ' ' '\n' | awk '!seen[$$0]++' | tr '\n' ' ') +PY_LIBS := $(shell $(PYTHON_CONFIG) --embed --ldflags 2>/dev/null) -LIBS = -lcrypt +CFLAGS = -g -O2 $(MYFLAGS) $(PROFILE) -I../third_party/tomlc99 $(PY_CFLAGS) -SRCFILES := $(shell ls *.c | sort) ../third_party/tomlc99/toml.c +LIBS = -lcrypt $(PY_LIBS) + +SRCFILES := $(shell ls *.c | grep -v '^dg_' | sort) ../third_party/tomlc99/toml.c OBJFILES := $(patsubst %.c,%.o,$(SRCFILES)) default: all diff --git a/src/Makefile.amiga b/src/Makefile.amiga index 1ca73cc..f4a18bd 100644 --- a/src/Makefile.amiga +++ b/src/Makefile.amiga @@ -117,7 +117,7 @@ act.wizard.o: act.wizard.c conf.h sysdep.h structs.h utils.h comm.h \ $(CC) -c $(CFLAGS) act.wizard.c set.o: set.c conf.h sysdep.h structs.h utils.h comm.h \ interpreter.h handler.h db.h constants.h genolc.h genwld.h genzon.h \ - oasis.h improved-edit.h modify.h genobj.h dg_scripts.h set.h + oasis.h improved-edit.h modify.h genobj.h set.h $(CC) -c $(CFLAGS) set.c ban.o: ban.c conf.h sysdep.h structs.h utils.h comm.h interpreter.h handler.h db.h $(CC) -c $(CFLAGS) ban.c diff --git a/src/Makefile.arc b/src/Makefile.arc index 6c68962..e9fc2da 100644 --- a/src/Makefile.arc +++ b/src/Makefile.arc @@ -76,7 +76,7 @@ act.o.wizard: act.c.wizard h.conf h.sysdep h.structs \ o.set: c.set h.conf h.sysdep h.structs \ h.utils h.comm h.interpreter h.handler \ h.db h.constants h.genobj h.genolc h.genwld h.genzon h.oasis \ - h.improved-edit h.modify h.dg_scripts h.set + h.improved-edit h.modify h.set $(CC) -c $(CFLAGS) c.set -o o.set o.ban: c.ban h.conf h.sysdep h.structs h.utils h.comm h.interpreter h.handler h.db $(CC) -c $(CFLAGS) c.ban @@ -159,7 +159,7 @@ o.weather: c.weather h.conf h.sysdep h.structs h.utils h.comm h.handler \ h.interpreter h.db $(CC) -c $(CFLAGS) c.weather o.players: c.players h.conf h.sysdep h.structs h.utils h.db h.handler \ - h.pfdefaults h.dg_scripts h.comm h.interpreter h.genolc h.config h.spells + h.pfdefaults h.comm h.interpreter h.genolc h.config h.spells $(CC) -c $(CFLAGS) c.players o.quest: c.quest h.conf h.sysdep h.structs h.utils h.interpreter h.handler \ h.comm h.db h.screen h.quest diff --git a/src/Makefile.in b/src/Makefile.in index e0517be..4672c7f 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -16,11 +16,15 @@ PROFILE = BINDIR = ../bin -CFLAGS = @CFLAGS@ $(MYFLAGS) $(PROFILE) -I../third_party/tomlc99 +PYTHON_CONFIG ?= python3.12-config +PY_CFLAGS := $(shell $(PYTHON_CONFIG) --includes 2>/dev/null | tr ' ' '\n' | awk '!seen[$$0]++' | tr '\n' ' ') +PY_LIBS := $(shell $(PYTHON_CONFIG) --embed --ldflags 2>/dev/null) -LIBS = @LIBS@ @CRYPTLIB@ @NETLIB@ +CFLAGS = @CFLAGS@ $(MYFLAGS) $(PROFILE) -I../third_party/tomlc99 $(PY_CFLAGS) -SRCFILES := $(shell ls *.c | sort) ../third_party/tomlc99/toml.c +LIBS = @LIBS@ @CRYPTLIB@ @NETLIB@ $(PY_LIBS) + +SRCFILES := $(shell ls *.c | grep -v '^dg_' | sort) ../third_party/tomlc99/toml.c OBJFILES := $(patsubst %.c,%.o,$(SRCFILES)) default: all diff --git a/src/Makefile.lcc b/src/Makefile.lcc index a128ba6..fbd2541 100644 --- a/src/Makefile.lcc +++ b/src/Makefile.lcc @@ -512,7 +512,6 @@ SET_C=\ $(DISTDIR)\src\oasis.h\ $(DISTDIR)\src\improved-edit.h\ $(DISTDIR)\src\modify.h\ - $(DISTDIR)\src\dg_scripts.h\ $(DISTDIR)\src\set.h\ set.obj: $(SET_C) $(DISTDIR)\src\set.c diff --git a/src/Makefile.msvc b/src/Makefile.msvc index 60d54e9..bc40669 100644 --- a/src/Makefile.msvc +++ b/src/Makefile.msvc @@ -39,15 +39,14 @@ OBJFILES = comm.obj act.comm.obj act.informative.obj act.movement.obj act.item.o asciimap.obj act.offensive.obj act.other.obj act.social.obj act.wizard.obj \ set.obj \ ban.obj boards.obj castle.obj class.obj config.obj constants.obj db.obj \ -dg_event.obj dg_scripts.obj dg_triggers.obj fight.obj genolc.obj graph.obj \ +fight.obj genolc.obj graph.obj \ handler.obj house.obj ibt.obj interpreter.obj limits.obj lists.obj magic.obj \ mail.obj msgedit.obj mobact.obj modify.obj mud_event.obj oasis.obj oasis_copy.obj \ oasis_delete.obj oasis_list.obj objsave.obj protocol.obj shop.obj spec_assign.obj \ spec_procs.obj spell_parser.obj improved-edit.obj spells.obj utils.obj weather.obj \ random.obj players.obj quest.obj qedit.obj genqst.obj aedit.obj cedit.obj hedit.obj \ medit.obj oedit.obj qedit.obj redit.obj sedit.obj tedit.obj zedit.obj \ -dg_comm.obj dg_db_scripts.obj dg_handler.obj dg_misc.obj dg_mobcmd.obj dg_objcmd.obj \ -dg_olc.obj dg_variables.obj dg_wldcmd.obj genmob.obj genobj.obj genshp.obj genwld.obj \ +genmob.obj genobj.obj genshp.obj genwld.obj \ genzon.obj prefedit.obj default: circle.exe @@ -86,7 +85,7 @@ act.wizard.obj: act.wizard.c conf.h sysdep.h structs.h utils.h comm.h \ $(CC) -c $(CFLAGS) act.wizard.c set.obj: set.c conf.h sysdep.h structs.h utils.h comm.h \ interpreter.h handler.h db.h constants.h genolc.h genwld.h genzon.h \ - oasis.h improved-edit.h modify.h genobj.h dg_scripts.h set.h + oasis.h improved-edit.h modify.h genobj.h set.h $(CC) -c $(CFLAGS) set.c ban.obj: ban.c conf.h sysdep.h structs.h utils.h comm.h interpreter.h handler.h db.h $(CC) -c $(CFLAGS) ban.c diff --git a/src/Makefile.os2 b/src/Makefile.os2 index c22f256..3fd950e 100644 --- a/src/Makefile.os2 +++ b/src/Makefile.os2 @@ -119,7 +119,7 @@ act.wizard.o: act.wizard.c conf.h sysdep.h structs.h utils.h comm.h \ $(CC) -c $(CFLAGS) act.wizard.c set.o: set.c conf.h sysdep.h structs.h utils.h comm.h \ interpreter.h handler.h db.h constants.h genolc.h genwld.h genzon.h \ - oasis.h improved-edit.h modify.h genobj.h dg_scripts.h set.h + oasis.h improved-edit.h modify.h genobj.h set.h $(CC) -c $(CFLAGS) set.c ban.o: ban.c conf.h sysdep.h structs.h utils.h comm.h interpreter.h handler.h db.h $(CC) -c $(CFLAGS) ban.c diff --git a/src/Smakefile b/src/Smakefile index 5635a98..73b287c 100644 --- a/src/Smakefile +++ b/src/Smakefile @@ -111,7 +111,7 @@ act.wizard.o: act.wizard.c conf.h sysdep.h structs.h utils.h comm.h \ $(CC) $(CFLAGS) act.wizard.c set.o: set.c conf.h sysdep.h structs.h utils.h comm.h \ interpreter.h handler.h db.h constants.h genolc.h genwld.h genzon.h \ - oasis.h improved-edit.h modify.h genobj.h dg_scripts.h set.h + oasis.h improved-edit.h modify.h genobj.h set.h $(CC) $(CFLAGS) set.c ban.o: ban.c conf.h sysdep.h structs.h utils.h comm.h interpreter.h handler.h db.h $(CC) $(CFLAGS) ban.c diff --git a/src/act.comm.c b/src/act.comm.c index de78d48..6a38b24 100644 --- a/src/act.comm.c +++ b/src/act.comm.c @@ -20,7 +20,7 @@ #include "constants.h" #include "spells.h" #include "improved-edit.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "act.h" #include "modify.h" #include diff --git a/src/act.informative.c b/src/act.informative.c index 4956ccb..fc8f2fd 100644 --- a/src/act.informative.c +++ b/src/act.informative.c @@ -19,7 +19,7 @@ #include "spells.h" #include "screen.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "mud_event.h" #include "mail.h" /**< For the has_mail function */ #include "act.h" diff --git a/src/act.item.c b/src/act.item.c index 9c753cc..4eedfd9 100644 --- a/src/act.item.c +++ b/src/act.item.c @@ -18,7 +18,7 @@ #include "db.h" #include "spells.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "oasis.h" #include "act.h" #include "quest.h" diff --git a/src/act.movement.c b/src/act.movement.c index 295cae4..56f692f 100644 --- a/src/act.movement.c +++ b/src/act.movement.c @@ -19,7 +19,7 @@ #include "spells.h" #include "house.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "act.h" #include "fight.h" #include "oasis.h" /* for buildwalk */ diff --git a/src/act.other.c b/src/act.other.c index 7c67feb..0b9d996 100644 --- a/src/act.other.c +++ b/src/act.other.c @@ -20,7 +20,7 @@ #include "screen.h" #include "house.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "act.h" #include "spec_procs.h" #include "class.h" diff --git a/src/act.wizard.c b/src/act.wizard.c index 6f8ef80..81fd754 100644 --- a/src/act.wizard.c +++ b/src/act.wizard.c @@ -21,7 +21,7 @@ #include "screen.h" #include "constants.h" #include "oasis.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "shop.h" #include "act.h" #include "genzon.h" /* for real_zone_by_thing */ @@ -355,13 +355,14 @@ static void stat_format_script_triggers(struct script_data *sc, char *buf, size_ if (len) stat_appendf(buf, buf_size, &len, "\n"); - stat_appendf(buf, buf_size, &len, "#%d %s (%s %s) narg=%d arg=%s", + stat_appendf(buf, buf_size, &len, "#%d %s (%s %s) narg=%d arg=%s script=%s", GET_TRIG_VNUM(t), GET_TRIG_NAME(t), attach, type_buf, GET_TRIG_NARG(t), - (GET_TRIG_ARG(t) && *GET_TRIG_ARG(t)) ? GET_TRIG_ARG(t) : "None"); + (GET_TRIG_ARG(t) && *GET_TRIG_ARG(t)) ? GET_TRIG_ARG(t) : "None", + (t->script && *t->script) ? t->script : "None"); if (GET_TRIG_WAIT(t)) stat_appendf(buf, buf_size, &len, " [wait]"); diff --git a/src/asciimap.c b/src/asciimap.c index 0039e51..cb65f00 100644 --- a/src/asciimap.c +++ b/src/asciimap.c @@ -20,7 +20,7 @@ #include "spells.h" #include "house.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "asciimap.h" /****************************************************************************** diff --git a/src/comm.c b/src/comm.c index 065da60..5ec9dac 100644 --- a/src/comm.c +++ b/src/comm.c @@ -69,8 +69,9 @@ #include "house.h" #include "oasis.h" #include "genolc.h" -#include "dg_scripts.h" -#include "dg_event.h" +#include "py_triggers.h" +#include "py_event.h" +#include "py_scripts.h" #include "screen.h" /* to support the gemote act type command */ #include "constants.h" /* For mud versions */ #include "boards.h" @@ -513,6 +514,8 @@ static void init_game(ush_int local_port) /* set up hash table for find_char() */ init_lookup_table(); + python_scripts_init(); + boot_db(); #if defined(CIRCLE_UNIX) || defined(CIRCLE_MACINTOSH) @@ -530,6 +533,8 @@ static void init_game(ush_int local_port) game_loop(mother_desc); + python_scripts_shutdown(); + Crash_save_all(); log("Closing all sockets."); diff --git a/src/db.c b/src/db.c index baed3e7..9480350 100644 --- a/src/db.c +++ b/src/db.c @@ -22,8 +22,8 @@ #include "constants.h" #include "oasis.h" #include "species.h" -#include "dg_scripts.h" -#include "dg_event.h" +#include "py_triggers.h" +#include "py_event.h" #include "act.h" #include "ban.h" #include "spec_procs.h" @@ -1957,6 +1957,22 @@ static void parse_mobile_toml(toml_table_t *mob_tab) if (v.ok) add_proto_trigger(&mob_proto[i], MOB_TRIGGER, (int)v.u.i); } + } else { + toml_table_t *enh_tab = toml_table_in(mob_tab, "enhanced"); + toml_table_t *save_tab = NULL; + if (enh_tab) + save_tab = toml_table_in(enh_tab, "saving_throws"); + if (save_tab) { + toml_array_t *tarr = toml_array_in(save_tab, "triggers"); + if (tarr) { + int n = toml_array_nelem(tarr); + for (j = 0; j < n; j++) { + toml_datum_t v = toml_int_at(tarr, j); + if (v.ok) + add_proto_trigger(&mob_proto[i], MOB_TRIGGER, (int)v.u.i); + } + } + } } mob_proto[i].aff_abils = mob_proto[i].real_abils; @@ -2143,28 +2159,12 @@ static void parse_trigger_toml(toml_table_t *trig_tab) trig->trigger_type = (long)toml_get_int_default(trig_tab, "flags", 0); trig->narg = toml_get_int_default(trig_tab, "narg", 0); trig->arglist = toml_get_string_dup(trig_tab, "arglist"); + trig->script = toml_get_string_dup(trig_tab, "script"); - arr = toml_array_in(trig_tab, "commands"); - if (arr) { - n = toml_array_nelem(arr); - for (i = 0; i < n; i++) { - toml_datum_t v = toml_string_at(arr, i); - if (!v.ok || !v.u.s) - continue; - if (!trig->cmdlist) { - CREATE(trig->cmdlist, struct cmdlist_element, 1); - trig->cmdlist->cmd = strdup(v.u.s); - trig->cmdlist->next = NULL; - cle = trig->cmdlist; - } else { - CREATE(cle->next, struct cmdlist_element, 1); - cle = cle->next; - cle->cmd = strdup(v.u.s); - cle->next = NULL; - } - free(v.u.s); - } - } + (void)arr; + (void)i; + (void)n; + (void)cle; trig_index[top_of_trigt++] = t_index; } diff --git a/src/db.h b/src/db.h index d2d3ec4..ab21cd3 100644 --- a/src/db.h +++ b/src/db.h @@ -35,6 +35,7 @@ #define LIB_PLRVARS ":plrvars:" #define LIB_PLRFILES ":plrfiles:" #define LIB_ACCTFILES ":acctfiles:" +#define LIB_SCRIPTS ":scripts:" #define LIB_HOUSE ":house:" #define SLASH ":" #elif defined(CIRCLE_AMIGA) || defined(CIRCLE_UNIX) || defined(CIRCLE_WINDOWS) || defined(CIRCLE_ACORN) || defined(CIRCLE_VMS) @@ -49,6 +50,7 @@ #define LIB_HOUSE "house/" #define LIB_PLRFILES "plrfiles/" #define LIB_ACCTFILES "acctfiles/" +#define LIB_SCRIPTS "scripts/" #define SLASH "/" #else #error "Unknown path components." @@ -85,6 +87,7 @@ #define ZON_PREFIX LIB_WORLD"zon"SLASH /* zon defs & command tables */ #define SHP_PREFIX LIB_WORLD"shp"SLASH /* shop definitions */ #define TRG_PREFIX LIB_WORLD"trg"SLASH /* trigger files */ +#define SCRIPTS_PREFIX LIB_SCRIPTS /* python scripts */ #define HLP_PREFIX LIB_TEXT"help"SLASH /* Help files */ #define QST_PREFIX LIB_WORLD"qst"SLASH /* quest files */ @@ -128,6 +131,7 @@ #define BADPWS_LOGFILE PREFIX_LOGFILE"badpws" #define OLC_LOGFILE PREFIX_LOGFILE"olc" #define TRIGGER_LOGFILE PREFIX_LOGFILE"trigger" +#define SCRIPT_LOGFILE "../log/script.log" /**/ /* END: Assumed default locations for logfiles, mainly used in do_file. */ diff --git a/src/dg_variables.c b/src/dg_variables.c deleted file mode 100644 index 8fade8b..0000000 --- a/src/dg_variables.c +++ /dev/null @@ -1,1646 +0,0 @@ -/************************************************************************** -* File: dg_variables.c Part of tbaMUD * -* Usage: Contains the functions dealing with variable substitution. * -* * -* $Author: Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00 $ * -* $Revision: 1.0.14 $ * -**************************************************************************/ - -#include "conf.h" -#include "sysdep.h" -#include "structs.h" -#include "dg_scripts.h" -#include "utils.h" -#include "comm.h" -#include "interpreter.h" -#include "handler.h" -#include "dg_event.h" -#include "db.h" -#include "fight.h" -#include "screen.h" -#include "constants.h" -#include "spells.h" -#include "oasis.h" -#include "class.h" -#include "quest.h" -#include "act.h" -#include "genobj.h" - -/* Utility functions */ - -/* Thanks to James Long for his assistance in plugging the memory leak that - * used to be here. - Welcor */ -/* Adds a variable with given name and value to trigger. */ -void add_var(struct trig_var_data **var_list, const char *name, const char *value, long id) -{ - struct trig_var_data *vd; - - if (strchr(name, '.')) { - log("add_var() : Attempt to add illegal var: %s", name); - return; - } - - for (vd = *var_list; vd && str_cmp(vd->name, name); vd = vd->next); - - if (vd && (!vd->context || vd->context==id)) { - free(vd->value); - CREATE(vd->value, char, strlen(value) + 1); - } - - else { - CREATE(vd, struct trig_var_data, 1); - - CREATE(vd->name, char, strlen(name) + 1); - strcpy(vd->name, name); /* strcpy: ok*/ - - CREATE(vd->value, char, strlen(value) + 1); - - vd->next = *var_list; - vd->context = id; - *var_list = vd; - } - - strcpy(vd->value, value); /* strcpy: ok*/ -} - -/* perhaps not the best place for this, but I didn't want a new file */ -char *skill_percent(struct char_data *ch, char *skill) -{ - static char retval[16]; - int skillnum; - - skillnum = find_skill_num(skill); - if (skillnum<=0) return("unknown skill"); - - snprintf(retval, sizeof(retval), "%d", GET_SKILL(ch, skillnum)); - return retval; -} - -/* Search through all the persons items, including containers. 0 if it doesnt - * exist, and greater then 0 if it does! Jamie Nelson. Now also searches by - * vnum and returns the number of matching objects. - Welcor */ -int item_in_list(char *item, obj_data *list) -{ - obj_data *i; - int count = 0; - - if (!item || !*item) - return 0; - - if (*item == UID_CHAR) { - long id = atol(item + 1); - - for (i = list; i; i = i->next_content) { - if (id == i->script_id) - count ++; - if (GET_OBJ_TYPE(i) == ITEM_CONTAINER) - count += item_in_list(item, i->contains); - } - } else if (is_number(item)) { /* check for vnum */ - obj_vnum ovnum = atoi(item); - - for (i = list; i; i = i->next_content) { - if (GET_OBJ_VNUM(i) == ovnum) - count++; - if (GET_OBJ_TYPE(i) == ITEM_CONTAINER) - count += item_in_list(item, i->contains); - } - } else { - for (i = list; i; i = i->next_content) { - if (isname(item, i->name)) - count++; - if (GET_OBJ_TYPE(i) == ITEM_CONTAINER) - count += item_in_list(item, i->contains); - } - } - return count; -} - -/* BOOLEAN return, just check if a player or mob has an item of any sort, - * searched for by name or id. Searching equipment as well as inventory, and - * containers. Jamie Nelson */ -int char_has_item(char *item, struct char_data *ch) -{ - - /* If this works, no more searching needed */ - if (get_object_in_equip(ch, item) != NULL) - return 1; - - if (item_in_list(item, ch->carrying) == 0) - return 0; - else - return 1; -} - -static int handle_oset(struct obj_data * obj, char * argument) -{ - int i = 0; - bool found = FALSE; - char value[MAX_INPUT_LENGTH]; - - struct oset_handler { - const char * type; - bool (* name)(struct obj_data *, char *); - } handler[] = { - { "alias", oset_alias }, - { "apply", oset_apply }, - { "longdesc", oset_long_description }, - { "shortdesc", oset_short_description}, - { "\n", NULL } - }; - - if (!obj || !*argument) - return 0; - - argument = one_argument(argument, value); - - while (*handler[i].type != '\n') { - if (is_abbrev(value, handler[i].type)) { - found = TRUE; - break; - } - i++; - } - - if (!found) - return 0; - - handler[i].name(obj, argument); - return 1; -} - -int text_processed(char *field, char *subfield, struct trig_var_data *vd, - char *str, size_t slen) -{ - char *p, *p2; - char tmpvar[MAX_STRING_LENGTH]; - - if (!str_cmp(field, "strlen")) { /* strlen */ - snprintf(str, slen, "%d", (int)strlen(vd->value)); - return TRUE; - } else if (!str_cmp(field, "toupper")) { /* toupper */ - char *upper = vd->value; - if (*upper) - snprintf(str, slen, "%c%s", UPPER(*upper), upper + 1); - return TRUE; - } else if (!str_cmp(field, "trim")) { /* trim */ - /* trim whitespace from ends */ - snprintf(tmpvar, sizeof(tmpvar)-1 , "%s", vd->value); /* -1 to use later*/ - p = tmpvar; - p2 = tmpvar + strlen(tmpvar) - 1; - while (*p && isspace(*p)) p++; - while ((p<=p2) && isspace(*p2)) p2--; - if (p>p2) { /* nothing left */ - *str = '\0'; - return TRUE; - } - *(++p2) = '\0'; /* +1 ok (see above) */ - snprintf(str, slen, "%s", p); - return TRUE; - } else if (!str_cmp(field, "contains")) { /* contains */ - if (str_str(vd->value, subfield)) - strcpy(str, "1"); - else - strcpy(str, "0"); - return TRUE; - } else if (!str_cmp(field, "car")) { /* car */ - char *car = vd->value; - while (*car && !isspace(*car)) - *str++ = *car++; - *str = '\0'; - return TRUE; - - } else if (!str_cmp(field, "cdr")) { /* cdr */ - char *cdr = vd->value; - while (*cdr && !isspace(*cdr)) cdr++; /* skip 1st field */ - while (*cdr && isspace(*cdr)) cdr++; /* skip to next */ - - snprintf(str, slen, "%s", cdr); - return TRUE; - } else if (!str_cmp(field, "charat")) { /* CharAt */ - size_t len = strlen(vd->value), cindex = atoi(subfield); - if (cindex > len || cindex < 1) - strcpy(str, ""); - else - snprintf(str, slen, "%c", vd->value[cindex - 1]); - return TRUE; - } else if (!str_cmp(field, "mudcommand")) { - /* find the mud command returned from this text */ -/* NOTE: you may need to replace "cmd_info" with "complete_cmd_info", */ -/* depending on what patches you've got applied. */ -/* on older source bases: extern struct command_info *cmd_info; */ - int length, cmd; - for (length = strlen(vd->value), cmd = 0; - *cmd_info[cmd].command != '\n'; cmd++) - if (!strncmp(cmd_info[cmd].command, vd->value, length)) - break; - - if (*cmd_info[cmd].command == '\n') - *str = '\0'; - else - snprintf(str, slen, "%s", cmd_info[cmd].command); - return TRUE; - } - - return FALSE; -} - -/* sets str to be the value of var.field */ -void find_replacement(void *go, struct script_data *sc, trig_data *trig, - int type, char *var, char *field, char *subfield, char *str, size_t slen) -{ - struct trig_var_data *vd=NULL; - char_data *ch, *c = NULL, *rndm; - obj_data *obj, *o = NULL; - struct room_data *room, *r = NULL; - char *name; - int num, count, i, j, doors; - - char *log_cmd[] = {"mlog ", "olog ", "wlog " }; - char *send_cmd[] = {"msend ", "osend ", "wsend " }; - char *echo_cmd[] = {"mecho ", "oecho ", "wecho " }; - char *echoaround_cmd[] = {"mechoaround ", "oechoaround ", "wechoaround "}; - char *door[] = {"mdoor ", "odoor ", "wdoor " }; - char *force[] = {"mforce ", "oforce ", "wforce " }; - char *load[] = {"mload ", "oload ", "wload " }; - char *purge[] = {"mpurge ", "opurge ", "wpurge " }; - char *teleport[] = {"mteleport ", "oteleport ", "wteleport " }; - /* the x kills a 'shadow' warning in gcc. */ - char *xdamage[] = {"mdamage ", "odamage ", "wdamage " }; - char *zoneecho[] = {"mzoneecho ", "ozoneecho ", "wzoneecho " }; - char *asound[] = {"masound ", "oasound ", "wasound " }; - char *at[] = {"mat ", "oat ", "wat " }; - /* there is no such thing as wtransform, thus the wecho below */ - char *transform[] = {"mtransform ", "otransform ", "wecho " }; - char *recho[] = {"mrecho ", "orecho ", "wrecho " }; - /* there is no such thing as mmove, thus the mecho below */ - char *omove[] = {"mecho ", "omove ", "wmove " }; - - *str = '\0'; - - /* X.global() will have a NULL trig */ - if (trig) - for (vd = GET_TRIG_VARS(trig); vd; vd = vd->next) - if (!str_cmp(vd->name, var)) - break; - - /* some evil waitstates could crash the mud if sent here with sc==NULL*/ - if (!vd && sc) - for (vd = sc->global_vars; vd; vd = vd->next) - if (!str_cmp(vd->name, var) && - (vd->context==0 || vd->context==sc->context)) - break; - - if (!*field) { - if (vd) - snprintf(str, slen, "%s", vd->value); - else { - if (!str_cmp(var, "self")) { - switch (type) { - case MOB_TRIGGER: - snprintf(str, slen, "%c%ld", UID_CHAR, char_script_id((char_data *) go)); - break; - case OBJ_TRIGGER: - snprintf(str, slen, "%c%ld", UID_CHAR, obj_script_id((obj_data *) go)); - break; - case WLD_TRIGGER: - snprintf(str, slen, "%c%ld", UID_CHAR, room_script_id((room_data *)go)); - break; - } - } - else if (!str_cmp(var, "global")) { - /* so "remote varname %global%" will work */ - snprintf(str, slen, "%d", ROOM_ID_BASE); - return; - } - else if (!str_cmp(var, "door")) - snprintf(str, slen, "%s", door[type]); - else if (!str_cmp(var, "force")) - snprintf(str, slen, "%s", force[type]); - else if (!str_cmp(var, "load")) - snprintf(str, slen, "%s", load[type]); - else if (!str_cmp(var, "purge")) - snprintf(str, slen, "%s", purge[type]); - else if (!str_cmp(var, "teleport")) - snprintf(str, slen, "%s", teleport[type]); - else if (!str_cmp(var, "damage")) - snprintf(str, slen, "%s", xdamage[type]); - else if (!str_cmp(var, "send")) - snprintf(str, slen, "%s", send_cmd[type]); - else if (!str_cmp(var, "echo")) - snprintf(str, slen, "%s", echo_cmd[type]); - else if (!str_cmp(var, "echoaround")) - snprintf(str, slen, "%s", echoaround_cmd[type]); - else if (!str_cmp(var, "zoneecho")) - snprintf(str, slen, "%s", zoneecho[type]); - else if (!str_cmp(var, "asound")) - snprintf(str, slen, "%s", asound[type]); - else if (!str_cmp(var, "at")) - snprintf(str, slen, "%s", at[type]); - else if (!str_cmp(var, "transform")) - snprintf(str, slen, "%s", transform[type]); - else if (!str_cmp(var, "recho")) - snprintf(str, slen, "%s", recho[type]); - else if (!str_cmp(var, "move")) - snprintf(str, slen, "%s", omove[type]); - else if (!str_cmp(var, "log")) - snprintf(str, slen, "%s", log_cmd[type]); - else - *str = '\0'; - } - - return; - } - - else if (vd && text_processed(field, subfield, vd, str, slen)) return; - - else { - if (vd) { - name = vd->value; - - switch (type) { - case MOB_TRIGGER: - ch = (char_data *) go; - - if ((o = get_object_in_equip(ch, name))); - else if ((o = get_obj_in_list(name, ch->carrying))); - else if (IN_ROOM(ch) != NOWHERE && (c = get_char_in_room(&world[IN_ROOM(ch)], name))); - else if ((o = get_obj_in_list(name,world[IN_ROOM(ch)].contents))); - else if ((c = get_char(name))); - else if ((o = get_obj(name))); - else if ((r = get_room(name))) {} - - break; - case OBJ_TRIGGER: - obj = (obj_data *) go; - - if ((c = get_char_by_obj(obj, name))); - else if ((o = get_obj_by_obj(obj, name))); - else if ((r = get_room(name))) {} - - break; - case WLD_TRIGGER: - room = (struct room_data *) go; - - if ((c = get_char_by_room(room, name))); - else if ((o = get_obj_by_room(room, name))); - else if ((r = get_room(name))) {} - - break; - } - } - - else { - if (!str_cmp(var, "self")) { - switch (type) { - case MOB_TRIGGER: - c = (char_data *) go; - r = NULL; - o = NULL; /* NULL assignments added to avoid self to always be */ - break; /* the room. - Welcor */ - case OBJ_TRIGGER: - o = (obj_data *) go; - c = NULL; - r = NULL; - break; - case WLD_TRIGGER: - r = (struct room_data *) go; - c = NULL; - o = NULL; - break; - } - } - - else if (!str_cmp(var, "global")) { - struct script_data *thescript = SCRIPT(&world[0]); - *str = '\0'; - if (!thescript) { - script_log("Attempt to find global var. Apparently the void has no script."); - return; - } - for (vd = thescript->global_vars; vd ; vd = vd->next) - if (!str_cmp(vd->name, field)) - break; - - if (vd) - snprintf(str, slen, "%s", vd->value); - - return; - } - else if (!str_cmp(var, "people")) { - snprintf(str, slen, "%d",((num = atoi(field)) > 0) ? trgvar_in_room(num) : 0); - return; - } - else if (!str_cmp(var, "time")) { - if (!str_cmp(field, "hour")) - snprintf(str, slen, "%d", time_info.hours); - else if (!str_cmp(field, "day")) - snprintf(str, slen, "%d", time_info.day + 1); - else if (!str_cmp(field, "month")) - snprintf(str, slen, "%d", time_info.month + 1); - else if (!str_cmp(field, "year")) - snprintf(str, slen, "%d", time_info.year); - else *str = '\0'; - return; - } -/* %findobj.()% - * - count number of objects in room X with this name/id/vnum - * %findmob.()% - * - count number of mobs in room X with vnum Y - * For example you want to check how many PC's are in room with vnum 1204. PC's - * have the vnum -1 so: %echo% players in room 1204: %findmob.1204(-1)% - * Or say you had a bank, and you want a script to check the number of bags of - * coins (vnum: 1234). In the vault (vnum: 453). Use: %findobj.453(1234)% and it - * will return the number of bags of coins. - * Addition inspired by Jamie Nelson */ - else if (!str_cmp(var, "findmob")) { - if (!field || !*field || !subfield || !*subfield) { - script_log("findmob.vnum(mvnum) - illegal syntax"); - strcpy(str, "0"); - } else { - room_rnum rrnum = real_room(atoi(field)); - mob_vnum mvnum = atoi(subfield); - - if (rrnum == NOWHERE) { - script_log("findmob.vnum(ovnum): No room with vnum %d", atoi(field)); - strcpy(str, "0"); - } else { - for (i = 0, ch = world[rrnum].people; ch; ch = ch->next_in_room) - if (GET_MOB_VNUM(ch) == mvnum) - i++; - - snprintf(str, slen, "%d", i); - } - } - } - /* Addition inspired by Jamie Nelson. */ - else if (!str_cmp(var, "findobj")) { - if (!field || !*field || !subfield || !*subfield) { - script_log("findobj.vnum(ovnum) - illegal syntax"); - strcpy(str, "0"); - } else { - room_rnum rrnum = real_room(atoi(field)); - - if (rrnum == NOWHERE) { - script_log("findobj.vnum(ovnum): No room with vnum %d", atoi(field)); - strcpy(str, "0"); - } else { - /* item_in_list looks within containers as well. */ - snprintf(str, slen, "%d", item_in_list(subfield, world[rrnum].contents)); - } - } - } - else if (!str_cmp(var, "random")) { - if (!str_cmp(field, "char")) { - rndm = NULL; - count = 0; - - if (type == MOB_TRIGGER) { - ch = (char_data *) go; - for (c = world[IN_ROOM(ch)].people; c; c = c->next_in_room) - if ((c != ch) && valid_dg_target(c, DG_ALLOW_GODS) && - CAN_SEE(ch, c)) { - if (!rand_number(0, count)) - rndm = c; - count++; - } - } - - else if (type == OBJ_TRIGGER) { - for (c = world[obj_room((obj_data *) go)].people; c; - c = c->next_in_room) - if (valid_dg_target(c, DG_ALLOW_GODS)) { - if (!rand_number(0, count)) - rndm = c; - count++; - } - } - - else if (type == WLD_TRIGGER) { - for (c = ((struct room_data *) go)->people; c; - c = c->next_in_room) - if (valid_dg_target(c, DG_ALLOW_GODS)) { - - if (!rand_number(0, count)) - rndm = c; - count++; - } - } - - if (rndm) - snprintf(str, slen, "%c%ld", UID_CHAR, char_script_id(rndm)); - else - *str = '\0'; - } - - else if (!str_cmp(field, "dir")) { - room_rnum in_room = NOWHERE; - - switch (type) { - case WLD_TRIGGER: - in_room = real_room(((struct room_data *) go)->number); - break; - case OBJ_TRIGGER: - in_room = obj_room((struct obj_data *) go); - break; - case MOB_TRIGGER: - in_room = IN_ROOM((struct char_data *)go); - break; - } - if (in_room == NOWHERE) { - *str = '\0'; - } else { - doors = 0; - room = &world[in_room]; - for (i = 0; i < DIR_COUNT; i++) - if (R_EXIT(room, i)) - doors++; - - if (!doors) { - *str = '\0'; - } else { - for ( ; ; ) { - doors = rand_number(0, DIR_COUNT-1); - if (R_EXIT(room, doors)) - break; - } - snprintf(str, slen, "%s", dirs[doors]); - } - } - } - else - snprintf(str, slen, "%d", ((num = atoi(field)) > 0) ? rand_number(1, num) : 0); - - return; - } - } - - if (c) { - if (!str_cmp(field, "global")) { /* get global of something else */ - if (IS_NPC(c) && c->script) { - find_replacement(go, c->script, NULL, MOB_TRIGGER, - subfield, NULL, NULL, str, slen); - } - } - /* set str to some 'non-text' first */ - *str = '\x1'; - - switch (LOWER(*field)) { - case 'a': - if (!str_cmp(field, "affect")) { - if (subfield && *subfield) { - int spell = find_skill_num(subfield); - if (affected_by_spell(c, spell)) - strcpy(str, "1"); - else - strcpy(str, "0"); - } else - strcpy(str, "0"); - } - else if (!str_cmp(field, "alias")) - snprintf(str, slen, "%s", GET_PC_NAME(c)); - - else if (!str_cmp(field, "align")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_ALIGNMENT(c) = MAX(-1000, MIN(addition, 1000)); - } - snprintf(str, slen, "%d", GET_ALIGNMENT(c)); - } - else if (!str_cmp(field, "armor")) - snprintf(str, slen, "%d", compute_armor_class(c)); - break; - case 'c': - if (!str_cmp(field, "canbeseen")) { - if ((type == MOB_TRIGGER) && !CAN_SEE(((char_data *)go), c)) - strcpy(str, "0"); - else - strcpy(str, "1"); - } - else if (!str_cmp(field, "cha")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - int max = (IS_NPC(c) || GET_LEVEL(c) >= LVL_GRGOD) ? 25 : 18; - c->real_abils.cha += addition; - if (c->real_abils.cha > max) c->real_abils.cha = max; - if (c->real_abils.cha < 3) c->real_abils.cha = 3; - affect_total(c); - } - snprintf(str, slen, "%d", GET_CHA(c)); - } - else if (!str_cmp(field, "class")) { - if (subfield && *subfield) { - int cl = get_class_by_name(subfield); - if (cl != -1) { - GET_CLASS(c) = cl; - snprintf(str, slen, "1"); - } else { - snprintf(str, slen, "0"); - } - } else - sprinttype(GET_CLASS(c), pc_class_types, str, slen); - } - else if (!str_cmp(field, "con")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - int max = (IS_NPC(c) || GET_LEVEL(c) >= LVL_GRGOD) ? 25 : 18; - c->real_abils.con += addition; - if (c->real_abils.con > max) c->real_abils.con = max; - if (c->real_abils.con < 3) c->real_abils.con = 3; - affect_total(c); - } - snprintf(str, slen, "%d", GET_CON(c)); - } - break; - case 'd': - if (!str_cmp(field, "dex")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - int max = (IS_NPC(c) || GET_LEVEL(c) >= LVL_GRGOD) ? 25 : 18; - c->real_abils.dex += addition; - if (c->real_abils.dex > max) c->real_abils.dex = max; - if (c->real_abils.dex < 3) c->real_abils.dex = 3; - affect_total(c); - } - snprintf(str, slen, "%d", GET_DEX(c)); - } - else if (!str_cmp(field, "drunk")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_COND(c, DRUNK) = MAX(-1, MIN(addition, 24)); - } - snprintf(str, slen, "%d", GET_COND(c, DRUNK)); - } - break; - case 'e': - if (!str_cmp(field, "eq")) { - int pos; - if (!subfield || !*subfield) - *str = '\0'; - else if (*subfield == '*') { - for (i = 0, j = 0; i < NUM_WEARS; i++) - if (GET_EQ(c, i)) { - j++; - break; - } - if (j > 0) - strcpy(str,"1"); - else - *str = '\0'; - } else if ((pos = find_eq_pos_script(subfield)) < 0 || !GET_EQ(c, pos)) - *str = '\0'; - else - snprintf(str, slen, "%c%ld",UID_CHAR, obj_script_id(GET_EQ(c, pos))); - } - else if (!str_cmp(field, "exp")) { - if (subfield && *subfield) { - int addition = MIN(atoi(subfield), 1000); - - gain_exp(c, addition); - } - snprintf(str, slen, "%d", GET_EXP(c)); - } - break; - case 'f': - if (!str_cmp(field, "fighting")) { - if (FIGHTING(c)) - snprintf(str, slen, "%c%ld", UID_CHAR, char_script_id(FIGHTING(c))); - else - *str = '\0'; - } - else if (!str_cmp(field, "follower")) { - if (!c->followers || !c->followers->follower) - *str = '\0'; - else - snprintf(str, slen, "%c%ld", UID_CHAR, char_script_id(c->followers->follower)); - } - break; - case 'g': - if (!str_cmp(field, "coins")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - increase_coins(c, addition); - } - snprintf(str, slen, "%d", GET_COINS(c)); - } - break; - case 'h': - if (!str_cmp(field, "has_item")) { - if (!(subfield && *subfield)) - *str = '\0'; - else - snprintf(str, slen, "%d", char_has_item(subfield, c)); - } - else if (!str_cmp(field, "hasattached")) { - if (!(subfield && *subfield) || !IS_NPC(c)) - *str = '\0'; - else { - i = atoi(subfield); - snprintf(str, slen, "%d", trig_is_attached(SCRIPT(c), i)); - } - } - else if (!str_cmp(field, "heshe")) - snprintf(str, slen, "%s", HSSH(c)); - else if (!str_cmp(field, "himher")) - snprintf(str, slen, "%s", HMHR(c)); - else if (!str_cmp(field, "hisher")) - snprintf(str, slen, "%s", HSHR(c)); - else if (!str_cmp(field, "hitp")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_HIT(c) += addition; - update_pos(c); - } - snprintf(str, slen, "%d", GET_HIT(c)); - } - else if (!str_cmp(field, "hunger")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_COND(c, HUNGER) = MAX(-1, MIN(addition, 24)); - } - snprintf(str, slen, "%d", GET_COND(c, HUNGER)); - } - break; - case 'i': - if (!str_cmp(field, "id")) - snprintf(str, slen, "%ld", char_script_id(c)); - /* new check for pc/npc status */ - else if (!str_cmp(field, "is_pc")) { - if (IS_NPC(c)) - strcpy(str, "0"); - else - strcpy(str, "1"); - } - else if (!str_cmp(field, "int")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - int max = (IS_NPC(c) || GET_LEVEL(c) >= LVL_GRGOD) ? 25 : 18; - c->real_abils.intel += addition; - if (c->real_abils.intel > max) c->real_abils.intel = max; - if (c->real_abils.intel < 3) c->real_abils.intel = 3; - affect_total(c); - } - snprintf(str, slen, "%d", GET_INT(c)); - } - else if (!str_cmp(field, "inventory")) { - if(subfield && *subfield) { - for (obj = c->carrying;obj;obj=obj->next_content) { - if(GET_OBJ_VNUM(obj)==atoi(subfield)) { - snprintf(str, slen, "%c%ld", UID_CHAR, obj_script_id(obj)); /* arg given, found */ - return; - } - } - if (!obj) - *str = '\0'; /* arg given, not found */ - } else { /* no arg given */ - if (c->carrying) { - snprintf(str, slen, "%c%ld", UID_CHAR, obj_script_id(c->carrying)); - } else { - *str = '\0'; - } - } - } - break; - case 'l': - if (!str_cmp(field, "level")) { - if (subfield && *subfield) { - int lev = atoi(subfield); - if (IS_NPC(c)) { - if (GET_LEVEL(c) != 1) - GET_LEVEL(c) = 1; - } else - GET_LEVEL(c) = MIN(MAX(lev, 1), LVL_IMPL); - } else - snprintf(str, slen, "%d", GET_LEVEL(c)); - } - break; - case 'm': - if (!str_cmp(field, "mana")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_MANA(c) += addition; - } - snprintf(str, slen, "%d", GET_MANA(c)); - } - else if (!str_cmp(field, "master")) { - if (!c->master) - *str = '\0'; - else - snprintf(str, slen, "%c%ld", UID_CHAR, char_script_id(c->master)); - } - else if (!str_cmp(field, "maxhitp")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_MAX_HIT(c) = MAX(GET_MAX_HIT(c) + addition, 1); - } - snprintf(str, slen, "%d", GET_MAX_HIT(c)); - } - else if (!str_cmp(field, "maxmana")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_MAX_MANA(c) = MAX(GET_MAX_MANA(c) + addition, 1); - } - snprintf(str, slen, "%d", GET_MAX_MANA(c)); - } - else if (!str_cmp(field, "maxmove") || !str_cmp(field, "maxstamina")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_MAX_STAMINA(c) = MAX(GET_MAX_STAMINA(c) + addition, 1); - } - snprintf(str, slen, "%d", GET_MAX_STAMINA(c)); - } - else if (!str_cmp(field, "move") || !str_cmp(field, "stamina")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_STAMINA(c) += addition; - } - snprintf(str, slen, "%d", GET_STAMINA(c)); - } - break; - case 'n': - if (!str_cmp(field, "name")) - snprintf(str, slen, "%s", GET_NAME(c)); - - else if (!str_cmp(field, "next_in_room")) { - if (c->next_in_room) - snprintf(str, slen,"%c%ld",UID_CHAR, char_script_id(c->next_in_room)); - else - *str = '\0'; - } - else if (!str_cmp(field, "npcflag")) { - if (subfield && *subfield) { - char buf[MAX_STRING_LENGTH]; - sprintbitarray(MOB_FLAGS(c), action_bits, PM_ARRAY_MAX, buf); - if (str_str(buf, subfield)) - snprintf(str, slen, "1"); - else - snprintf(str, slen, "0"); - } - else { - snprintf(str, slen, "0"); - } - } - break; - case 'p': - /* Thanks to Christian Ejlertsen for this idea - And to Ken Ray for speeding the implementation up :)*/ - if (!str_cmp(field, "pos")) { - if (subfield && *subfield) { - for (i = POS_SLEEPING; i <= POS_STANDING; i++) { - /* allows : Sleeping, Resting, Sitting, Fighting, Standing */ - if (!strn_cmp(subfield, position_types[i], strlen(subfield))) { - GET_POS(c) = i; - break; - } - } - } - snprintf(str, slen, "%s", position_types[GET_POS(c)]); - } - else if (!str_cmp(field, "pref")) { - if (subfield && *subfield) { - int pref = get_flag_by_name(preference_bits, subfield); - if (!IS_NPC(c) && pref != NOFLAG && PRF_FLAGGED(c, pref)) - strcpy(str, "1"); - else - strcpy(str, "0"); - } else - strcpy(str, "0"); - } - break; - case 'q': - if (!IS_NPC(c) && (!str_cmp(field, "questpoints") || - !str_cmp(field, "qp") || !str_cmp(field, "qpnts"))) - { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_QUESTPOINTS(c) += addition; - } - snprintf(str, slen, "%d", GET_QUESTPOINTS(c)); - } - else if (!str_cmp(field, "quest")) - { - if (!IS_NPC(c) && (GET_QUEST(c) != NOTHING) && (real_quest(GET_QUEST(c)) != NOTHING)) - snprintf(str, slen, "%d", GET_QUEST(c)); - else - strcpy(str, "0"); - } - else if (!str_cmp(field, "questdone")) - { - if (!IS_NPC(c) && subfield && *subfield) { - int q_num = atoi(subfield); - if (is_complete(c, q_num)) - strcpy(str, "1"); - else - strcpy(str, "0"); - } - else - strcpy(str, "0"); - } - break; - case 'r': - if (!str_cmp(field, "room")) { /* in NOWHERE, return the void */ -/* see note in dg_scripts.h */ -#ifdef ACTOR_ROOM_IS_UID - snprintf(str, slen, "%c%ld",UID_CHAR, - (IN_ROOM(c)!= NOWHERE) ? room_script_id(world + IN_ROOM(c)) : ROOM_ID_BASE); -#else - snprintf(str, slen, "%d", (IN_ROOM(c)!= NOWHERE) ? world[IN_ROOM(c)].number : 0); -#endif - } - break; - case 's': - if (!str_cmp(field, "save_str")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_SAVE(c, ABIL_STR) += addition; - } - snprintf(str, slen, "%d", GET_SAVE(c, ABIL_STR)); - } - - else if (!str_cmp(field, "save_dex")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_SAVE(c, ABIL_DEX) += addition; - } - snprintf(str, slen, "%d", GET_SAVE(c, ABIL_DEX)); - } - - else if (!str_cmp(field, "save_con")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_SAVE(c, ABIL_CON) += addition; - } - snprintf(str, slen, "%d", GET_SAVE(c, ABIL_CON)); - } - - else if (!str_cmp(field, "save_int")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_SAVE(c, ABIL_INT) += addition; - } - snprintf(str, slen, "%d", GET_SAVE(c, ABIL_INT)); - } - - else if (!str_cmp(field, "save_wis")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_SAVE(c, ABIL_WIS) += addition; - } - snprintf(str, slen, "%d", GET_SAVE(c, ABIL_WIS)); - } - - else if (!str_cmp(field, "save_cha")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_SAVE(c, ABIL_CHA) += addition; - } - snprintf(str, slen, "%d", GET_SAVE(c, ABIL_CHA)); - } - else if (!str_cmp(field, "sex")) - snprintf(str, slen, "%s", genders[(int)GET_SEX(c)]); - else if (!str_cmp(field, "skill")) - snprintf(str, slen, "%s", skill_percent(c, subfield)); - else if (!str_cmp(field, "str")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - int max = (IS_NPC(c) || GET_LEVEL(c) >= LVL_GRGOD) ? 25 : 18; - c->real_abils.str += addition; - if (c->real_abils.str > max) c->real_abils.str = max; - if (c->real_abils.str < 3) c->real_abils.str = 3; - affect_total(c); - } - snprintf(str, slen, "%d", GET_STR(c)); - } - break; - case 't': - if (!str_cmp(field, "thirst")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_COND(c, THIRST) = MAX(-1, MIN(addition, 24)); - } - snprintf(str, slen, "%d", GET_COND(c, THIRST)); - } - break; - case 'v': - if (!str_cmp(field, "varexists")) { - struct trig_var_data *remote_vd; - strcpy(str, "0"); - if (SCRIPT(c)) { - for (remote_vd = SCRIPT(c)->global_vars; remote_vd; remote_vd = remote_vd->next) { - if (!str_cmp(remote_vd->name, subfield)) break; - } - if (remote_vd) strcpy(str, "1"); - } - } - else if (!str_cmp(field, "vnum")) { - if (subfield && *subfield) { - /* When this had -1 at the end of the line it returned true for PC's if you did - * something like if %actor.vnum(500)%. It should return false for PC's instead - * -- Fizban 02/18 - */ - snprintf(str, slen, "%d", IS_NPC(c) ? (int)(GET_MOB_VNUM(c) == atoi(subfield)) : 0 ); - } else { - if (IS_NPC(c)) - snprintf(str, slen, "%d", GET_MOB_VNUM(c)); - else - /* - * for compatibility with unsigned indexes - * - this is deprecated - use %actor.is_pc% to check - * instead of %actor.vnum% == -1 --Welcor 09/03 - */ - strcpy(str, "-1"); - } - } - break; - case 'w': - if (!str_cmp(field, "weight")) - snprintf(str, slen, "%d", GET_WEIGHT(c)); - else if (!str_cmp(field, "wis")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - int max = (IS_NPC(c) || GET_LEVEL(c) >= LVL_GRGOD) ? 25 : 18; - c->real_abils.wis += addition; - if (c->real_abils.wis > max) c->real_abils.wis = max; - if (c->real_abils.wis < 3) c->real_abils.wis = 3; - affect_total(c); - } - snprintf(str, slen, "%d", GET_WIS(c)); - } - - else if (!str_cmp(field, "wait")) - { - if (subfield && *subfield) - { - int addition = atoi(subfield); - WAIT_STATE(c, addition * ( PULSE_VIOLENCE / 2) ); // by default violence is 2 seconds - } - snprintf(str, slen, "%d", GET_WAIT_STATE(c)); - } - - break; - } /* switch *field */ - - if (*str == '\x1') { /* no match found in switch */ - if (SCRIPT(c)) { - for (vd = (SCRIPT(c))->global_vars; vd; vd = vd->next) - if (!str_cmp(vd->name, field)) - break; - if (vd) - snprintf(str, slen, "%s", vd->value); - else { - *str = '\0'; - script_log("Trigger: %s, VNum %d. unknown char field: '%s'", - GET_TRIG_NAME(trig), GET_TRIG_VNUM(trig), field); - } - } else { - *str = '\0'; - script_log("Trigger: %s, VNum %d. unknown char field: '%s'", - GET_TRIG_NAME(trig), GET_TRIG_VNUM(trig), field); - } - } - } /* if (c) ...*/ - - else if (o) { - - *str = '\x1'; - switch (LOWER(*field)) { - case 'a': - if (!str_cmp(field, "affects")) { - if (subfield && *subfield) { - if (check_flags_by_name_ar(GET_OBJ_AFFECT(o), NUM_AFF_FLAGS, subfield, affected_bits) == TRUE) - snprintf(str, slen, "1"); - else - snprintf(str, slen, "0"); - } else - snprintf(str, slen, "0"); - } - case 'c': - if (!str_cmp(field, "cost")) { - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_OBJ_COST(o) = MAX(1, addition + GET_OBJ_COST(o)); - } - snprintf(str, slen, "%d", GET_OBJ_COST(o)); - } - - else if (!str_cmp(field, "carried_by")) { - if (o->carried_by) - snprintf(str, slen,"%c%ld",UID_CHAR, char_script_id(o->carried_by)); - else - *str = '\0'; - } - - else if (!str_cmp(field, "contents")) { - if (o->contains) - snprintf(str, slen, "%c%ld", UID_CHAR, obj_script_id(o->contains)); - else - *str = '\0'; - } - /* thanks to Jamie Nelson (Mordecai of 4 Dimensions MUD) */ - else if (!str_cmp(field, "count")) { - if (GET_OBJ_TYPE(o) == ITEM_CONTAINER) - snprintf(str, slen, "%d", item_in_list(subfield, o->contains)); - else - strcpy(str, "0"); - } - break; - case 'e': - if (!str_cmp(field, "extra")) { - if (subfield && *subfield) { - if (check_flags_by_name_ar(GET_OBJ_EXTRA(o), NUM_ITEM_FLAGS, subfield, extra_bits) > 0) - snprintf(str, slen, "1"); - else - snprintf(str, slen, "0"); - } else { - sprintbitarray(GET_OBJ_EXTRA(o), extra_bits, EF_ARRAY_MAX, str); - } - } - break; - case 'h': - /* thanks to Jamie Nelson (Mordecai of 4 Dimensions MUD) */ - if (!str_cmp(field, "has_in")) { - if (GET_OBJ_TYPE(o) == ITEM_CONTAINER) - snprintf(str, slen, "%s", (item_in_list(subfield, o->contains) ? "1" : "0")); - else - strcpy(str, "0"); - } - else if (!str_cmp(field, "hasattached")) { - if (!(subfield && *subfield)) - *str = '\0'; - else { - i = atoi(subfield); - snprintf(str, slen, "%d", trig_is_attached(SCRIPT(o), i)); - } - } - break; - case 'i': - if (!str_cmp(field, "id")) - snprintf(str, slen, "%ld", obj_script_id(o)); - - else if (!str_cmp(field, "is_inroom")) { - if (IN_ROOM(o) != NOWHERE) - snprintf(str, slen,"%c%ld",UID_CHAR, room_script_id(world + IN_ROOM(o))); - else - *str = '\0'; - } - else if (!str_cmp(field, "is_pc")) { - strcpy(str, "-1"); - } - break; - case 'n': - if (!str_cmp(field, "name")) - snprintf(str, slen, "%s", o->name); - - else if (!str_cmp(field, "next_in_list")) { - if (o->next_content) - snprintf(str, slen,"%c%ld",UID_CHAR, obj_script_id(o->next_content)); - else - *str = '\0'; - } - break; - case 'o': - if (!str_cmp(field, "oset")) { - if (subfield && *subfield) { - if (handle_oset(o, subfield)) - strcpy(str, "1"); - else - strcpy(str, "0"); - } - } - break; - case 'r': - if (!str_cmp(field, "room")) { - if (obj_room(o) != NOWHERE) - snprintf(str, slen,"%c%ld",UID_CHAR, room_script_id(world + obj_room(o))); - else - *str = '\0'; - } - break; - case 's': - if (!str_cmp(field, "shortdesc")) - snprintf(str, slen, "%s", o->short_description); - break; - case 't': - if (!str_cmp(field, "type")) - sprinttype(GET_OBJ_TYPE(o), item_types, str, slen); - - else if (!str_cmp(field, "timer")) - snprintf(str, slen, "%d", GET_OBJ_TIMER(o)); - break; - case 'v': - if (!str_cmp(field, "vnum")) - if (subfield && *subfield) { - snprintf(str, slen, "%d", (int)(GET_OBJ_VNUM(o) == atoi(subfield))); - } else { - snprintf(str, slen, "%d", GET_OBJ_VNUM(o)); - } - else if (!str_cmp(field, "val0")) - snprintf(str, slen, "%d", GET_OBJ_VAL(o, 0)); - - else if (!str_cmp(field, "val1")) - snprintf(str, slen, "%d", GET_OBJ_VAL(o, 1)); - - else if (!str_cmp(field, "val2")) - snprintf(str, slen, "%d", GET_OBJ_VAL(o, 2)); - - else if (!str_cmp(field, "val3")) - snprintf(str, slen, "%d", GET_OBJ_VAL(o, 3)); - break; - case 'w': - if (!str_cmp(field, "wearflag")) { - if (subfield && *subfield) { - if (can_wear_on_pos(o, find_eq_pos_script(subfield))) - snprintf(str, slen, "1"); - else - snprintf(str, slen, "0"); - } else - snprintf(str, slen, "0"); - } - - else if (!str_cmp(field, "weight")){ - if (subfield && *subfield) { - int addition = atoi(subfield); - GET_OBJ_WEIGHT(o) = MAX(1, addition + GET_OBJ_WEIGHT(o)); - } - snprintf(str, slen, "%d", GET_OBJ_WEIGHT(o)); - } - - else if (!str_cmp(field, "worn_by")) { - if (o->worn_by) - snprintf(str, slen,"%c%ld",UID_CHAR, char_script_id(o->worn_by)); - else - *str = '\0'; - } - break; - } /* switch *field */ - - - if (*str == '\x1') { /* no match in switch */ - if (SCRIPT(o)) { /* check for global var */ - for (vd = (SCRIPT(o))->global_vars; vd; vd = vd->next) - if (!str_cmp(vd->name, field)) - break; - if (vd) - snprintf(str, slen, "%s", vd->value); - else { - *str = '\0'; - script_log("Trigger: %s, VNum %d, type: %d. unknown object field: '%s'", - GET_TRIG_NAME(trig), GET_TRIG_VNUM(trig), type, field); - } - } else { - *str = '\0'; - script_log("Trigger: %s, VNum %d, type: %d. unknown object field: '%s'", - GET_TRIG_NAME(trig), GET_TRIG_VNUM(trig), type, field); - } - } - } /* if (o) ... */ - - else if (r) { - - /* special handling of the void, as it stores all 'full global' variables */ - if (r->number == 0) { - if (!SCRIPT(r)) { - *str = '\0'; - script_log("Trigger: %s, Vnum %d, type %d. Trying to access Global var list of void. Apparently this has not been set up!", - GET_TRIG_NAME(trig), GET_TRIG_VNUM(trig), type); - } else { - for (vd = (SCRIPT(r))->global_vars; vd; vd = vd->next) - if (!str_cmp(vd->name, field)) - break; - if (vd) - snprintf(str, slen, "%s", vd->value); - else - *str = '\0'; - } - } - - else if (!str_cmp(field, "name")) - snprintf(str, slen, "%s", r->name); - - else if (!str_cmp(field, "sector")) - sprinttype(r->sector_type, sector_types, str, slen); - - else if (!str_cmp(field, "vnum")) { - if (subfield && *subfield) { - snprintf(str, slen, "%d", (int)(r->number == atoi(subfield))); - } else { - snprintf(str, slen,"%d",r->number); - } - } else if (!str_cmp(field, "contents")) { - if (subfield && *subfield) { - for (obj = r->contents; obj; obj = obj->next_content) { - if (GET_OBJ_VNUM(obj) == atoi(subfield)) { - /* arg given, found */ - snprintf(str, slen, "%c%ld", UID_CHAR, obj_script_id(obj)); - return; - } - } - if (!obj) - *str = '\0'; /* arg given, not found */ - } else { /* no arg given */ - if (r->contents) { - snprintf(str, slen, "%c%ld", UID_CHAR, obj_script_id(r->contents)); - } else { - *str = '\0'; - } - } - } - - else if (!str_cmp(field, "people")) { - if (r->people) - snprintf(str, slen, "%c%ld", UID_CHAR, char_script_id(r->people)); - else - *str = '\0'; - } - else if (!str_cmp(field, "id")) { - room_rnum rnum = real_room(r->number); - if (rnum != NOWHERE) - snprintf(str, slen, "%ld", room_script_id(world + rnum)); - else - *str = '\0'; - } - else if (!str_cmp(field, "weather")) { - const char *sky_look[] = { - "sunny", - "cloudy", - "rainy", - "lightning" - }; - - if (!IS_SET_AR(r->room_flags, ROOM_INDOORS)) - snprintf(str, slen, "%s", sky_look[weather_info.sky]); - else - *str = '\0'; - } - else if (!str_cmp(field, "hasattached")) { - if (!(subfield && *subfield)) - *str = '\0'; - else { - i = atoi(subfield); - snprintf(str, slen, "%d", trig_is_attached(SCRIPT(r), i)); - } - } - else if (!str_cmp(field, "zonenumber")) - snprintf(str, slen, "%d", zone_table[r->zone].number); - else if (!str_cmp(field, "zonename")) - snprintf(str, slen, "%s", zone_table[r->zone].name); - else if (!str_cmp(field, "roomflag")) { - if (subfield && *subfield) { - room_rnum thisroom = real_room(r->number); - if (check_flags_by_name_ar(ROOM_FLAGS(thisroom), NUM_ROOM_FLAGS, subfield, room_bits) == TRUE) - snprintf(str, slen, "1"); - else - snprintf(str, slen, "0"); - } else - snprintf(str, slen, "0"); - } - else if (!str_cmp(field, "north")) { - if (R_EXIT(r, NORTH)) { - if (subfield && *subfield) { - if (!str_cmp(subfield, "vnum")) - snprintf(str, slen, "%d", GET_ROOM_VNUM(R_EXIT(r, NORTH)->to_room)); - else if (!str_cmp(subfield, "key")) - snprintf(str, slen, "%d", R_EXIT(r, NORTH)->key); - else if (!str_cmp(subfield, "bits")) - sprintbit(R_EXIT(r, NORTH)->exit_info ,exit_bits, str, slen); - else if (!str_cmp(subfield, "room")) { - if (R_EXIT(r, NORTH)->to_room != NOWHERE) - snprintf(str, slen, "%c%ld", UID_CHAR, room_script_id(world + R_EXIT(r, NORTH)->to_room)); - else - *str = '\0'; - } - } else /* no subfield - default to bits */ - sprintbit(R_EXIT(r, NORTH)->exit_info ,exit_bits, str, slen); - } else - *str = '\0'; - } - else if (!str_cmp(field, "east")) { - if (R_EXIT(r, EAST)) { - if (subfield && *subfield) { - if (!str_cmp(subfield, "vnum")) - snprintf(str, slen, "%d", GET_ROOM_VNUM(R_EXIT(r, EAST)->to_room)); - else if (!str_cmp(subfield, "key")) - snprintf(str, slen, "%d", R_EXIT(r, EAST)->key); - else if (!str_cmp(subfield, "bits")) - sprintbit(R_EXIT(r, EAST)->exit_info ,exit_bits, str, slen); - else if (!str_cmp(subfield, "room")) { - if (R_EXIT(r, EAST)->to_room != NOWHERE) - snprintf(str, slen, "%c%ld", UID_CHAR, room_script_id(world + R_EXIT(r, EAST)->to_room)); - else - *str = '\0'; - } - } else /* no subfield - default to bits */ - sprintbit(R_EXIT(r, EAST)->exit_info ,exit_bits, str, slen); - } else - *str = '\0'; - } - else if (!str_cmp(field, "south")) { - if (R_EXIT(r, SOUTH)) { - if (subfield && *subfield) { - if (!str_cmp(subfield, "vnum")) - snprintf(str, slen, "%d", GET_ROOM_VNUM(R_EXIT(r, SOUTH)->to_room)); - else if (!str_cmp(subfield, "key")) - snprintf(str, slen, "%d", R_EXIT(r, SOUTH)->key); - else if (!str_cmp(subfield, "bits")) - sprintbit(R_EXIT(r, SOUTH)->exit_info ,exit_bits, str, slen); - else if (!str_cmp(subfield, "room")) { - if (R_EXIT(r, SOUTH)->to_room != NOWHERE) - snprintf(str, slen, "%c%ld", UID_CHAR, room_script_id(world + R_EXIT(r, SOUTH)->to_room)); - else - *str = '\0'; - } - } else /* no subfield - default to bits */ - sprintbit(R_EXIT(r, SOUTH)->exit_info ,exit_bits, str, slen); - } else - *str = '\0'; - } - else if (!str_cmp(field, "west")) { - if (R_EXIT(r, WEST)) { - if (subfield && *subfield) { - if (!str_cmp(subfield, "vnum")) - snprintf(str, slen, "%d", GET_ROOM_VNUM(R_EXIT(r, WEST)->to_room)); - else if (!str_cmp(subfield, "key")) - snprintf(str, slen, "%d", R_EXIT(r, WEST)->key); - else if (!str_cmp(subfield, "bits")) - sprintbit(R_EXIT(r, WEST)->exit_info ,exit_bits, str, slen); - else if (!str_cmp(subfield, "room")) { - if (R_EXIT(r, WEST)->to_room != NOWHERE) - snprintf(str, slen, "%c%ld", UID_CHAR, room_script_id(world + R_EXIT(r, WEST)->to_room)); - else - *str = '\0'; - } - } else /* no subfield - default to bits */ - sprintbit(R_EXIT(r, WEST)->exit_info ,exit_bits, str, slen); - } else - *str = '\0'; - } - else if (!str_cmp(field, "up")) { - if (R_EXIT(r, UP)) { - if (subfield && *subfield) { - if (!str_cmp(subfield, "vnum")) - snprintf(str, slen, "%d", GET_ROOM_VNUM(R_EXIT(r, UP)->to_room)); - else if (!str_cmp(subfield, "key")) - snprintf(str, slen, "%d", R_EXIT(r, UP)->key); - else if (!str_cmp(subfield, "bits")) - sprintbit(R_EXIT(r, UP)->exit_info ,exit_bits, str, slen); - else if (!str_cmp(subfield, "room")) { - if (R_EXIT(r, UP)->to_room != NOWHERE) - snprintf(str, slen, "%c%ld", UID_CHAR, room_script_id(world + R_EXIT(r, UP)->to_room)); - else - *str = '\0'; - } - } else /* no subfield - default to bits */ - sprintbit(R_EXIT(r, UP)->exit_info ,exit_bits, str, slen); - } else - *str = '\0'; - } - else if (!str_cmp(field, "down")) { - if (R_EXIT(r, DOWN)) { - if (subfield && *subfield) { - if (!str_cmp(subfield, "vnum")) - snprintf(str, slen, "%d", GET_ROOM_VNUM(R_EXIT(r, DOWN)->to_room)); - else if (!str_cmp(subfield, "key")) - snprintf(str, slen, "%d", R_EXIT(r, DOWN)->key); - else if (!str_cmp(subfield, "bits")) - sprintbit(R_EXIT(r, DOWN)->exit_info ,exit_bits, str, slen); - else if (!str_cmp(subfield, "room")) { - if (R_EXIT(r, DOWN)->to_room != NOWHERE) - snprintf(str, slen, "%c%ld", UID_CHAR, room_script_id(world + R_EXIT(r, DOWN)->to_room)); - else - *str = '\0'; - } - } else /* no subfield - default to bits */ - sprintbit(R_EXIT(r, DOWN)->exit_info ,exit_bits, str, slen); - } else - *str = '\0'; - } - else { - if (SCRIPT(r)) { /* check for global var */ - for (vd = (SCRIPT(r))->global_vars; vd; vd = vd->next) - if (!str_cmp(vd->name, field)) - break; - if (vd) - snprintf(str, slen, "%s", vd->value); - else { - *str = '\0'; - script_log("Trigger: %s, VNum %d, type: %d. unknown room field: '%s'", - GET_TRIG_NAME(trig), GET_TRIG_VNUM(trig), type, field); - } - } else { - *str = '\0'; - script_log("Trigger: %s, VNum %d, type: %d. unknown room field: '%s'", - GET_TRIG_NAME(trig), GET_TRIG_VNUM(trig), type, field); - } - } - } /* if (r).. */ - } -} - -/* Now automatically checks if the variable has more then one field in it. And - * if the field returns a name or a script UID or the like it can recurse. If - * you supply a value like, %actor.int.str% it wont blow up on you either. Now - * also lets subfields have variables parsed inside of them so that: %echo% - * %actor.coins(%actor.coins%)% will double the actors coins every time its called. - * - Jamie Nelson */ - -/* substitutes any variables into line and returns it as buf */ -void var_subst(void *go, struct script_data *sc, trig_data *trig, - int type, char *line, char *buf) -{ - char tmp[MAX_INPUT_LENGTH], repl_str[MAX_INPUT_LENGTH - 20]; // - 20 to make room for "eval tmpvr " - char *var = NULL, *field = NULL, *p = NULL; - char tmp2[MAX_INPUT_LENGTH]; - char *subfield_p, subfield[MAX_INPUT_LENGTH]; - int left, len; - int paren_count = 0; - int dots = 0; - - /* skip out if no %'s */ - if (!strchr(line, '%')) { - strcpy(buf, line); - return; - } - /*lets just empty these to start with*/ - *repl_str = *tmp = *tmp2 = '\0'; - - p = strcpy(tmp, line); - subfield_p = subfield; - - left = MAX_INPUT_LENGTH - 1; - - while (*p && (left > 0)) { - - - /* copy until we find the first % */ - while (*p && (*p != '%') && (left > 0)) { - *(buf++) = *(p++); - left--; - } - - *buf = '\0'; - - /* double % */ - if (*p && (*(++p) == '%') && (left > 0)) { - *(buf++) = *(p++); - *buf = '\0'; - left--; - continue; - } - - /* so it wasn't double %'s */ - else if (*p && (left > 0)) { - - /* search until end of var or beginning of field */ - for (var = p; *p && (*p != '%') && (*p != '.'); p++); - - field = p; - if (*p == '.') { - *(p++) = '\0'; - dots = 0; - for (field = p; *p && ((*p != '%')||(paren_count > 0) || (dots)); p++) { - if (dots > 0) { - *subfield_p = '\0'; - find_replacement(go, sc, trig, type, var, field, subfield, repl_str, sizeof(repl_str)); - if (*repl_str) { - snprintf(tmp2, sizeof(tmp2), "eval tmpvr %s", repl_str); //temp var - process_eval(go, sc, trig, type, tmp2); - strcpy(var, "tmpvr"); - field = p; - dots = 0; - continue; - } - dots = 0; - } else if (*p=='(') { - *p = '\0'; - paren_count++; - } else if (*p==')') { - *p = '\0'; - paren_count--; - } else if (paren_count > 0) { - *subfield_p++ = *p; - } else if (*p=='.') { - *p = '\0'; - dots++; - } - } /* for (field.. */ - } /* if *p == '.' */ - - *(p++) = '\0'; - *subfield_p = '\0'; - - if (*subfield) { - var_subst(go, sc, trig, type, subfield, tmp2); - strcpy(subfield, tmp2); - } - - find_replacement(go, sc, trig, type, var, field, subfield, repl_str, sizeof(repl_str)); - - strncat(buf, repl_str, left); - len = strlen(repl_str); - buf += len; - left -= len; - } /* else if *p .. */ - } /* while *p .. */ - buf[sizeof(buf) - 1] = '\0'; -} diff --git a/src/fight.c b/src/fight.c index ba9357b..906eb0f 100644 --- a/src/fight.c +++ b/src/fight.c @@ -19,7 +19,7 @@ #include "spells.h" #include "screen.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "act.h" #include "class.h" #include "fight.h" diff --git a/src/genmob.c b/src/genmob.c index eea4935..12acc13 100644 --- a/src/genmob.c +++ b/src/genmob.c @@ -15,7 +15,7 @@ #include "genolc.h" #include "genmob.h" #include "genzon.h" -#include "dg_olc.h" +#include "py_olc.h" #include "spells.h" #include "toml_utils.h" @@ -479,6 +479,9 @@ int write_mobile_record(mob_vnum mvnum, struct char_data *mob, FILE *fd) fprintf(fd, "alignment = %d\n", GET_ALIGNMENT(mob)); toml_write_kv_string(fd, "mob_type", "enhanced"); + /* --- DG Scripts --- */ + script_save_to_disk(fd, mob, MOB_TRIGGER); + fprintf(fd, "\n[mob.simple]\n"); fprintf(fd, "level = %d\n", GET_LEVEL(mob)); fprintf(fd, "hit_dice = %d\n", GET_HIT(mob)); @@ -527,9 +530,6 @@ int write_mobile_record(mob_vnum mvnum, struct char_data *mob, FILE *fd) fprintf(fd, "quantity = %d\n", MAX(1, e->quantity)); } - /* --- DG Scripts --- */ - script_save_to_disk(fd, mob, MOB_TRIGGER); - /* --- Skinning yields --- */ { mob_rnum rmob = real_mobile(mvnum); diff --git a/src/genobj.c b/src/genobj.c index 87c6d90..7e71383 100644 --- a/src/genobj.c +++ b/src/genobj.c @@ -15,7 +15,7 @@ #include "genolc.h" #include "genobj.h" #include "genzon.h" -#include "dg_olc.h" +#include "py_olc.h" #include "handler.h" #include "interpreter.h" #include "boards.h" /* for board_info */ diff --git a/src/genolc.c b/src/genolc.c index add026a..0502ebe 100644 --- a/src/genolc.c +++ b/src/genolc.c @@ -20,7 +20,7 @@ #include "genshp.h" #include "genzon.h" #include "genobj.h" -#include "dg_olc.h" +#include "py_olc.h" #include "constants.h" #include "interpreter.h" #include "act.h" /* for the space_to_minus function */ diff --git a/src/genwld.c b/src/genwld.c index 5e6aad0..ef9c013 100644 --- a/src/genwld.c +++ b/src/genwld.c @@ -16,7 +16,7 @@ #include "genwld.h" #include "genzon.h" #include "shop.h" -#include "dg_olc.h" +#include "py_olc.h" #include "mud_event.h" #include "toml_utils.h" @@ -310,6 +310,8 @@ int save_rooms(zone_rnum rzone) room->room_flags[2], room->room_flags[3]); fprintf(sf, "sector = %d\n", room->sector_type); + script_save_to_disk(sf, room, WLD_TRIGGER); + /* Now you write out the exits for the room. */ for (j = 0; j < DIR_COUNT; j++) { if (R_EXIT(room, j)) { @@ -356,7 +358,6 @@ int save_rooms(zone_rnum rzone) fprintf(sf, "dc = %d\n", entry->dc); } } - script_save_to_disk(sf, room, WLD_TRIGGER); fputc('\n', sf); } } diff --git a/src/genzon.c b/src/genzon.c index 9099a6d..e15dcff 100644 --- a/src/genzon.c +++ b/src/genzon.c @@ -14,7 +14,7 @@ #include "toml.h" #include "toml_utils.h" #include "genzon.h" -#include "dg_scripts.h" +#include "py_triggers.h" /* local functions */ static void remove_cmd_from_list(struct reset_com **list, int pos); diff --git a/src/graph.c b/src/graph.c index 2b794c3..ea570ff 100644 --- a/src/graph.c +++ b/src/graph.c @@ -24,6 +24,7 @@ /* local functions */ static int VALID_EDGE(room_rnum x, int y); +static int VALID_EDGE_NO_DOORS(room_rnum x, int y); static void bfs_enqueue(room_rnum room, int dir); static void bfs_dequeue(void); static void bfs_clear_queue(void); @@ -56,6 +57,18 @@ static int VALID_EDGE(room_rnum x, int y) return 1; } +static int VALID_EDGE_NO_DOORS(room_rnum x, int y) +{ + if (world[x].dir_option[y] == NULL || TOROOM(x, y) == NOWHERE) + return 0; + if (IS_CLOSED(x, y)) + return 0; + if (ROOM_FLAGGED(TOROOM(x, y), ROOM_NOTRACK) || IS_MARKED(TOROOM(x, y))) + return 0; + + return 1; +} + static void bfs_enqueue(room_rnum room, int dir) { struct bfs_queue_struct *curr; @@ -137,6 +150,50 @@ static int find_first_step(room_rnum src, room_rnum target) return (BFS_NO_PATH); } +int find_first_step_no_doors(room_rnum src, room_rnum target) +{ + int curr_dir; + room_rnum curr_room; + + if (src == NOWHERE || target == NOWHERE || src > top_of_world || target > top_of_world) { + log("SYSERR: Illegal value %d or %d passed to find_first_step_no_doors. (%s)", src, target, __FILE__); + return (BFS_ERROR); + } + if (src == target) + return (BFS_ALREADY_THERE); + + /* clear marks first, some OLC systems will save the mark. */ + for (curr_room = 0; curr_room <= top_of_world; curr_room++) + UNMARK(curr_room); + + MARK(src); + + /* first, enqueue the first steps, saving which direction we're going. */ + for (curr_dir = 0; curr_dir < DIR_COUNT; curr_dir++) + if (VALID_EDGE_NO_DOORS(src, curr_dir)) { + MARK(TOROOM(src, curr_dir)); + bfs_enqueue(TOROOM(src, curr_dir), curr_dir); + } + + /* now, do the classic BFS. */ + while (queue_head) { + if (queue_head->room == target) { + curr_dir = queue_head->dir; + bfs_clear_queue(); + return (curr_dir); + } else { + for (curr_dir = 0; curr_dir < DIR_COUNT; curr_dir++) + if (VALID_EDGE_NO_DOORS(queue_head->room, curr_dir)) { + MARK(TOROOM(queue_head->room, curr_dir)); + bfs_enqueue(TOROOM(queue_head->room, curr_dir), queue_head->dir); + } + bfs_dequeue(); + } + } + + return (BFS_NO_PATH); +} + /* Functions and Commands which use the above functions. */ ACMD(do_track) { diff --git a/src/graph.h b/src/graph.h index 55d1f10..cb0df33 100644 --- a/src/graph.h +++ b/src/graph.h @@ -16,5 +16,6 @@ ACMD(do_track); void hunt_victim(struct char_data *ch); +int find_first_step_no_doors(room_rnum src, room_rnum target); #endif /* _GRAPH_H_*/ diff --git a/src/handler.c b/src/handler.c index bc008aa..7b76414 100644 --- a/src/handler.c +++ b/src/handler.c @@ -18,7 +18,7 @@ #include "screen.h" #include "interpreter.h" #include "spells.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "act.h" #include "class.h" #include "fight.h" diff --git a/src/improved-edit.c b/src/improved-edit.c index 8c46110..44dd88c 100644 --- a/src/improved-edit.c +++ b/src/improved-edit.c @@ -11,7 +11,7 @@ #include "comm.h" #include "interpreter.h" #include "improved-edit.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "modify.h" diff --git a/src/interpreter.c b/src/interpreter.c index 41735fe..0f19c76 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -22,7 +22,7 @@ #include "genolc.h" #include "oasis.h" #include "improved-edit.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "constants.h" #include "act.h" /* ACMDs located within the act*.c files */ #include "ban.h" @@ -334,6 +334,7 @@ cpp_extern const struct command_info cmd_info[] = { { "trigedit" , "trigedit", POS_DEAD , do_oasis_trigedit, LVL_BUILDER, 0 }, { "typo" , "typo" , POS_DEAD , do_ibt , 0, SCMD_TYPO }, { "tlist" , "tlist" , POS_DEAD , do_oasis_list, LVL_BUILDER, SCMD_OASIS_TLIST }, + { "pylist" , "pylist" , POS_DEAD , do_pylist , LVL_BUILDER, 0 }, { "tcopy" , "tcopy" , POS_DEAD , do_oasis_copy, LVL_GOD, CON_TRIGEDIT }, { "tstat" , "tstat" , POS_DEAD , do_tstat , LVL_BUILDER, 0 }, diff --git a/src/limits.c b/src/limits.c index e53b3be..7afa21e 100644 --- a/src/limits.c +++ b/src/limits.c @@ -18,7 +18,7 @@ #include "db.h" #include "handler.h" #include "interpreter.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "class.h" #include "fight.h" #include "screen.h" diff --git a/src/lists.c b/src/lists.c index 3a61209..7ffeb3d 100644 --- a/src/lists.c +++ b/src/lists.c @@ -10,7 +10,7 @@ #include "structs.h" #include "utils.h" #include "db.h" -#include "dg_event.h" +#include "py_event.h" static struct iterator_data Iterator; static bool loop = FALSE; diff --git a/src/magic.c b/src/magic.c index ef1ade0..ff236f1 100644 --- a/src/magic.c +++ b/src/magic.c @@ -18,7 +18,7 @@ #include "db.h" #include "interpreter.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "class.h" #include "fight.h" #include "mud_event.h" diff --git a/src/medit.c b/src/medit.c index 3acc57a..a327d80 100644 --- a/src/medit.c +++ b/src/medit.c @@ -24,7 +24,7 @@ #include "handler.h" #include "constants.h" #include "improved-edit.h" -#include "dg_olc.h" +#include "py_olc.h" #include "screen.h" #include "fight.h" #include "modify.h" /* for smash_tilde */ diff --git a/src/modify.c b/src/modify.c index c8526e7..3d74e9a 100644 --- a/src/modify.c +++ b/src/modify.c @@ -22,7 +22,7 @@ #include "improved-edit.h" #include "oasis.h" #include "class.h" -#include "dg_scripts.h" /* for trigedit_string_cleanup */ +#include "py_triggers.h" /* for trigedit_string_cleanup */ #include "modify.h" #include "quest.h" #include "ibt.h" diff --git a/src/mud_event.c b/src/mud_event.c index d457f32..8dfc561 100644 --- a/src/mud_event.c +++ b/src/mud_event.c @@ -10,7 +10,7 @@ #include "structs.h" #include "utils.h" #include "db.h" -#include "dg_event.h" +#include "py_event.h" #include "constants.h" #include "comm.h" /* For access to the game pulse */ #include "mud_event.h" diff --git a/src/mud_event.h b/src/mud_event.h index dc6feb9..799f4d8 100644 --- a/src/mud_event.h +++ b/src/mud_event.h @@ -13,7 +13,7 @@ #ifndef _MUD_EVENT_H_ #define _MUD_EVENT_H_ -#include "dg_event.h" +#include "py_event.h" #define EVENT_WORLD 0 #define EVENT_DESC 1 diff --git a/src/oasis.c b/src/oasis.c index 359c65d..e0871d5 100644 --- a/src/oasis.c +++ b/src/oasis.c @@ -21,7 +21,7 @@ #include "genobj.h" #include "oasis.h" #include "screen.h" -#include "dg_olc.h" +#include "py_olc.h" #include "act.h" #include "handler.h" /* for is_name */ #include "quest.h" diff --git a/src/oasis.h b/src/oasis.h index 582bcbf..dc25d06 100644 --- a/src/oasis.h +++ b/src/oasis.h @@ -489,5 +489,6 @@ void print_zone(struct char_data *ch, zone_rnum rnum); /** @deprecated is do_oasis_links intentionally dead code? */ ACMD(do_oasis_links); ACMD(do_oasis_list); +ACMD(do_pylist); #endif /* _OASIS_H_ */ diff --git a/src/oasis_copy.c b/src/oasis_copy.c index dda491d..a855830 100644 --- a/src/oasis_copy.c +++ b/src/oasis_copy.c @@ -22,7 +22,7 @@ #include "oasis.h" #include "improved-edit.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" /* Local, filescope function prototypes */ /* Utility function for buildwalk */ diff --git a/src/oasis_list.c b/src/oasis_list.c index 87cf124..f4a8de8 100644 --- a/src/oasis_list.c +++ b/src/oasis_list.c @@ -8,6 +8,14 @@ #include "conf.h" #include "sysdep.h" + +#include +#include +#include +#include +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif #include "structs.h" #include "utils.h" #include "comm.h" @@ -20,7 +28,7 @@ #include "shop.h" #include "screen.h" #include "constants.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "quest.h" #include "modify.h" #include "spells.h" @@ -38,6 +46,7 @@ static void list_mobiles(struct char_data *ch, zone_rnum rnum, mob_vnum vmin , m static void list_objects(struct char_data *ch, zone_rnum rnum, obj_vnum vmin , obj_vnum vmax ); static void list_shops(struct char_data *ch , zone_rnum rnum, shop_vnum vmin, shop_vnum vmax); static void list_zones(struct char_data *ch, zone_rnum rnum, zone_vnum vmin, zone_vnum vmax, char *name); +static int pylist_name_cmp(const void *a, const void *b); static void perform_mob_flag_list(struct char_data * ch, char *arg) { @@ -358,6 +367,20 @@ static void perform_obj_name_list(struct char_data * ch, char *arg) page_string(ch->desc, buf, TRUE); } +static int pylist_name_cmp(const void *a, const void *b) +{ + const char *left = *(const char *const *)a; + const char *right = *(const char *const *)b; + + if (!left && !right) + return 0; + if (!left) + return -1; + if (!right) + return 1; + return strcmp(left, right); +} + /* Ingame Commands */ ACMD(do_oasis_list) { @@ -499,6 +522,94 @@ ACMD(do_oasis_list) } } +ACMD(do_pylist) +{ + DIR *dir; + struct dirent *entry; + struct stat st; + char path[PATH_MAX]; + char **names = NULL; + size_t count = 0; + size_t capacity = 0; + size_t i; + size_t len = 0; + size_t buf_size = 8192; + char *buf; + + if (!ch->desc) + return; + + dir = opendir(SCRIPTS_PREFIX); + if (!dir) { + send_to_char(ch, "Unable to open scripts directory (%s).\r\n", strerror(errno)); + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') + continue; + if (!str_cmp(entry->d_name, ".keep")) + continue; + if (!strstr(entry->d_name, ".py")) + continue; + + snprintf(path, sizeof(path), "%s%s", SCRIPTS_PREFIX, entry->d_name); + if (stat(path, &st) < 0) + continue; + if (!S_ISREG(st.st_mode)) + continue; + + if (capacity == count) { + size_t new_cap = capacity ? (capacity * 2) : 32; + RECREATE(names, char *, new_cap); + capacity = new_cap; + } + + names[count++] = strdup(entry->d_name); + } + + closedir(dir); + + if (count == 0) { + send_to_char(ch, "No scripts found in %s.\r\n", SCRIPTS_PREFIX); + return; + } + + qsort(names, count, sizeof(*names), pylist_name_cmp); + + CREATE(buf, char, buf_size); + len = snprintf(buf, buf_size, + "Index Script Name\r\n" + "----- ------------------------------\r\n"); + + for (i = 0; i < count; i++) { + char line[MAX_STRING_LENGTH]; + size_t line_len; + + snprintf(line, sizeof(line), "%4zu) %s\r\n", i + 1, names[i]); + line_len = strlen(line); + + if (len + line_len + 1 > buf_size) { + size_t new_size = buf_size; + while (len + line_len + 1 > new_size) + new_size *= 2; + RECREATE(buf, char, new_size); + buf_size = new_size; + } + + memcpy(buf + len, line, line_len + 1); + len += line_len; + } + + page_string(ch->desc, buf, TRUE); + + for (i = 0; i < count; i++) + if (names[i]) + free(names[i]); + free(names); + free(buf); +} + ACMD(do_oasis_links) { zone_rnum zrnum; @@ -916,8 +1027,8 @@ static void list_triggers(struct char_data *ch, zone_rnum rnum, trig_vnum vmin, /* Store the header for the room listing. */ send_to_char (ch, - "Index VNum Trigger Name Type\r\n" - "----- ------- --------------------------------------------- ---------\r\n"); + "Index VNum Trigger Name Script Type\r\n" + "----- ------- ----------------------------- ---------------------- ---------\r\n"); /* Loop through the world and find each room. */ for (i = 0; i < top_of_trigt; i++) { @@ -925,8 +1036,10 @@ static void list_triggers(struct char_data *ch, zone_rnum rnum, trig_vnum vmin, if ((trig_index[i]->vnum >= bottom) && (trig_index[i]->vnum <= top)) { counter++; - send_to_char(ch, "%4d) [%s%5d%s] %s%-45.45s%s ", - counter, QGRN, trig_index[i]->vnum, QNRM, QCYN, trig_index[i]->proto->name, QNRM); + send_to_char(ch, "%4d) [%s%5d%s] %s%-29.29s%s %-22.22s ", + counter, QGRN, trig_index[i]->vnum, QNRM, QCYN, + trig_index[i]->proto->name, QNRM, + trig_index[i]->proto->script ? trig_index[i]->proto->script : "-"); if (trig_index[i]->proto->attach_type == OBJ_TRIGGER) { sprintbit(GET_TRIG_TYPE(trig_index[i]->proto), otrig_types, trgtypes, sizeof(trgtypes)); diff --git a/src/oedit.c b/src/oedit.c index ba9cb44..7fe7129 100644 --- a/src/oedit.c +++ b/src/oedit.c @@ -21,7 +21,7 @@ #include "genzon.h" #include "oasis.h" #include "improved-edit.h" -#include "dg_olc.h" +#include "py_olc.h" #include "fight.h" #include "modify.h" diff --git a/src/players.c b/src/players.c index edb6f66..32fda5c 100644 --- a/src/players.c +++ b/src/players.c @@ -15,12 +15,12 @@ #include "db.h" #include "handler.h" #include "pfdefaults.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "comm.h" #include "interpreter.h" #include "genolc.h" /* for strip_cr */ #include "config.h" /* for pclean_criteria[] */ -#include "dg_scripts.h" /* To enable saving of player variables to disk */ +#include "py_triggers.h" /* To enable saving of player variables to disk */ #include "quest.h" #include "toml.h" #include "toml_utils.h" diff --git a/src/protocol.c b/src/protocol.c index 1efab4b..dba98ba 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -32,7 +32,7 @@ #include "db.h" #include "screen.h" #include "improved-edit.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "act.h" #include "modify.h" diff --git a/src/dg_comm.c b/src/py_comm.c similarity index 82% rename from src/dg_comm.c rename to src/py_comm.c index 1c2eb39..976a6f8 100644 --- a/src/dg_comm.c +++ b/src/py_comm.c @@ -1,21 +1,13 @@ -/************************************************************************** -* File: dg_comm.c Part of tbaMUD * -* Usage: Contains routines to handle mud to player communication. * -* * -* All rights reserved. See license for complete information. * -* * -* Death's Gate MUD is based on CircleMUD, Copyright (C) 1993, 94. * -* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * -* * -* $Author: Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_comm.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "utils.h" #include "comm.h" #include "handler.h" diff --git a/src/dg_db_scripts.c b/src/py_db_scripts.c similarity index 89% rename from src/dg_db_scripts.c rename to src/py_db_scripts.c index 54b61d2..b44773b 100644 --- a/src/dg_db_scripts.c +++ b/src/py_db_scripts.c @@ -1,25 +1,17 @@ -/************************************************************************** -* File: dg_db_scripts.c Part of tbaMUD * -* Usage: Contains routines to handle db functions for scripts and trigs. * -* * -* All rights reserved. See license for complete information. * -* * -* Death's Gate MUD is based on CircleMUD, Copyright (C) 1993, 94. * -* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * -* * -* $Author: Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_db_scripts.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "utils.h" #include "db.h" #include "handler.h" -#include "dg_event.h" +#include "py_event.h" #include "comm.h" #include "constants.h" #include "interpreter.h" /* For half_chop */ @@ -97,6 +89,7 @@ static void trig_data_init(trig_data *this_data) this_data->data_type = 0; this_data->name = NULL; this_data->trigger_type = 0; + this_data->script = NULL; this_data->cmdlist = NULL; this_data->curr_state = NULL; this_data->narg = 0; @@ -123,6 +116,8 @@ void trig_data_copy(trig_data *this_data, const trig_data *trg) log("Trigger with no name! (%d)", trg->nr); } this_data->trigger_type = trg->trigger_type; + if (trg->script) + this_data->script = strdup(trg->script); this_data->cmdlist = trg->cmdlist; this_data->narg = trg->narg; if (trg->arglist) this_data->arglist = strdup(trg->arglist); diff --git a/src/dg_event.c b/src/py_event.c similarity index 92% rename from src/dg_event.c rename to src/py_event.c index d146a8c..37d71b4 100644 --- a/src/dg_event.c +++ b/src/py_event.c @@ -1,27 +1,15 @@ /** -* @file dg_event.c -* This file contains a simplified event system to allow trigedit -* to use the "wait" command, causing a delay in the middle of a script. -* This system could easily be expanded by coders who wish to implement -* an event driven mud. +* @file py_event.c * -* Part of the core tbaMUD source code distribution, which is a derivative -* of, and continuation of, CircleMUD. -* -* This source code, which was not part of the CircleMUD legacy code, -* was created by the following people: -* $Author: Mark A. Heilpern/egreen/Welcor $ -* $Date: 2004/10/11 12:07:00$ -* $Revision: 1.0.14 $ +* This set of code was not originally part of the circlemud distribution. */ - #include "conf.h" #include "sysdep.h" #include "structs.h" #include "utils.h" #include "db.h" -#include "dg_event.h" +#include "py_event.h" #include "constants.h" #include "comm.h" /* For access to the game pulse */ #include "mud_event.h" diff --git a/src/dg_event.h b/src/py_event.h similarity index 77% rename from src/dg_event.h rename to src/py_event.h index ee36e47..718a2dd 100644 --- a/src/dg_event.h +++ b/src/py_event.h @@ -1,21 +1,11 @@ /** -* @file dg_event.h -* This file contains defines for the simplified event system to allow trigedit -* to use the "wait" command, causing a delay in the middle of a script. -* This system could easily be expanded by coders who wish to implement -* an event driven mud. +* @file py_event.h * -* Part of the core tbaMUD source code distribution, which is a derivative -* of, and continuation of, CircleMUD. -* -* This source code, which was not part of the CircleMUD legacy code, -* is attributed to: -* $Author: Mark A. Heilpern/egreen/Welcor $ -* $Date: 2004/10/11 12:07:00$ -* $Revision: 1.0.14 $ +* This set of code was not originally part of the circlemud distribution. */ -#ifndef _DG_EVENT_H_ -#define _DG_EVENT_H_ + +#ifndef _PY_EVENT_H_ +#define _PY_EVENT_H_ /** How often will heartbeat() call the 'wait' event function? * @deprecated Currently not used. */ @@ -80,4 +70,4 @@ long queue_elmt_key(struct q_element *qe); void queue_free(struct dg_queue *q); int event_is_queued(struct event *event); -#endif /* _DG_EVENT_H_ */ +#endif /* _PY_EVENT_H_ */ diff --git a/src/dg_handler.c b/src/py_handler.c similarity index 85% rename from src/dg_handler.c rename to src/py_handler.c index ef2eab6..a1afeba 100644 --- a/src/dg_handler.c +++ b/src/py_handler.c @@ -1,27 +1,19 @@ -/************************************************************************** -* File: dg_handler.c Part of tbaMUD * -* Usage: Contains functions to handle memory for scripts. * -* * -* All rights reserved. See license for complete information. * -* * -* Death's Gate MUD is based on CircleMUD, Copyright (C) 1993, 94. * -* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * -* * -* $Author: Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -***************************************************************************/ +/** +* @file py_handler.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" #include "utils.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "comm.h" #include "db.h" #include "handler.h" #include "spells.h" -#include "dg_event.h" +#include "py_event.h" #include "constants.h" /* frees memory associated with var */ @@ -76,6 +68,11 @@ void free_trigger(struct trig_data *trig) free(trig->name); trig->name = NULL; + if (trig->script) { + free(trig->script); + trig->script = NULL; + } + if (trig->arglist) { free(trig->arglist); trig->arglist = NULL; diff --git a/src/dg_misc.c b/src/py_misc.c similarity index 93% rename from src/dg_misc.c rename to src/py_misc.c index ed572dd..d509bcb 100644 --- a/src/dg_misc.c +++ b/src/py_misc.c @@ -1,21 +1,18 @@ -/************************************************************************** -* File: dg_misc.c Part of tbaMUD * -* Usage: Contains general functions for script usage. * -* * -* $Author: Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_misc.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" #include "utils.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "comm.h" #include "interpreter.h" #include "handler.h" -#include "dg_event.h" +#include "py_event.h" #include "db.h" #include "screen.h" #include "spells.h" @@ -26,6 +23,15 @@ /* copied from spell_parser.c: */ #define SINFO spell_info[spellnum] +struct room_data *dg_room_of_obj(struct obj_data *obj) +{ + if (IN_ROOM(obj) != NOWHERE) return &world[IN_ROOM(obj)]; + if (obj->carried_by) return &world[IN_ROOM(obj->carried_by)]; + if (obj->worn_by) return &world[IN_ROOM(obj->worn_by)]; + if (obj->in_obj) return (dg_room_of_obj(obj->in_obj)); + return NULL; +} + /* Cast a spell; can be called by mobiles, objects and rooms, and no level * check is required. Note that mobs should generally use the normal 'cast' @@ -307,4 +313,3 @@ void script_damage(struct char_data *vict, int dam) die(vict, NULL); } } - diff --git a/src/dg_mobcmd.c b/src/py_mobcmd.c similarity index 97% rename from src/dg_mobcmd.c rename to src/py_mobcmd.c index 3eabb6a..4c4003b 100644 --- a/src/dg_mobcmd.c +++ b/src/py_mobcmd.c @@ -1,18 +1,15 @@ -/************************************************************************** -* File: dg_mobcmd.c Part of tbaMUD * -* Usage: Contains the mobile script commands. * -* * -* $Author: N'Atas-ha/Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_mobcmd.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" #include "utils.h" #include "screen.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "db.h" #include "handler.h" #include "interpreter.h" diff --git a/src/dg_objcmd.c b/src/py_objcmd.c similarity index 97% rename from src/dg_objcmd.c rename to src/py_objcmd.c index 29caa33..bbb252f 100644 --- a/src/dg_objcmd.c +++ b/src/py_objcmd.c @@ -1,17 +1,14 @@ -/************************************************************************** -* File: dg_objcmd.c Part of tbaMUD * -* Usage: Contains the command_interpreter for objects, object commands. * -* * -* $Author: galion/Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_objcmd.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" #include "screen.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "utils.h" #include "comm.h" #include "interpreter.h" diff --git a/src/dg_olc.c b/src/py_olc.c similarity index 98% rename from src/dg_olc.c rename to src/py_olc.c index 4934e6a..724f5af 100644 --- a/src/dg_olc.c +++ b/src/py_olc.c @@ -1,11 +1,8 @@ -/************************************************************************** -* File: dg_olc.c Part of tbaMUD * -* Usage: This source file is used in extending Oasis OLC for trigedit. * -* * -* $Author: Chris Jacobsen/Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_olc.h +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" @@ -16,8 +13,8 @@ #include "genolc.h" #include "interpreter.h" #include "oasis.h" -#include "dg_olc.h" -#include "dg_event.h" +#include "py_olc.h" +#include "py_event.h" #include "genzon.h" /* for real_zone_by_thing */ #include "constants.h" /* for the *trig_types */ #include "modify.h" /* for smash_tilde */ diff --git a/src/dg_olc.h b/src/py_olc.h similarity index 62% rename from src/dg_olc.h rename to src/py_olc.h index ecb4e81..8a3ae0e 100644 --- a/src/dg_olc.h +++ b/src/py_olc.h @@ -1,20 +1,13 @@ /** -* @file dg_olc.h -* This source file is used in extending Oasis OLC for trigedit. +* @file py_olc.c * -* Part of the core tbaMUD source code distribution, which is a derivative -* of, and continuation of, CircleMUD. -* -* This source code, which was not part of the CircleMUD legacy code, -* was created by the following people: -* $Author: Mark A. Heilpern/egreen/Welcor $ -* $Date: 2004/10/11 12:07:00$ -* $Revision: 1.0.14 $ +* This set of code was not originally part of the circlemud distribution. */ -#ifndef _DG_OLC_H_ -#define _DG_OLC_H_ -#include "dg_scripts.h" +#ifndef _PY_OLC_H_ +#define _PY_OLC_H_ + +#include "py_triggers.h" #define NUM_TRIG_TYPE_FLAGS 21 @@ -46,4 +39,4 @@ void dg_script_menu(struct descriptor_data *d); int dg_script_edit_parse(struct descriptor_data *d, char *arg); -#endif /* _DG_OLC_H_ */ +#endif /* _PY_OLC_H_ */ diff --git a/src/dg_scripts.c b/src/py_script_driver.c similarity index 92% rename from src/dg_scripts.c rename to src/py_script_driver.c index 5dad339..58bf50e 100644 --- a/src/dg_scripts.c +++ b/src/py_script_driver.c @@ -1,26 +1,18 @@ /** -* @file dg_scripts.c -* Contains the main script driver interface. +* @file py_script_driver.c * -* Part of the core tbaMUD source code distribution, which is a derivative -* of, and continuation of, CircleMUD. -* -* This source code, which was not part of the CircleMUD legacy code, -* was created by the following people: -* $Author: Mark A. Heilpern/egreen/Welcor $ -* $Date: 2004/10/11 12:07:00$ -* $Revision: 1.0.14 $ +* This set of code was not originally part of the circlemud distribution. */ #include "conf.h" #include "sysdep.h" #include "structs.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "utils.h" #include "comm.h" #include "interpreter.h" #include "handler.h" -#include "dg_event.h" +#include "py_event.h" #include "db.h" #include "screen.h" #include "constants.h" @@ -31,6 +23,7 @@ #include "modify.h" #include "toml.h" #include "toml_utils.h" +#include "py_scripts.h" #define PULSES_PER_MUD_HOUR (SECS_PER_MUD_HOUR*PASSES_PER_SEC) @@ -40,6 +33,7 @@ static room_data *find_room(long n); static void do_stat_trigger(struct char_data *ch, trig_data *trig); static void script_stat(char_data *ch, struct script_data *sc); static int remove_trigger(struct script_data *sc, char *name); +#if 0 /* DG scripting language removed */ static int is_num(char *arg); static void eval_op(char *op, char *lhs, char *rhs, char *result, void *go, struct script_data *sc, trig_data *trig); @@ -73,9 +67,12 @@ static void dg_letter_value(struct script_data *sc, trig_data *trig, char *cmd); static struct cmdlist_element * find_case(struct trig_data *trig, struct cmdlist_element *cl, void *go, struct script_data *sc, int type, char *cond); static struct cmdlist_element *find_done(struct cmdlist_element *cl); +#endif static struct char_data *find_char_by_uid_in_lookup_table(long uid); static struct obj_data *find_obj_by_uid_in_lookup_table(long uid); +#if 0 /* DG scripting language removed */ static EVENTFUNC(trig_wait_event); +#endif /* Return pointer to first occurrence of string ct in cs, or NULL if not @@ -708,6 +705,7 @@ void check_time_triggers(void) } } +#if 0 /* DG scripting language removed */ static EVENTFUNC(trig_wait_event) { struct wait_event_data *wait_event_obj = (struct wait_event_data *)event_obj; @@ -756,10 +754,10 @@ static EVENTFUNC(trig_wait_event) /* Do not reenqueue*/ return 0; } +#endif static void do_stat_trigger(struct char_data *ch, trig_data *trig) { - struct cmdlist_element *cmd_list; char sb[MAX_STRING_LENGTH], buf[MAX_STRING_LENGTH]; int len = 0; @@ -790,20 +788,8 @@ static void do_stat_trigger(struct char_data *ch, trig_data *trig) ((GET_TRIG_ARG(trig) && *GET_TRIG_ARG(trig)) ? GET_TRIG_ARG(trig) : "None")); - len += snprintf(sb + len, sizeof(sb)-len, "Commands:\r\n"); - - cmd_list = trig->cmdlist; - while (cmd_list) { - if (cmd_list->cmd) - len += snprintf(sb + len, sizeof(sb)-len, "%s\r\n", cmd_list->cmd); - - if (len>MAX_STRING_LENGTH-80) { - snprintf(sb + len, sizeof(sb)-len, "*** Overflow - script too long! ***\r\n"); - break; - } - - cmd_list = cmd_list->next; - } + len += snprintf(sb + len, sizeof(sb)-len, "Script: %s\r\n", + (trig->script && *trig->script) ? trig->script : "None"); page_string(ch->desc, sb, 1); } @@ -1357,6 +1343,7 @@ void script_log(const char *format, ...) va_end(args); } +#if 0 /* DG scripting language removed */ /* Returns 1 if string is all digits, else 0. Bugfixed - would have returned * true on num="------". */ static int is_num(char *arg) @@ -1474,6 +1461,7 @@ static void eval_op(char *op, char *lhs, char *rhs, char *result, void *go, sprintf(result, "%d", !*rhs); } } +#endif /* p points to the first quote, returns the matching end quote, or the last * non-null char in p.*/ @@ -1490,6 +1478,7 @@ char *matching_quote(char *p) return p; } +#if 0 /* DG scripting language removed */ /* p points to the first paren. returns a pointer to the matching closing * paren, or the last non-null char in p. */ static char *matching_paren(char *p) @@ -2262,6 +2251,7 @@ ACMD(do_vdelete) send_to_char(ch, "Deleted.\r\n"); } +#endif /* Called from do_set - return 0 for failure, 1 for success. ch and vict are * verified. */ @@ -2282,6 +2272,7 @@ int perform_set_dg_var(struct char_data *ch, struct char_data *vict, char *val_a return 1; } +#if 0 /* DG scripting language removed */ /* Delete a variable from the globals of another script. * 'rdelete ' */ static void process_rdelete(struct script_data *sc, trig_data *trig, char *cmd) @@ -2465,6 +2456,7 @@ static void dg_letter_value(struct script_data *sc, trig_data *trig, char *cmd) *(junk+1) = '\0'; add_var(&GET_TRIG_VARS(trig), varname, junk, sc->context); } +#endif /* This is the core driver for scripts. * Arguments: @@ -2486,14 +2478,8 @@ int script_driver(void *go_adress, trig_data *trig, int type, int mode) { static int depth = 0; int ret_val = 1; - struct cmdlist_element *cl; - char cmd[MAX_INPUT_LENGTH], *p; - struct script_data *sc = 0; - struct cmdlist_element *temp; void *go = NULL; - - void obj_command_interpreter(obj_data *obj, char *argument); - void wld_command_interpreter(struct room_data *room, char *argument); + struct script_data *sc = NULL; switch (type) { case MOB_TRIGGER: @@ -2542,178 +2528,23 @@ int script_driver(void *go_adress, trig_data *trig, int type, int mode) if (mode == TRIG_NEW) { GET_TRIG_DEPTH(trig) = 1; GET_TRIG_LOOPS(trig) = 0; - sc->context = 0; + if (sc) + sc->context = 0; } dg_owner_purged = 0; - for (cl = (mode == TRIG_NEW) ? trig->cmdlist : trig->curr_state; - cl && GET_TRIG_DEPTH(trig); cl = cl->next) { - for (p = cl->cmd; *p && isspace(*p); p++); + ret_val = python_trigger_run(go, trig, type, mode); - if (*p == '*') /* comment */ - continue; - - else if (!strn_cmp(p, "if ", 3)) { - if (process_if(p + 3, go, sc, trig, type)) - GET_TRIG_DEPTH(trig)++; - else - cl = find_else_end(trig, cl, go, sc, type); - } - - else if (!strn_cmp("elseif ", p, 7) || - !strn_cmp("else", p, 4)) { - /* If not in an if-block, ignore the extra 'else[if]' and warn about it. */ - if (GET_TRIG_DEPTH(trig) == 1) { - script_log("Trigger VNum %d has 'else' without 'if'.", - GET_TRIG_VNUM(trig)); - continue; - } - cl = find_end(trig, cl); - GET_TRIG_DEPTH(trig)--; - } else if (!strn_cmp("while ", p, 6)) { - temp = find_done(cl); - if (!temp) { - script_log("Trigger VNum %d has 'while' without 'done'.", - GET_TRIG_VNUM(trig)); - return ret_val; - } - if (process_if(p + 6, go, sc, trig, type)) { - temp->original = cl; - } else { - cl->loops = 0; - cl = temp; - } - } else if (!strn_cmp("switch ", p, 7)) { - cl = find_case(trig, cl, go, sc, type, p + 7); - } else if (!strn_cmp("end", p, 3)) { - /* If not in an if-block, ignore the extra 'end' and warn about it. */ - if (GET_TRIG_DEPTH(trig) == 1) { - script_log("Trigger VNum %d has 'end' without 'if'.", - GET_TRIG_VNUM(trig)); - continue; - } - GET_TRIG_DEPTH(trig)--; - } else if (!strn_cmp("done", p, 4)) { - /* if in a while loop, cl->original is non-NULL */ - if (cl->original) { - char *orig_cmd = cl->original->cmd; - while (*orig_cmd && isspace(*orig_cmd)) orig_cmd++; - if (cl->original && process_if(orig_cmd + 6, go, sc, trig, - type)) { - cl = cl->original; - cl->loops++; - GET_TRIG_LOOPS(trig)++; - if (cl->loops == 30) { - cl->loops = 0; - process_wait(go, trig, type, "wait 1", cl); - depth--; - return ret_val; - } - if (GET_TRIG_LOOPS(trig) >= 100) { - script_log("Trigger VNum %d has looped 100 times!!!", - GET_TRIG_VNUM(trig)); - break; - } - } else { - /* if we're falling through a switch statement, this ends it. */ - } - } - } else if (!strn_cmp("break", p, 5)) { - cl = find_done(cl); - } else if (!strn_cmp("case", p, 4)) { - /* Do nothing, this allows multiple cases to a single instance */ - } - - else { - var_subst(go, sc, trig, type, p, cmd); - - if (!strn_cmp(cmd, "eval ", 5)) - process_eval(go, sc, trig, type, cmd); - - else if (!strn_cmp(cmd, "nop ", 4)); /* nop: do nothing */ - - else if (!strn_cmp(cmd, "extract ", 8)) - extract_value(sc, trig, cmd); - - else if (!strn_cmp(cmd, "dg_letter ", 10)) - dg_letter_value(sc, trig, cmd); - - else if (!strn_cmp(cmd, "makeuid ", 8)) - makeuid_var(go, sc, trig, type, cmd); - - else if (!strn_cmp(cmd, "halt", 4)) - break; - - else if (!strn_cmp(cmd, "dg_cast ", 8)) - do_dg_cast(go, sc, trig, type, cmd); - - else if (!strn_cmp(cmd, "dg_affect ", 10)) - do_dg_affect(go, sc, trig, type, cmd); - - else if (!strn_cmp(cmd, "global ", 7)) - process_global(sc, trig, cmd, sc->context); - - else if (!strn_cmp(cmd, "context ", 8)) - process_context(sc, trig, cmd); - - else if (!strn_cmp(cmd, "remote ", 7)) - process_remote(sc, trig, cmd); - - else if (!strn_cmp(cmd, "rdelete ", 8)) - process_rdelete(sc, trig, cmd); - - else if (!strn_cmp(cmd, "return ", 7)) - ret_val = process_return(trig, cmd); - - else if (!strn_cmp(cmd, "set ", 4)) - process_set(sc, trig, cmd); - - else if (!strn_cmp(cmd, "unset ", 6)) - process_unset(sc, trig, cmd); - - else if (!strn_cmp(cmd, "wait ", 5)) { - process_wait(go, trig, type, cmd, cl); - depth--; - return ret_val; - } - - else if (!strn_cmp(cmd, "attach ", 7)) - process_attach(go, sc, trig, type, cmd); - - else if (!strn_cmp(cmd, "detach ", 7)) - process_detach(go, sc, trig, type, cmd); - - else { - switch (type) { - case MOB_TRIGGER: - if (!script_command_interpreter((char_data *) go, cmd)) - command_interpreter((char_data *) go, cmd); - break; - case OBJ_TRIGGER: - obj_command_interpreter((obj_data *) go, cmd); - break; - case WLD_TRIGGER: - wld_command_interpreter((struct room_data *) go, cmd); - break; - } - if (dg_owner_purged) { - depth--; - if (type == OBJ_TRIGGER) - *(obj_data **)go_adress = NULL; - return ret_val; - } - } - } + if (dg_owner_purged) { + depth--; + if (type == OBJ_TRIGGER) + *(obj_data **)go_adress = NULL; + return ret_val; } - switch (type) { /* the script may have been detached */ - case MOB_TRIGGER: sc = SCRIPT((char_data *) go); break; - case OBJ_TRIGGER: sc = SCRIPT((obj_data *) go); break; - case WLD_TRIGGER: sc = SCRIPT((room_data *) go); break; - } if (sc) - free_varlist(GET_TRIG_VARS(trig)); + free_varlist(GET_TRIG_VARS(trig)); GET_TRIG_VARS(trig) = NULL; GET_TRIG_DEPTH(trig) = 0; @@ -2764,6 +2595,95 @@ ACMD(do_tstat) send_to_char(ch, "Usage: tstat \r\n"); } +/* Command-line interface to rdelete. Named vdelete so people didn't think it + * was to delete rooms. */ +ACMD(do_vdelete) +{ + struct trig_var_data *vd, *vd_prev=NULL; + struct script_data *sc_remote=NULL; + char *var, *uid_p; + char buf[MAX_INPUT_LENGTH], buf2[MAX_INPUT_LENGTH]; + long uid; + room_data *room; + char_data *mob; + obj_data *obj; + + two_arguments(argument, buf, buf2); + var = buf; + uid_p = buf2; + skip_spaces(&var); + skip_spaces(&uid_p); + + + if (!*buf || !*buf2) { + send_to_char(ch, "Usage: vdelete { | * | all } \r\n"); + return; + } + + /* find the target script from the uid number */ + uid = atoi(buf2); + if (uid<=0) { + send_to_char(ch, "vdelete: illegal id specified.\r\n"); + return; + } + + if ((room = find_room(uid))) { + sc_remote = SCRIPT(room); + } else if ((mob = find_char(uid))) { + sc_remote = SCRIPT(mob); + } else if ((obj = find_obj(uid))) { + sc_remote = SCRIPT(obj); + } else { + send_to_char(ch, "vdelete: cannot resolve specified id.\r\n"); + return; + } + + if (sc_remote==NULL) { + send_to_char(ch, "That id represents no global variables.(1)\r\n"); + return; + } + + if (sc_remote->global_vars==NULL) { + send_to_char(ch, "That id represents no global variables.(2)\r\n"); + return; + } + + if (*var == '*' || is_abbrev(var, "all")) { + struct trig_var_data *vd_next; + for (vd = sc_remote->global_vars; vd; vd = vd_next) { + vd_next = vd->next; + free(vd->value); + free(vd->name); + free(vd); + } + sc_remote->global_vars = NULL; + send_to_char(ch, "All variables deleted from that id.\r\n"); + return; + } + + /* find the global */ + for (vd = sc_remote->global_vars; vd; vd_prev = vd, vd = vd->next) + if (!str_cmp(vd->name, var)) + break; + + if (!vd) { + send_to_char(ch, "That variable cannot be located.\r\n"); + return; + } + + /* ok, delete the variable */ + if (vd_prev) vd_prev->next = vd->next; + else sc_remote->global_vars = vd->next; + + /* and free up the space */ + free(vd->value); + free(vd->name); + free(vd); + + send_to_char(ch, "Deleted.\r\n"); +} + +#if 0 /* DG scripting language removed */ /* Scans for a case/default instance. Returns the line containg the correct * case instance, or the last line of the trigger if not found. */ static struct cmdlist_element * @@ -2822,6 +2742,7 @@ static struct cmdlist_element *find_done(struct cmdlist_element *cl) return c; } +#endif /* load in a character's saved variables */ diff --git a/src/py_scripts.c b/src/py_scripts.c new file mode 100644 index 0000000..ff55e73 --- /dev/null +++ b/src/py_scripts.c @@ -0,0 +1,2594 @@ +/** +* @file py_scripts.c +* +* This set of code was not originally part of the circlemud distribution. +*/ + +#define PY_SSIZE_T_CLEAN +#include + +#include "conf.h" +#include "sysdep.h" +#include +#include "structs.h" +#include "py_triggers.h" +#include "utils.h" +#include "comm.h" +#include "interpreter.h" +#include "db.h" +#include "handler.h" +#include "py_event.h" +#include "class.h" +#include "species.h" +#include "py_scripts.h" +#include "graph.h" + +#define SCRIPT_NAME_MAX 256 + +struct py_script_cache { + char *path; + time_t mtime; + PyObject *globals; + struct py_script_cache *next; +}; + +struct dsl_script_cache { + char *path; + time_t mtime; + PyObject *globals; + PyObject *func; + struct dsl_script_cache *next; +}; + +struct dsl_state { + char *path; + int self_type; + long self_uid; + time_t mtime; + PyObject *generator; + int running; + struct dsl_state *next; +}; + +struct py_trigger_context { + void *go; + int type; + struct trig_data *trig; +}; + +struct py_call_data { + char *script; + char *func; + int self_type; + long self_uid; + PyObject *args; + PyObject *kwargs; +}; + +struct dsl_call_data { + struct dsl_state *state; +}; + +static struct py_script_cache *script_cache = NULL; +static struct dsl_script_cache *dsl_cache = NULL; +static struct dsl_state *dsl_states = NULL; +static struct py_trigger_context *current_context = NULL; +static FILE *script_log_fp = NULL; +static char script_log_path[PATH_MAX]; + +static PyObject *mud_module = NULL; +static PyObject *mud_sleep_exc = NULL; +static const char *mud_dir_names[] = { + "n", "s", "e", "w", + "ne", "nw", "se", "sw", + "u", "d", + "north", "south", "east", "west", + "northeast", "northwest", "southeast", "southwest", + "up", "down", + NULL +}; + +static int python_audit_hook(const char *event, PyObject *args, void *userData); +static int python_path_is_script_log(const char *path); +static void python_log_exception(const char *context); +static void python_log_message(const char *message); +static void python_rotate_script_log(void); +static struct py_script_cache *python_load_script(const char *path); +static int python_is_dsl_script(const char *path); +static struct dsl_script_cache *dsl_load_script(const char *path); +static struct dsl_state *dsl_get_state(const char *path, int type, long uid); +static int dsl_trigger_run(void *go, struct trig_data *trig, int type, const char *path); +static char *dsl_build_source(const char *path); +static int dsl_run_state(struct dsl_state *state, PyObject *func, PyObject *npc, PyObject *room, + PyObject *pc, PyObject *object); +static PyObject *python_build_event(struct trig_data *trig, void *go, int type); +static PyObject *python_entity_from_uid(long uid, int kind); +static int python_uid_kind(long uid); +static long python_entity_uid_from_obj(PyObject *obj, int *kind); +static EVENTFUNC(python_call_event); +static EVENTFUNC(dsl_call_event); +static obj_data *python_find_obj(long uid); +static room_data *python_find_room(long uid); +static PyObject *MudEntity_getattro(PyObject *obj, PyObject *nameobj); +static PyObject *MudEntity_richcompare(PyObject *a, PyObject *b, int op); +static PyTypeObject MudEntityType; + +/* Mud entity type */ +typedef struct { + PyObject_HEAD + int kind; + long uid; +} MudEntity; + +static PyObject *MudEntity_repr(MudEntity *self) +{ + char buf[128]; + const char *kind = "unknown"; + + if (self->kind == MOB_TRIGGER) + kind = "mob"; + else if (self->kind == OBJ_TRIGGER) + kind = "obj"; + else if (self->kind == WLD_TRIGGER) + kind = "room"; + + snprintf(buf, sizeof(buf), "", kind, self->uid); + return PyUnicode_FromString(buf); +} + +static char_data *MudEntity_get_char(MudEntity *self) +{ + if (self->kind != MOB_TRIGGER) + return NULL; + return find_char(self->uid); +} + +static obj_data *MudEntity_get_obj(MudEntity *self) +{ + if (self->kind != OBJ_TRIGGER) + return NULL; + return python_find_obj(self->uid); +} + +static room_data *MudEntity_get_room(MudEntity *self) +{ + if (self->kind != WLD_TRIGGER) + return NULL; + return python_find_room(self->uid); +} + +static PyObject *MudEntity_get_kind(MudEntity *self, void *closure) +{ + return PyLong_FromLong(self->kind); +} + +static PyObject *MudEntity_get_uid(MudEntity *self, void *closure) +{ + return PyLong_FromLong(self->uid); +} + +static PyObject *MudEntity_get_vnum(MudEntity *self, void *closure) +{ + if (self->kind == MOB_TRIGGER) { + char_data *ch = MudEntity_get_char(self); + if (!ch) + Py_RETURN_NONE; + if (!IS_NPC(ch)) + return PyLong_FromLong(-1); + return PyLong_FromLong(GET_MOB_VNUM(ch)); + } + + if (self->kind == OBJ_TRIGGER) { + obj_data *obj = MudEntity_get_obj(self); + if (!obj) + Py_RETURN_NONE; + return PyLong_FromLong(GET_OBJ_VNUM(obj)); + } + + if (self->kind == WLD_TRIGGER) { + room_data *room = MudEntity_get_room(self); + if (!room) + Py_RETURN_NONE; + return PyLong_FromLong(room->number); + } + + Py_RETURN_NONE; +} + +static PyObject *MudEntity_get_name(MudEntity *self, void *closure) +{ + if (self->kind == MOB_TRIGGER) { + char_data *ch = MudEntity_get_char(self); + if (!ch) + Py_RETURN_NONE; + return PyUnicode_FromString(GET_NAME(ch)); + } + + if (self->kind == OBJ_TRIGGER) { + obj_data *obj = MudEntity_get_obj(self); + const char *name; + if (!obj) + Py_RETURN_NONE; + name = obj->short_description ? obj->short_description : obj->name; + return PyUnicode_FromString(name ? name : ""); + } + + if (self->kind == WLD_TRIGGER) { + room_data *room = MudEntity_get_room(self); + if (!room) + Py_RETURN_NONE; + return PyUnicode_FromString(room->name ? room->name : ""); + } + + Py_RETURN_NONE; +} + +static PyObject *MudEntity_build_room_contents(room_data *room) +{ + PyObject *list; + obj_data *obj; + + if (!room) + Py_RETURN_NONE; + + list = PyList_New(0); + if (!list) + return NULL; + + for (obj = room->contents; obj; obj = obj->next_content) { + PyObject *ent = python_entity_from_uid(obj_script_id(obj), OBJ_TRIGGER); + if (!ent) + continue; + PyList_Append(list, ent); + Py_DECREF(ent); + } + + return list; +} + +static PyObject *MudEntity_build_room_people(room_data *room) +{ + PyObject *list; + char_data *ch; + + if (!room) + Py_RETURN_NONE; + + list = PyList_New(0); + if (!list) + return NULL; + + for (ch = room->people; ch; ch = ch->next_in_room) { + PyObject *ent = python_entity_from_uid(char_script_id(ch), MOB_TRIGGER); + if (!ent) + continue; + PyList_Append(list, ent); + Py_DECREF(ent); + } + + return list; +} + +static PyObject *MudEntity_build_obj_oval(obj_data *obj) +{ + PyObject *list; + int i; + + if (!obj) + Py_RETURN_NONE; + + list = PyList_New(NUM_OBJ_VAL_POSITIONS); + if (!list) + return NULL; + + for (i = 0; i < NUM_OBJ_VAL_POSITIONS; i++) { + PyObject *val = PyLong_FromLong(GET_OBJ_VAL(obj, i)); + if (!val) { + Py_DECREF(list); + return NULL; + } + PyList_SetItem(list, i, val); + } + + return list; +} + +static PyObject *MudEntity_getattro(PyObject *obj, PyObject *nameobj) +{ + MudEntity *self = (MudEntity *)obj; + const char *name; + + if (!PyUnicode_Check(nameobj)) + return PyObject_GenericGetAttr(obj, nameobj); + + name = PyUnicode_AsUTF8(nameobj); + if (!name) + return NULL; + + if (self->kind == MOB_TRIGGER) { + char_data *ch = MudEntity_get_char(self); + if (!ch) + Py_RETURN_NONE; + if (!strcmp(name, "health")) + return PyLong_FromLong(GET_HIT(ch)); + if (!strcmp(name, "mana")) + return PyLong_FromLong(GET_MANA(ch)); + if (!strcmp(name, "stamina") || !strcmp(name, "move")) + return PyLong_FromLong(GET_STAMINA(ch)); + if (!strcmp(name, "max_health")) + return PyLong_FromLong(GET_MAX_HIT(ch)); + if (!strcmp(name, "max_mana")) + return PyLong_FromLong(GET_MAX_MANA(ch)); + if (!strcmp(name, "max_stamina") || !strcmp(name, "max_move")) + return PyLong_FromLong(GET_MAX_STAMINA(ch)); + if (!strcmp(name, "class")) + return PyUnicode_FromString(CLASS_NAME(ch)); + if (!strcmp(name, "class_id")) + return PyLong_FromLong(GET_CLASS(ch)); + if (!strcmp(name, "species")) + return PyUnicode_FromString(get_species_name(GET_SPECIES(ch))); + if (!strcmp(name, "species_id")) + return PyLong_FromLong(GET_SPECIES(ch)); + if (!strcmp(name, "is_pc")) + return PyBool_FromLong(!IS_NPC(ch)); + if (!strcmp(name, "is_npc")) + return PyBool_FromLong(IS_NPC(ch)); + if (!strcmp(name, "keyword")) { + const char *kw = IS_NPC(ch) ? GET_KEYWORDS(ch) : GET_NAME(ch); + return PyUnicode_FromString(kw ? kw : ""); + } + if (!strcmp(name, "room")) { + if (IN_ROOM(ch) != NOWHERE) { + room_data *room = &world[IN_ROOM(ch)]; + return python_entity_from_uid(room_script_id(room), WLD_TRIGGER); + } + Py_RETURN_NONE; + } + } + + if (self->kind == OBJ_TRIGGER) { + if (!strcmp(name, "keyword")) { + obj_data *objp = MudEntity_get_obj(self); + return PyUnicode_FromString(objp && objp->name ? objp->name : ""); + } + if (!strcmp(name, "oval")) + return MudEntity_build_obj_oval(MudEntity_get_obj(self)); + if (!strcmp(name, "room")) { + obj_data *objp = MudEntity_get_obj(self); + room_rnum rnum; + if (!objp) + Py_RETURN_NONE; + rnum = objp->in_room; + if (rnum != NOWHERE) { + room_data *room = &world[rnum]; + return python_entity_from_uid(room_script_id(room), WLD_TRIGGER); + } + if (objp->carried_by && IN_ROOM(objp->carried_by) != NOWHERE) { + room_data *room = &world[IN_ROOM(objp->carried_by)]; + return python_entity_from_uid(room_script_id(room), WLD_TRIGGER); + } + if (objp->worn_by && IN_ROOM(objp->worn_by) != NOWHERE) { + room_data *room = &world[IN_ROOM(objp->worn_by)]; + return python_entity_from_uid(room_script_id(room), WLD_TRIGGER); + } + Py_RETURN_NONE; + } + } + + if (self->kind == WLD_TRIGGER) { + if (!strcmp(name, "contents")) + return MudEntity_build_room_contents(MudEntity_get_room(self)); + if (!strcmp(name, "people")) + return MudEntity_build_room_people(MudEntity_get_room(self)); + } + + return PyObject_GenericGetAttr(obj, nameobj); +} + +static int MudEntity_name_matches(MudEntity *ent, const char *needle) +{ + if (!ent || !needle || !*needle) + return 0; + + if (ent->kind == MOB_TRIGGER) { + char_data *ch = MudEntity_get_char(ent); + if (!ch) + return 0; + return isname(needle, IS_NPC(ch) ? GET_KEYWORDS(ch) : GET_NAME(ch)); + } + + if (ent->kind == OBJ_TRIGGER) { + obj_data *obj = MudEntity_get_obj(ent); + if (!obj || !obj->name) + return 0; + return isname(needle, obj->name); + } + + return 0; +} + +static PyObject *MudEntity_richcompare(PyObject *a, PyObject *b, int op) +{ + MudEntity *left = NULL; + MudEntity *right = NULL; + int match = 0; + + if (op != Py_EQ && op != Py_NE) + Py_RETURN_NOTIMPLEMENTED; + + if (PyObject_TypeCheck(a, &MudEntityType)) + left = (MudEntity *)a; + if (PyObject_TypeCheck(b, &MudEntityType)) + right = (MudEntity *)b; + + if (left && right) { + match = (left->kind == right->kind && left->uid == right->uid); + } else if (left && PyUnicode_Check(b)) { + const char *needle = PyUnicode_AsUTF8(b); + if (!needle) + return NULL; + match = MudEntity_name_matches(left, needle); + } else if (right && PyUnicode_Check(a)) { + const char *needle = PyUnicode_AsUTF8(a); + if (!needle) + return NULL; + match = MudEntity_name_matches(right, needle); + } else if (left && PyLong_Check(b)) { + long uid = PyLong_AsLong(b); + match = (left->uid == uid); + } else if (right && PyLong_Check(a)) { + long uid = PyLong_AsLong(a); + match = (right->uid == uid); + } else { + Py_RETURN_NOTIMPLEMENTED; + } + + if (op == Py_NE) + match = !match; + + return PyBool_FromLong(match); +} + +static PyGetSetDef MudEntity_getset[] = { + {"kind", (getter)MudEntity_get_kind, NULL, "entity kind", NULL}, + {"uid", (getter)MudEntity_get_uid, NULL, "entity uid", NULL}, + {"vnum", (getter)MudEntity_get_vnum, NULL, "entity vnum", NULL}, + {"name", (getter)MudEntity_get_name, NULL, "entity name", NULL}, + {NULL} +}; + +static PyTypeObject MudEntityType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mud.Entity", + .tp_basicsize = sizeof(MudEntity), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_repr = (reprfunc)MudEntity_repr, + .tp_getattro = MudEntity_getattro, + .tp_richcompare = MudEntity_richcompare, + .tp_getset = MudEntity_getset, +}; + +static PyObject *mud_do(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *command = NULL; + PyObject *actor_obj = Py_None; + static char *kwlist[] = {"command", "actor", NULL}; + int kind = -1; + long uid = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O:do", kwlist, &command, &actor_obj)) + return NULL; + + if (actor_obj != Py_None) { + uid = python_entity_uid_from_obj(actor_obj, &kind); + } else if (current_context && current_context->trig) { + if (current_context->type == MOB_TRIGGER) { + char_data *ch = (char_data *)current_context->go; + if (ch) + uid = char_script_id(ch); + kind = MOB_TRIGGER; + } else if (current_context->type == OBJ_TRIGGER) { + obj_data *obj = (obj_data *)current_context->go; + if (obj) + uid = obj_script_id(obj); + kind = OBJ_TRIGGER; + } else if (current_context->type == WLD_TRIGGER) { + room_data *room = (room_data *)current_context->go; + if (room) + uid = room_script_id(room); + kind = WLD_TRIGGER; + } + } + + if (kind == MOB_TRIGGER) { + char_data *ch = find_char(uid); + if (!ch) + Py_RETURN_FALSE; + command_interpreter(ch, (char *)command); + Py_RETURN_TRUE; + } + + if (kind == OBJ_TRIGGER) { + obj_data *obj = python_find_obj(uid); + if (!obj) + Py_RETURN_FALSE; + obj_command_interpreter(obj, (char *)command); + Py_RETURN_TRUE; + } + + if (kind == WLD_TRIGGER) { + room_data *room = python_find_room(uid); + if (!room) + Py_RETURN_FALSE; + wld_command_interpreter(room, (char *)command); + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject *mud_emote(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *message = NULL; + PyObject *actor_obj = Py_None; + static char *kwlist[] = {"message", "actor", NULL}; + int kind = -1; + long uid = 0; + char buf[MAX_STRING_LENGTH]; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O:emote", kwlist, &message, &actor_obj)) + return NULL; + + if (actor_obj != Py_None) { + uid = python_entity_uid_from_obj(actor_obj, &kind); + } else if (current_context && current_context->trig) { + if (current_context->type == MOB_TRIGGER) { + char_data *ch = (char_data *)current_context->go; + if (ch) + uid = char_script_id(ch); + kind = MOB_TRIGGER; + } + } + + if (kind == MOB_TRIGGER) { + char_data *ch = find_char(uid); + if (!ch) + Py_RETURN_FALSE; + snprintf(buf, sizeof(buf), "emote %s", message); + command_interpreter(ch, buf); + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject *mud_move(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *direction_obj = NULL; + const char *direction = NULL; + long dest_vnum = -1; + PyObject *actor_obj = Py_None; + static char *kwlist[] = {"direction", "actor", NULL}; + int kind = -1; + long uid = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O:move", kwlist, &direction_obj, &actor_obj)) + return NULL; + + if (PyLong_Check(direction_obj)) { + dest_vnum = PyLong_AsLong(direction_obj); + } else if (PyUnicode_Check(direction_obj)) { + direction = PyUnicode_AsUTF8(direction_obj); + if (direction && is_number(direction)) + dest_vnum = atol(direction); + } else { + PyErr_SetString(PyExc_TypeError, "move expects a direction string or room vnum"); + return NULL; + } + + if (actor_obj != Py_None) { + uid = python_entity_uid_from_obj(actor_obj, &kind); + } else if (current_context && current_context->trig) { + if (current_context->type == MOB_TRIGGER) { + char_data *ch = (char_data *)current_context->go; + if (ch) + uid = char_script_id(ch); + kind = MOB_TRIGGER; + } + } + + if (kind == MOB_TRIGGER) { + char_data *ch = find_char(uid); + if (!ch) + Py_RETURN_FALSE; + if (dest_vnum > 0) { + room_rnum src = IN_ROOM(ch); + room_rnum target = real_room((room_vnum)dest_vnum); + int dir; + + if (src == NOWHERE || target == NOWHERE) + Py_RETURN_FALSE; + + dir = find_first_step_no_doors(src, target); + if (dir == BFS_ALREADY_THERE) + Py_RETURN_TRUE; + if (dir < 0) + Py_RETURN_FALSE; + + perform_move(ch, dir, 0); + Py_RETURN_TRUE; + } + + if (!direction) + Py_RETURN_FALSE; + command_interpreter(ch, (char *)direction); + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject *mud_log(PyObject *self, PyObject *args) +{ + const char *message = NULL; + + if (!PyArg_ParseTuple(args, "s:log", &message)) + return NULL; + + python_log_message(message); + Py_RETURN_NONE; +} + +static PyObject *mud_roll(PyObject *self, PyObject *args) +{ + const char *expr = NULL; + int count = 0; + int sides = 0; + int total = 0; + int i; + + if (!PyArg_ParseTuple(args, "s:roll", &expr)) + return NULL; + + if (!expr || !*expr) { + PyErr_SetString(PyExc_ValueError, "roll requires a dice string like 1d6"); + return NULL; + } + + if (sscanf(expr, "%dd%d", &count, &sides) != 2 || count <= 0 || sides <= 0) { + PyErr_SetString(PyExc_ValueError, "roll requires a dice string like 1d6"); + return NULL; + } + + if (!(sides == 4 || sides == 6 || sides == 8 || sides == 10 || + sides == 12 || sides == 20 || sides == 100)) { + PyErr_SetString(PyExc_ValueError, "roll supports d4, d6, d8, d10, d12, d20, d100"); + return NULL; + } + + for (i = 0; i < count; i++) + total += rand_number(1, sides); + + return PyLong_FromLong(total); +} + +static PyObject *mud_sleep(PyObject *self, PyObject *args) +{ + int seconds = 0; + struct py_call_data *data; + int kind = -1; + long uid = 0; + + if (!PyArg_ParseTuple(args, "i:sleep", &seconds)) + return NULL; + + if (seconds < 0) { + PyErr_SetString(PyExc_ValueError, "seconds must be non-negative"); + return NULL; + } + + if (!current_context || !current_context->trig || !current_context->trig->script) { + PyErr_SetString(PyExc_RuntimeError, "sleep requires an active trigger context"); + return NULL; + } + + if (current_context->type == MOB_TRIGGER) { + char_data *ch = (char_data *)current_context->go; + if (ch) + uid = char_script_id(ch); + kind = MOB_TRIGGER; + } else if (current_context->type == OBJ_TRIGGER) { + obj_data *obj = (obj_data *)current_context->go; + if (obj) + uid = obj_script_id(obj); + kind = OBJ_TRIGGER; + } else if (current_context->type == WLD_TRIGGER) { + room_data *room = (room_data *)current_context->go; + if (room) + uid = room_script_id(room); + kind = WLD_TRIGGER; + } + + data = (struct py_call_data *)malloc(sizeof(*data)); + if (!data) { + PyErr_SetString(PyExc_MemoryError, "sleep out of memory"); + return NULL; + } + + { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s%s", SCRIPTS_PREFIX, current_context->trig->script); + data->script = strdup(path); + } + data->func = strdup("on_trigger"); + data->args = PyTuple_New(0); + data->kwargs = PyDict_New(); + if (!data->args || !data->kwargs) { + if (data->args) + Py_DECREF(data->args); + if (data->kwargs) + Py_DECREF(data->kwargs); + free(data->script); + free(data->func); + free(data); + PyErr_SetString(PyExc_MemoryError, "sleep out of memory"); + return NULL; + } + + data->self_type = kind; + data->self_uid = uid; + + event_create(python_call_event, data, (long)(seconds * PASSES_PER_SEC)); + + PyErr_SetString(mud_sleep_exc, "sleep"); + return NULL; +} + +static PyObject *mud_echo_room(PyObject *self, PyObject *args) +{ + PyObject *room_obj = NULL; + const char *message = NULL; + int kind = -1; + long uid = 0; + + if (!PyArg_ParseTuple(args, "Os:echo_room", &room_obj, &message)) + return NULL; + + uid = python_entity_uid_from_obj(room_obj, &kind); + if (kind != WLD_TRIGGER) + Py_RETURN_FALSE; + + if (uid) { + room_data *room = python_find_room(uid); + if (room) { + room_rnum rnum = real_room(room->number); + if (rnum != NOWHERE) + send_to_room(rnum, "%s\r\n", message); + } + } + + Py_RETURN_TRUE; +} + +static PyObject *mud_send_char(PyObject *self, PyObject *args) +{ + PyObject *char_obj = NULL; + const char *message = NULL; + int kind = -1; + long uid = 0; + + if (!PyArg_ParseTuple(args, "Os:send_char", &char_obj, &message)) + return NULL; + + uid = python_entity_uid_from_obj(char_obj, &kind); + if (kind != MOB_TRIGGER) + Py_RETURN_FALSE; + + if (uid) { + char_data *ch = find_char(uid); + if (ch) + send_to_char(ch, "%s\r\n", message); + } + + Py_RETURN_TRUE; +} + +static PyObject *mud_call_later(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int seconds = 0; + const char *func = NULL; + PyObject *rest = NULL; + PyObject *call_args = NULL; + PyObject *call_kwargs = NULL; + struct py_call_data *data; + int kind = -1; + long uid = 0; + int nargs; + + if (!current_context || !current_context->trig || !current_context->trig->script) { + PyErr_SetString(PyExc_RuntimeError, "call_later requires an active trigger context"); + return NULL; + } + + nargs = (int)PyTuple_Size(args); + if (nargs < 2) { + PyErr_SetString(PyExc_TypeError, "call_later requires at least seconds and function name"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "is:call_later", &seconds, &func)) + return NULL; + + if (seconds < 0) { + PyErr_SetString(PyExc_ValueError, "seconds must be non-negative"); + return NULL; + } + + rest = PyTuple_GetSlice(args, 2, nargs); + if (!rest) + return NULL; + + call_args = rest; + if (kwargs) { + Py_INCREF(kwargs); + call_kwargs = kwargs; + } else { + call_kwargs = PyDict_New(); + } + if (!call_kwargs) { + Py_DECREF(call_args); + return NULL; + } + + data = (struct py_call_data *)malloc(sizeof(*data)); + if (!data) { + Py_DECREF(call_args); + Py_DECREF(call_kwargs); + PyErr_SetString(PyExc_MemoryError, "call_later out of memory"); + return NULL; + } + + { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s%s", SCRIPTS_PREFIX, current_context->trig->script); + data->script = strdup(path); + } + data->func = strdup(func); + data->args = call_args; + data->kwargs = call_kwargs; + + if (current_context->type == MOB_TRIGGER) { + char_data *ch = (char_data *)current_context->go; + if (ch) + uid = char_script_id(ch); + kind = MOB_TRIGGER; + } else if (current_context->type == OBJ_TRIGGER) { + obj_data *obj = (obj_data *)current_context->go; + if (obj) + uid = obj_script_id(obj); + kind = OBJ_TRIGGER; + } else if (current_context->type == WLD_TRIGGER) { + room_data *room = (room_data *)current_context->go; + if (room) + uid = room_script_id(room); + kind = WLD_TRIGGER; + } + + data->self_type = kind; + data->self_uid = uid; + + event_create(python_call_event, data, (long)(seconds * PASSES_PER_SEC)); + + Py_RETURN_NONE; +} + +static PyMethodDef mud_methods[] = { + {"do", (PyCFunction)mud_do, METH_VARARGS | METH_KEYWORDS, "Execute a command."}, + {"emote", (PyCFunction)mud_emote, METH_VARARGS | METH_KEYWORDS, "Emote as a character."}, + {"move", (PyCFunction)mud_move, METH_VARARGS | METH_KEYWORDS, "Move an entity in a direction."}, + {"roll", mud_roll, METH_VARARGS, "Roll dice like 1d6."}, + {"sleep", mud_sleep, METH_VARARGS, "Pause script execution."}, + {"log", mud_log, METH_VARARGS, "Write to script.log."}, + {"echo_room", mud_echo_room, METH_VARARGS, "Echo a message to a room."}, + {"send_char", mud_send_char, METH_VARARGS, "Send a message to a character."}, + {"call_later", (PyCFunction)mud_call_later, METH_VARARGS | METH_KEYWORDS, "Schedule a callback."}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef mud_module_def = { + PyModuleDef_HEAD_INIT, + "mud", + NULL, + -1, + mud_methods +}; + +static PyObject *PyInit_mud(void) +{ + PyObject *module; + + if (PyType_Ready(&MudEntityType) < 0) + return NULL; + + module = PyModule_Create(&mud_module_def); + if (!module) + return NULL; + + mud_sleep_exc = PyErr_NewException("mud.Sleep", NULL, NULL); + if (!mud_sleep_exc) + return NULL; + PyModule_AddObject(module, "Sleep", mud_sleep_exc); + + for (int i = 0; mud_dir_names[i]; i++) { + PyObject *dir = PyUnicode_FromString(mud_dir_names[i]); + if (!dir) + return NULL; + PyModule_AddObject(module, mud_dir_names[i], dir); + } + + Py_INCREF(&MudEntityType); + PyModule_AddObject(module, "Entity", (PyObject *)&MudEntityType); + PyModule_AddIntConstant(module, "ENTITY_MOB", MOB_TRIGGER); + PyModule_AddIntConstant(module, "ENTITY_OBJ", OBJ_TRIGGER); + PyModule_AddIntConstant(module, "ENTITY_ROOM", WLD_TRIGGER); + + return module; +} + +static PyObject *python_entity_from_uid(long uid, int kind) +{ + MudEntity *ent; + + if (uid <= 0) + Py_RETURN_NONE; + + ent = PyObject_New(MudEntity, &MudEntityType); + if (!ent) + return NULL; + + ent->kind = kind; + ent->uid = uid; + + return (PyObject *)ent; +} + +static int python_uid_kind(long uid) +{ + if (uid >= OBJ_ID_BASE) + return OBJ_TRIGGER; + if (uid >= ROOM_ID_BASE) + return WLD_TRIGGER; + return MOB_TRIGGER; +} + +static long python_entity_uid_from_obj(PyObject *obj, int *kind) +{ + if (!obj || obj == Py_None) + return 0; + + if (PyObject_TypeCheck(obj, &MudEntityType)) { + MudEntity *ent = (MudEntity *)obj; + if (kind) + *kind = ent->kind; + return ent->uid; + } + + if (PyLong_Check(obj)) { + long uid = PyLong_AsLong(obj); + if (kind) + *kind = python_uid_kind(uid); + return uid; + } + + if (kind) + *kind = -1; + return 0; +} + +static struct dsl_state *dsl_get_state(const char *path, int type, long uid) +{ + struct dsl_state *state; + + for (state = dsl_states; state; state = state->next) { + if (state->self_uid == uid && state->self_type == type && + !strcmp(state->path, path)) + return state; + } + + state = (struct dsl_state *)malloc(sizeof(*state)); + if (!state) + return NULL; + state->path = strdup(path); + state->self_type = type; + state->self_uid = uid; + state->mtime = 0; + state->generator = NULL; + state->running = 0; + state->next = dsl_states; + dsl_states = state; + return state; +} + +static int python_is_dsl_script(const char *path) +{ + FILE *fp; + char line[MAX_STRING_LENGTH]; + char *p; + int has_def = 0; + + if (!path || !*path) + return 0; + + fp = fopen(path, "r"); + if (!fp) + return 0; + + while (fgets(line, sizeof(line), fp)) { + p = line; + while (*p && isspace((unsigned char)*p)) + p++; + if (!*p || *p == '#') + continue; + if (!strn_cmp(p, "def on_trigger", 14)) { + has_def = 1; + break; + } + } + + fclose(fp); + if (has_def) + return 0; + return 1; +} + +static void dsl_buf_append(char **buf, size_t *len, size_t *cap, const char *text) +{ + size_t add = strlen(text); + if (*len + add + 1 >= *cap) { + size_t newcap = (*cap == 0) ? 512 : (*cap * 2); + while (*len + add + 1 >= newcap) + newcap *= 2; + *buf = (char *)realloc(*buf, newcap); + *cap = newcap; + } + memcpy(*buf + *len, text, add); + *len += add; + (*buf)[*len] = '\0'; +} + +static char *dsl_escape_string(const char *text) +{ + size_t len = 0; + size_t cap = 0; + char *out = NULL; + const char *p = text; + char buf[2] = {0, 0}; + + while (*p) { + if (*p == '\\' || *p == '"') { + buf[0] = '\\'; + dsl_buf_append(&out, &len, &cap, buf); + } else if (*p == '\n') { + dsl_buf_append(&out, &len, &cap, "\\n"); + p++; + continue; + } else if (*p == '\r') { + dsl_buf_append(&out, &len, &cap, "\\r"); + p++; + continue; + } + buf[0] = *p; + dsl_buf_append(&out, &len, &cap, buf); + p++; + } + + if (!out) { + out = strdup(""); + } + + return out; +} + +static void dsl_emit_action(char **buf, size_t *len, size_t *cap, + const char *indent, const char *verb, const char *arg) +{ + char line[MAX_STRING_LENGTH]; + char *escaped = NULL; + + if (!str_cmp(verb, "pass")) { + snprintf(line, sizeof(line), "%spass\n", indent); + dsl_buf_append(buf, len, cap, line); + return; + } + + if (!str_cmp(verb, "sleep")) { + if (!arg || !*arg) + snprintf(line, sizeof(line), "%syield 0\n", indent); + else + snprintf(line, sizeof(line), "%syield %s\n", indent, arg); + dsl_buf_append(buf, len, cap, line); + return; + } + + if (!str_cmp(verb, "move")) { + if (!arg || !*arg) + snprintf(line, sizeof(line), "%smud.move(\"\", actor=npc)\n", indent); + else + snprintf(line, sizeof(line), "%smud.move(%s, actor=npc)\n", indent, arg); + dsl_buf_append(buf, len, cap, line); + return; + } + + if (!str_cmp(verb, "emote")) { + escaped = dsl_escape_string(arg ? arg : ""); + snprintf(line, sizeof(line), "%smud.emote(\"%s\", actor=npc)\n", indent, escaped); + dsl_buf_append(buf, len, cap, line); + free(escaped); + return; + } + + if (!str_cmp(verb, "log")) { + const char *msg = arg ? arg : ""; + char trimmed[MAX_STRING_LENGTH]; + size_t msg_len; + + if (msg[0] == '"' || msg[0] == '\'') { + msg_len = strlen(msg); + if (msg_len >= 2 && msg[msg_len - 1] == msg[0]) { + size_t copy_len = msg_len - 2; + if (copy_len >= sizeof(trimmed)) + copy_len = sizeof(trimmed) - 1; + memcpy(trimmed, msg + 1, copy_len); + trimmed[copy_len] = '\0'; + msg = trimmed; + } + } + + escaped = dsl_escape_string(msg); + snprintf(line, sizeof(line), "%smud.log(f\"%s\")\n", indent, escaped); + dsl_buf_append(buf, len, cap, line); + free(escaped); + return; + } + + if (!str_cmp(verb, "say")) { + char tmp[MAX_STRING_LENGTH]; + snprintf(tmp, sizeof(tmp), "say %s", arg ? arg : ""); + escaped = dsl_escape_string(tmp); + snprintf(line, sizeof(line), "%smud.do(\"%s\", actor=npc)\n", indent, escaped); + dsl_buf_append(buf, len, cap, line); + free(escaped); + return; + } + + if (!str_cmp(verb, "get")) { + char tmp[MAX_STRING_LENGTH]; + snprintf(tmp, sizeof(tmp), "get %s", arg ? arg : ""); + escaped = dsl_escape_string(tmp); + snprintf(line, sizeof(line), "%smud.do(\"%s\", actor=npc)\n", indent, escaped); + dsl_buf_append(buf, len, cap, line); + free(escaped); + return; + } + + if (!str_cmp(verb, "junk")) { + char tmp[MAX_STRING_LENGTH]; + snprintf(tmp, sizeof(tmp), "junk %s", arg ? arg : ""); + escaped = dsl_escape_string(tmp); + snprintf(line, sizeof(line), "%smud.do(\"%s\", actor=npc)\n", indent, escaped); + dsl_buf_append(buf, len, cap, line); + free(escaped); + return; + } + + if (!str_cmp(verb, "rest") || !str_cmp(verb, "stand")) { + escaped = dsl_escape_string(verb); + snprintf(line, sizeof(line), "%smud.do(\"%s\", actor=npc)\n", indent, escaped); + dsl_buf_append(buf, len, cap, line); + free(escaped); + return; + } + + if (!str_cmp(verb, "do")) { + escaped = dsl_escape_string(arg ? arg : ""); + snprintf(line, sizeof(line), "%smud.do(\"%s\", actor=npc)\n", indent, escaped); + dsl_buf_append(buf, len, cap, line); + free(escaped); + return; + } + + snprintf(line, sizeof(line), "%spass\n", indent); + dsl_buf_append(buf, len, cap, line); + python_log_message("DSL script: unknown command"); +} + +static void dsl_emit_action_call(char **buf, size_t *len, size_t *cap, + const char *indent, const char *verb, const char *arg) +{ + const char *use_arg = arg ? arg : ""; + + if (!str_cmp(verb, "sleep")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "yield 0\n"); + } else { + dsl_buf_append(buf, len, cap, "yield "); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, "\n"); + } + return; + } + + if (!str_cmp(verb, "move")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "mud.move(\"\", actor=npc)\n"); + } else { + dsl_buf_append(buf, len, cap, "mud.move("); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, ", actor=npc)\n"); + } + return; + } + + if (!str_cmp(verb, "emote")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "mud.emote(\"\", actor=npc)\n"); + } else { + dsl_buf_append(buf, len, cap, "mud.emote("); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, ", actor=npc)\n"); + } + return; + } + + if (!str_cmp(verb, "say")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "mud.do(\"say\", actor=npc)\n"); + } else { + dsl_buf_append(buf, len, cap, "mud.do(\"say \" + "); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, ", actor=npc)\n"); + } + return; + } + + if (!str_cmp(verb, "get")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "mud.do(\"get\", actor=npc)\n"); + } else { + dsl_buf_append(buf, len, cap, "mud.do(\"get \" + "); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, ", actor=npc)\n"); + } + return; + } + + if (!str_cmp(verb, "junk")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "mud.do(\"junk\", actor=npc)\n"); + } else { + dsl_buf_append(buf, len, cap, "mud.do(\"junk \" + "); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, ", actor=npc)\n"); + } + return; + } + + if (!str_cmp(verb, "rest") || !str_cmp(verb, "stand")) { + dsl_buf_append(buf, len, cap, indent); + dsl_buf_append(buf, len, cap, "mud.do(\""); + dsl_buf_append(buf, len, cap, verb); + dsl_buf_append(buf, len, cap, "\", actor=npc)\n"); + return; + } + + if (!str_cmp(verb, "do")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "mud.do(\"\", actor=npc)\n"); + } else { + dsl_buf_append(buf, len, cap, "mud.do("); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, ", actor=npc)\n"); + } + return; + } + + if (!str_cmp(verb, "log")) { + dsl_buf_append(buf, len, cap, indent); + if (!use_arg[0]) { + dsl_buf_append(buf, len, cap, "mud.log(\"\")\n"); + } else { + dsl_buf_append(buf, len, cap, "mud.log("); + dsl_buf_append(buf, len, cap, use_arg); + dsl_buf_append(buf, len, cap, ")\n"); + } + return; + } + + dsl_buf_append(buf, len, cap, indent); + dsl_buf_append(buf, len, cap, "pass\n"); + python_log_message("DSL script: unknown command"); +} + +static char *dsl_normalize_bools(const char *text) +{ + size_t len = 0; + size_t cap = 0; + char *out = NULL; + const char *p = text; + + while (*p) { + if (isalpha((unsigned char)*p) || *p == '_') { + const char *start = p; + char word[MAX_INPUT_LENGTH]; + size_t wlen = 0; + + while (*p && (isalnum((unsigned char)*p) || *p == '_')) + p++; + wlen = (size_t)(p - start); + if (wlen >= sizeof(word)) + wlen = sizeof(word) - 1; + memcpy(word, start, wlen); + word[wlen] = '\0'; + + if (!strcmp(word, "true")) + dsl_buf_append(&out, &len, &cap, "True"); + else if (!strcmp(word, "false")) + dsl_buf_append(&out, &len, &cap, "False"); + else + dsl_buf_append(&out, &len, &cap, word); + } else { + char ch[2] = { *p, '\0' }; + dsl_buf_append(&out, &len, &cap, ch); + p++; + } + } + + if (!out) + out = strdup(""); + + return out; +} + +static char *dsl_build_source(const char *path) +{ + FILE *fp; + char line[MAX_STRING_LENGTH]; + char *buf = NULL; + size_t len = 0; + size_t cap = 0; + int saw_stmt = 0; + + fp = fopen(path, "r"); + if (!fp) + return NULL; + + dsl_buf_append(&buf, &len, &cap, + "def __dsl__(npc, room, pc=None, object=None):\n" + " if False:\n" + " yield 0\n"); + + while (fgets(line, sizeof(line), fp)) { + char *p = line; + char *start; + char *end; + char *colon; + char verb[MAX_INPUT_LENGTH]; + char indent_buf[128]; + int indent = 0; + + while (*p == ' ' || *p == '\t') { + if (*p == '\t') + indent += 4; + else + indent += 1; + p++; + } + + start = p; + while (*start && isspace((unsigned char)*start)) + start++; + if (!*start || *start == '#') + continue; + + if (!strn_cmp(start, "mudscript", 9)) + continue; + + end = start + strlen(start); + while (end > start && isspace((unsigned char)end[-1])) + end--; + *end = '\0'; + + if (!*start) + continue; + + saw_stmt = 1; + + { + char *open = strchr(start, '('); + char *close = strrchr(start, ')'); + char name[MAX_INPUT_LENGTH]; + char argbuf[MAX_STRING_LENGTH]; + size_t name_len; + size_t arg_len; + char *name_end; + int is_call = 0; + + if (open && close && close > open) { + name_end = open; + while (name_end > start && isspace((unsigned char)name_end[-1])) + name_end--; + name_len = (size_t)(name_end - start); + if (name_len > 0 && name_len < sizeof(name)) { + memcpy(name, start, name_len); + name[name_len] = '\0'; + if (!str_cmp(name, "move") || !str_cmp(name, "emote") || + !str_cmp(name, "say") || !str_cmp(name, "get") || + !str_cmp(name, "junk") || !str_cmp(name, "rest") || + !str_cmp(name, "stand") || !str_cmp(name, "do") || + !str_cmp(name, "sleep") || !str_cmp(name, "log")) { + is_call = 1; + } + } + } + + if (is_call) { + arg_len = (size_t)(close - open - 1); + if (arg_len >= sizeof(argbuf)) + arg_len = sizeof(argbuf) - 1; + memcpy(argbuf, open + 1, arg_len); + argbuf[arg_len] = '\0'; + { + char *arg = argbuf; + while (*arg && isspace((unsigned char)*arg)) + arg++; + while (arg_len > 0 && isspace((unsigned char)argbuf[arg_len - 1])) { + argbuf[arg_len - 1] = '\0'; + arg_len--; + } + snprintf(indent_buf, sizeof(indent_buf), "%*s", 4 + indent, ""); + dsl_emit_action_call(&buf, &len, &cap, indent_buf, name, arg); + } + continue; + } + } + + if (!strn_cmp(start, "if ", 3) || !strn_cmp(start, "elif ", 5) || + !strn_cmp(start, "for ", 4) || !strn_cmp(start, "while ", 6) || + !strn_cmp(start, "else", 4)) { + char *body = NULL; + char body_stmt[MAX_STRING_LENGTH]; + int has_body = 0; + char *normalized = NULL; + + colon = strchr(start, ':'); + if (colon) { + *colon = '\0'; + body = colon + 1; + while (body && isspace((unsigned char)*body)) + body++; + if (body && *body) + has_body = 1; + } + + snprintf(indent_buf, sizeof(indent_buf), "%*s", 4 + indent, ""); + if (!colon) + colon = start + strlen(start); + + normalized = dsl_normalize_bools(start); + if (!normalized) + normalized = strdup(start); + + if (!has_body) { + char control_line[MAX_STRING_LENGTH]; + snprintf(control_line, sizeof(control_line), "%s%s:\n", indent_buf, normalized); + dsl_buf_append(&buf, &len, &cap, control_line); + } else { + char *arg = body; + char *q = arg; + while (*q && !isspace((unsigned char)*q)) + q++; + snprintf(verb, sizeof(verb), "%.*s", (int)(q - arg), arg); + while (*q && isspace((unsigned char)*q)) + q++; + snprintf(body_stmt, sizeof(body_stmt), "%s", q); + snprintf(indent_buf, sizeof(indent_buf), "%*s", 4 + indent, ""); + { + char control_line[MAX_STRING_LENGTH]; + snprintf(control_line, sizeof(control_line), "%s%s: ", indent_buf, normalized); + dsl_buf_append(&buf, &len, &cap, control_line); + } + dsl_emit_action(&buf, &len, &cap, "", verb, body_stmt); + } + free(normalized); + continue; + } + + { + char *arg; + char *q = start; + + while (*q && !isspace((unsigned char)*q)) + q++; + snprintf(verb, sizeof(verb), "%.*s", (int)(q - start), start); + while (*q && isspace((unsigned char)*q)) + q++; + arg = q; + + snprintf(indent_buf, sizeof(indent_buf), "%*s", 4 + indent, ""); + dsl_emit_action(&buf, &len, &cap, indent_buf, verb, arg); + } + } + + fclose(fp); + + if (!saw_stmt) + dsl_buf_append(&buf, &len, &cap, " pass\n"); + + return buf; +} + +static struct dsl_script_cache *dsl_load_script(const char *path) +{ + struct dsl_script_cache *entry; + struct stat st; + char *source = NULL; + PyObject *code = NULL; + PyObject *globals = NULL; + PyObject *builtins = NULL; + PyObject *result = NULL; + PyObject *func = NULL; + + if (!path || !*path) + return NULL; + + for (entry = dsl_cache; entry; entry = entry->next) { + if (!strcmp(entry->path, path)) + break; + } + + if (stat(path, &st) < 0) { + script_log("DSL script not found: %s", path); + return NULL; + } + + if (entry && entry->mtime == st.st_mtime && entry->func) + return entry; + + source = dsl_build_source(path); + if (!source) { + script_log("Unable to read DSL script: %s", path); + return NULL; + } + + code = Py_CompileString(source, path, Py_file_input); + free(source); + if (!code) { + python_log_exception("dsl compile"); + return NULL; + } + + globals = PyDict_New(); + if (!globals) { + Py_DECREF(code); + return NULL; + } + + builtins = PyEval_GetBuiltins(); + PyDict_SetItemString(globals, "__builtins__", builtins); + + if (mud_module) + PyDict_SetItemString(globals, "mud", mud_module); + if (mud_module) { + for (int i = 0; mud_dir_names[i]; i++) { + PyObject *dir = PyObject_GetAttrString(mud_module, mud_dir_names[i]); + if (dir) { + PyDict_SetItemString(globals, mud_dir_names[i], dir); + Py_DECREF(dir); + } else { + PyErr_Clear(); + } + } + } + + result = PyEval_EvalCode(code, globals, globals); + Py_DECREF(code); + + if (!result) { + python_log_exception("dsl exec"); + Py_DECREF(globals); + return NULL; + } + + Py_DECREF(result); + + func = PyDict_GetItemString(globals, "__dsl__"); + if (!func || !PyCallable_Check(func)) { + python_log_message("DSL script missing __dsl__"); + Py_DECREF(globals); + return NULL; + } + + if (!entry) { + entry = (struct dsl_script_cache *)malloc(sizeof(*entry)); + if (!entry) { + Py_DECREF(globals); + return NULL; + } + entry->path = strdup(path); + entry->next = dsl_cache; + dsl_cache = entry; + } else { + if (entry->globals) + Py_DECREF(entry->globals); + if (entry->func) + Py_DECREF(entry->func); + } + + entry->globals = globals; + entry->func = func; + Py_INCREF(func); + entry->mtime = st.st_mtime; + + return entry; +} + +static obj_data *python_find_obj(long uid) +{ + char name[32]; + + if (uid <= 0) + return NULL; + + snprintf(name, sizeof(name), "%c%ld", UID_CHAR, uid); + return get_obj(name); +} + +static room_data *python_find_room(long uid) +{ + char name[32]; + + if (uid <= 0) + return NULL; + + snprintf(name, sizeof(name), "%c%ld", UID_CHAR, uid); + return get_room(name); +} + +static int python_path_is_script_log(const char *path) +{ + char resolved[PATH_MAX]; + + if (!path || !*path) + return 0; + + if (realpath(path, resolved)) { + if (!strcmp(resolved, script_log_path)) + return 1; + } + + return 0; +} + +static int python_audit_hook(const char *event, PyObject *args, void *userData) +{ + (void)userData; + + if (!event) + return 0; + + if (!strcmp(event, "open")) { + PyObject *path_obj = NULL; + PyObject *mode_obj = NULL; + const char *path = NULL; + const char *mode = NULL; + + if (PyTuple_Size(args) >= 2) { + path_obj = PyTuple_GetItem(args, 0); + mode_obj = PyTuple_GetItem(args, 1); + } + + if (path_obj && PyUnicode_Check(path_obj)) + path = PyUnicode_AsUTF8(path_obj); + else if (path_obj && PyBytes_Check(path_obj)) + path = PyBytes_AsString(path_obj); + if (mode_obj && PyUnicode_Check(mode_obj)) + mode = PyUnicode_AsUTF8(mode_obj); + + if (mode && (strchr(mode, 'w') || strchr(mode, 'a') || strchr(mode, '+') || strchr(mode, 'x'))) { + if (!path || !python_path_is_script_log(path)) { + PyErr_SetString(PyExc_PermissionError, "Python scripts may only write to script.log"); + return -1; + } + } + } + + if (!strcmp(event, "os.open") || !strcmp(event, "os.openat")) { + PyObject *path_obj = NULL; + PyObject *flags_obj = NULL; + const char *path = NULL; + long flags = 0; + + if (PyTuple_Size(args) >= 2) { + path_obj = PyTuple_GetItem(args, 0); + flags_obj = PyTuple_GetItem(args, 1); + } + + if (path_obj && PyUnicode_Check(path_obj)) + path = PyUnicode_AsUTF8(path_obj); + else if (path_obj && PyBytes_Check(path_obj)) + path = PyBytes_AsString(path_obj); + if (flags_obj && PyLong_Check(flags_obj)) + flags = PyLong_AsLong(flags_obj); + + if (flags & (O_WRONLY | O_RDWR | O_CREAT | O_TRUNC | O_APPEND)) { + if (!path || !python_path_is_script_log(path)) { + PyErr_SetString(PyExc_PermissionError, "Python scripts may only write to script.log"); + return -1; + } + } + } + + if (!strcmp(event, "socket.connect") || !strcmp(event, "socket.bind")) { + PyObject *addr_obj = NULL; + + if (PyTuple_Size(args) >= 1) + addr_obj = PyTuple_GetItem(args, 0); + + if (addr_obj) { + if (PyTuple_Check(addr_obj) && PyTuple_Size(addr_obj) >= 1) { + PyObject *host_obj = PyTuple_GetItem(addr_obj, 0); + const char *host = NULL; + + if (host_obj && PyUnicode_Check(host_obj)) + host = PyUnicode_AsUTF8(host_obj); + + if (!host || !*host || !strcmp(host, "0.0.0.0") || !strcmp(host, "::") || + (strcmp(host, "127.0.0.1") && strcmp(host, "localhost") && strcmp(host, "::1"))) { + PyErr_SetString(PyExc_PermissionError, "Outbound network connections are blocked"); + return -1; + } + } + } + } + + return 0; +} + +static void python_log_exception(const char *context) +{ + PyObject *ptype = NULL; + PyObject *pvalue = NULL; + PyObject *ptrace = NULL; + PyObject *pstr = NULL; + const char *msg = NULL; + char buf[MAX_STRING_LENGTH]; + + PyErr_Fetch(&ptype, &pvalue, &ptrace); + PyErr_NormalizeException(&ptype, &pvalue, &ptrace); + + if (pvalue) + pstr = PyObject_Str(pvalue); + + if (pstr) + msg = PyUnicode_AsUTF8(pstr); + + if (!msg) + msg = "(unknown python error)"; + + snprintf(buf, sizeof(buf), "Python error in %s: %s", context ? context : "script", msg); + python_log_message(buf); + script_log("%s", buf); + + Py_XDECREF(pstr); + Py_XDECREF(ptype); + Py_XDECREF(pvalue); + Py_XDECREF(ptrace); +} + +static void python_log_message(const char *message) +{ + time_t ct = time(0); + char timestr[21]; + int i; + + if (!message || !*message) + return; + + if (!script_log_fp) { + script_log_fp = fopen(SCRIPT_LOGFILE, "a"); + if (!script_log_fp) + return; + } + + for (i = 0; i < 21; i++) + timestr[i] = 0; + strftime(timestr, sizeof(timestr), "%b %d %H:%M:%S %Y", localtime(&ct)); + + fprintf(script_log_fp, "%-20.20s :: %s\n", timestr, message); + fflush(script_log_fp); +} + +static void python_rotate_script_log(void) +{ + struct stat st; + char path[PATH_MAX]; + char next[PATH_MAX]; + int i; + + if (stat(SCRIPT_LOGFILE, &st) != 0) + return; + + for (i = 1; ; i++) { + if (snprintf(path, sizeof(path), "%s.%d", SCRIPT_LOGFILE, i) >= (int)sizeof(path)) + return; + if (stat(path, &st) != 0) + break; + } + + for (i = i - 1; i >= 1; i--) { + if (snprintf(path, sizeof(path), "%s.%d", SCRIPT_LOGFILE, i) >= (int)sizeof(path)) + return; + if (snprintf(next, sizeof(next), "%s.%d", SCRIPT_LOGFILE, i + 1) >= (int)sizeof(next)) + return; + rename(path, next); + } + + if (snprintf(next, sizeof(next), "%s.1", SCRIPT_LOGFILE) >= (int)sizeof(next)) + return; + rename(SCRIPT_LOGFILE, next); +} + +void python_debug_log(const char *message) +{ + python_log_message(message); +} + +static struct py_script_cache *python_load_script(const char *path) +{ + struct py_script_cache *entry; + struct stat st; + char *source = NULL; + FILE *fp = NULL; + long size = 0; + PyObject *code = NULL; + PyObject *globals = NULL; + PyObject *builtins = NULL; + PyObject *result = NULL; + PyObject *file_str = NULL; + + if (!path || !*path) + return NULL; + + for (entry = script_cache; entry; entry = entry->next) { + if (!strcmp(entry->path, path)) + break; + } + + if (stat(path, &st) < 0) { + script_log("Python script not found: %s", path); + return NULL; + } + + if (entry && entry->mtime == st.st_mtime && entry->globals) + return entry; + + fp = fopen(path, "rb"); + if (!fp) { + script_log("Unable to open python script: %s", path); + return NULL; + } + + if (fseek(fp, 0, SEEK_END) == 0) + size = ftell(fp); + if (size < 0) + size = 0; + rewind(fp); + + source = (char *)malloc((size_t)size + 1); + if (!source) { + fclose(fp); + script_log("Out of memory reading python script: %s", path); + return NULL; + } + + if (size > 0 && fread(source, 1, (size_t)size, fp) != (size_t)size) { + free(source); + fclose(fp); + script_log("Failed to read python script: %s", path); + return NULL; + } + source[size] = '\0'; + fclose(fp); + + code = Py_CompileString(source, path, Py_file_input); + free(source); + if (!code) { + python_log_exception("compile"); + return NULL; + } + + globals = PyDict_New(); + if (!globals) { + Py_DECREF(code); + return NULL; + } + + builtins = PyEval_GetBuiltins(); + PyDict_SetItemString(globals, "__builtins__", builtins); + + file_str = PyUnicode_FromString(path); + if (file_str) { + PyDict_SetItemString(globals, "__file__", file_str); + Py_DECREF(file_str); + } + + if (mud_module) + PyDict_SetItemString(globals, "mud", mud_module); + if (mud_sleep_exc) + PyDict_SetItemString(globals, "MudSleep", mud_sleep_exc); + if (mud_module) { + PyObject *log_func = PyObject_GetAttrString(mud_module, "log"); + if (log_func) { + PyDict_SetItemString(globals, "log", log_func); + Py_DECREF(log_func); + } else { + PyErr_Clear(); + } + } + if (mud_module) { + for (int i = 0; mud_dir_names[i]; i++) { + PyObject *dir = PyObject_GetAttrString(mud_module, mud_dir_names[i]); + if (dir) { + PyDict_SetItemString(globals, mud_dir_names[i], dir); + Py_DECREF(dir); + } else { + PyErr_Clear(); + } + } + } + + result = PyEval_EvalCode(code, globals, globals); + Py_DECREF(code); + + if (!result) { + python_log_exception("exec"); + Py_DECREF(globals); + return NULL; + } + + Py_DECREF(result); + + if (!entry) { + entry = (struct py_script_cache *)malloc(sizeof(*entry)); + if (!entry) { + Py_DECREF(globals); + return NULL; + } + entry->path = strdup(path); + entry->next = script_cache; + script_cache = entry; + } else if (entry->globals) { + Py_DECREF(entry->globals); + } + + entry->globals = globals; + entry->mtime = st.st_mtime; + + return entry; +} + +static int dsl_run_state(struct dsl_state *state, PyObject *func, PyObject *npc, + PyObject *room, PyObject *pc, PyObject *object) +{ + PyObject *value = NULL; + double seconds = 0.0; + long passes = 0; + + if (!state || !func) + return 1; + + if (!state->generator) { + state->generator = PyObject_CallFunctionObjArgs(func, npc, room, pc, object, NULL); + if (!state->generator) { + python_log_exception("dsl start"); + return 1; + } + } + + state->running = 1; + value = PyIter_Next(state->generator); + state->running = 0; + + if (!value) { + if (PyErr_Occurred()) + python_log_exception("dsl run"); + PyErr_Clear(); + Py_DECREF(state->generator); + state->generator = NULL; + return 1; + } + + if (PyLong_Check(value)) + seconds = (double)PyLong_AsLong(value); + else if (PyFloat_Check(value)) + seconds = PyFloat_AsDouble(value); + else { + python_log_message("DSL script yield must be a number"); + seconds = 0.0; + } + + Py_DECREF(value); + + if (seconds < 0.0) + seconds = 0.0; + + passes = (long)(seconds * PASSES_PER_SEC); + if (passes < 0) + passes = 0; + + { + struct dsl_call_data *data = (struct dsl_call_data *)malloc(sizeof(*data)); + if (!data) { + script_log("DSL script out of memory scheduling sleep"); + return 1; + } + data->state = state; + event_create(dsl_call_event, data, passes); + } + + return 1; +} + +static int dsl_trigger_run(void *go, struct trig_data *trig, int type, const char *path) +{ + PyGILState_STATE gstate; + struct dsl_script_cache *entry; + struct dsl_state *state; + char_data *ch = NULL; + obj_data *obj = NULL; + room_data *room = NULL; + long uid = 0; + PyObject *npc = NULL; + PyObject *room_obj = NULL; + PyObject *pc = NULL; + PyObject *object = NULL; + + if (!path || !*path) + return 1; + + gstate = PyGILState_Ensure(); + + entry = dsl_load_script(path); + if (!entry || !entry->func) { + PyGILState_Release(gstate); + return 1; + } + + if (type == MOB_TRIGGER) { + ch = (char_data *)go; + if (ch) + uid = char_script_id(ch); + if (ch && IN_ROOM(ch) != NOWHERE) + room = &world[IN_ROOM(ch)]; + } else if (type == OBJ_TRIGGER) { + obj = (obj_data *)go; + if (obj) + uid = obj_script_id(obj); + if (obj) { + if (obj->in_room != NOWHERE) + room = &world[obj->in_room]; + else if (obj->carried_by && IN_ROOM(obj->carried_by) != NOWHERE) + room = &world[IN_ROOM(obj->carried_by)]; + else if (obj->worn_by && IN_ROOM(obj->worn_by) != NOWHERE) + room = &world[IN_ROOM(obj->worn_by)]; + } + } else if (type == WLD_TRIGGER) { + room = (room_data *)go; + if (room) + uid = room_script_id(room); + } + + if (!uid) { + PyGILState_Release(gstate); + return 1; + } + + state = dsl_get_state(path, type, uid); + if (!state) { + PyGILState_Release(gstate); + return 1; + } + + if (state->running) { + PyGILState_Release(gstate); + return 1; + } + + if (state->mtime != entry->mtime) { + if (state->generator) { + Py_DECREF(state->generator); + state->generator = NULL; + } + state->mtime = entry->mtime; + } + + if (type == MOB_TRIGGER && ch) + npc = python_entity_from_uid(uid, MOB_TRIGGER); + if (room) + room_obj = python_entity_from_uid(room_script_id(room), WLD_TRIGGER); + if (type == OBJ_TRIGGER && obj) + object = python_entity_from_uid(uid, OBJ_TRIGGER); + + if (room) { + char_data *tch; + for (tch = room->people; tch; tch = tch->next_in_room) { + if (!IS_NPC(tch)) { + pc = python_entity_from_uid(char_script_id(tch), MOB_TRIGGER); + break; + } + } + } + + if (!npc && PyErr_Occurred()) + PyErr_Clear(); + if (!room_obj && PyErr_Occurred()) + PyErr_Clear(); + if (!pc && PyErr_Occurred()) + PyErr_Clear(); + if (!object && PyErr_Occurred()) + PyErr_Clear(); + + if (!npc) { + npc = Py_None; + Py_INCREF(npc); + } + if (!room_obj) { + room_obj = Py_None; + Py_INCREF(room_obj); + } + if (!pc) { + pc = Py_None; + Py_INCREF(pc); + } + if (!object) { + object = Py_None; + Py_INCREF(object); + } + + { + struct py_trigger_context ctx; + ctx.go = go; + ctx.type = type; + ctx.trig = trig; + current_context = &ctx; + dsl_run_state(state, entry->func, npc, room_obj, pc, object); + current_context = NULL; + } + + Py_DECREF(npc); + Py_DECREF(room_obj); + Py_DECREF(pc); + Py_DECREF(object); + + PyGILState_Release(gstate); + return 1; +} + +static EVENTFUNC(dsl_call_event) +{ + struct dsl_call_data *data = (struct dsl_call_data *)event_obj; + struct dsl_state *state; + void *go = NULL; + + if (!data) + return 0; + + state = data->state; + free(data); + + if (!state) + return 0; + + if (state->self_type == MOB_TRIGGER) + go = find_char(state->self_uid); + else if (state->self_type == OBJ_TRIGGER) + go = python_find_obj(state->self_uid); + else if (state->self_type == WLD_TRIGGER) + go = python_find_room(state->self_uid); + + if (!go) { + PyGILState_STATE gstate = PyGILState_Ensure(); + if (state->generator) { + Py_DECREF(state->generator); + state->generator = NULL; + } + PyGILState_Release(gstate); + return 0; + } + + dsl_trigger_run(go, NULL, state->self_type, state->path); + return 0; +} + +static PyObject *python_build_event(struct trig_data *trig, void *go, int type) +{ + PyObject *event; + PyObject *vars; + PyObject *trigger; + PyObject *temp; + + event = PyDict_New(); + if (!event) + return NULL; + + vars = PyDict_New(); + if (!vars) { + Py_DECREF(event); + return NULL; + } + + trigger = PyDict_New(); + if (!trigger) { + Py_DECREF(vars); + Py_DECREF(event); + return NULL; + } + + temp = PyLong_FromLong(GET_TRIG_VNUM(trig)); + if (temp) { + PyDict_SetItemString(trigger, "vnum", temp); + Py_DECREF(temp); + } + temp = PyUnicode_FromString(GET_TRIG_NAME(trig) ? GET_TRIG_NAME(trig) : ""); + if (temp) { + PyDict_SetItemString(trigger, "name", temp); + Py_DECREF(temp); + } + temp = PyLong_FromLong(GET_TRIG_TYPE(trig)); + if (temp) { + PyDict_SetItemString(trigger, "type", temp); + Py_DECREF(temp); + } + temp = PyLong_FromLong(trig->attach_type); + if (temp) { + PyDict_SetItemString(trigger, "attach_type", temp); + Py_DECREF(temp); + } + temp = PyLong_FromLong(GET_TRIG_NARG(trig)); + if (temp) { + PyDict_SetItemString(trigger, "narg", temp); + Py_DECREF(temp); + } + temp = PyUnicode_FromString(GET_TRIG_ARG(trig) ? GET_TRIG_ARG(trig) : ""); + if (temp) { + PyDict_SetItemString(trigger, "arglist", temp); + Py_DECREF(temp); + } + temp = PyUnicode_FromString(trig->script ? trig->script : ""); + if (temp) { + PyDict_SetItemString(trigger, "script", temp); + Py_DECREF(temp); + } + + if (type == MOB_TRIGGER && go) { + char_data *ch = (char_data *)go; + temp = python_entity_from_uid(char_script_id(ch), MOB_TRIGGER); + if (temp) { + PyDict_SetItemString(event, "self", temp); + Py_DECREF(temp); + } + } else if (type == OBJ_TRIGGER && go) { + obj_data *obj = (obj_data *)go; + temp = python_entity_from_uid(obj_script_id(obj), OBJ_TRIGGER); + if (temp) { + PyDict_SetItemString(event, "self", temp); + Py_DECREF(temp); + } + } else if (type == WLD_TRIGGER && go) { + room_data *room = (room_data *)go; + temp = python_entity_from_uid(room_script_id(room), WLD_TRIGGER); + if (temp) { + PyDict_SetItemString(event, "self", temp); + Py_DECREF(temp); + } + } + + for (struct trig_var_data *vd = GET_TRIG_VARS(trig); vd; vd = vd->next) { + PyObject *value_obj = NULL; + + if (!vd->name || !vd->value) + continue; + + if (*(vd->value) == UID_CHAR) { + long uid = atol(vd->value + 1); + int kind = python_uid_kind(uid); + value_obj = python_entity_from_uid(uid, kind); + } else { + value_obj = PyUnicode_FromString(vd->value); + } + + if (!value_obj) + continue; + + PyDict_SetItemString(vars, vd->name, value_obj); + + if (!PyDict_GetItemString(event, vd->name)) + PyDict_SetItemString(event, vd->name, value_obj); + + Py_DECREF(value_obj); + } + + PyDict_SetItemString(event, "trigger", trigger); + PyDict_SetItemString(event, "vars", vars); + + Py_DECREF(vars); + Py_DECREF(trigger); + + return event; +} + +static EVENTFUNC(python_call_event) +{ + struct py_call_data *data = (struct py_call_data *)event_obj; + PyGILState_STATE gstate; + struct py_script_cache *entry; + PyObject *func = NULL; + PyObject *event = NULL; + PyObject *result = NULL; + struct py_trigger_context ctx; + struct trig_data temp_trig; + const char *script_name = NULL; + + if (!data) + return 0; + + gstate = PyGILState_Ensure(); + + entry = python_load_script(data->script); + if (entry && entry->globals) { + func = PyDict_GetItemString(entry->globals, data->func); + if (func && PyCallable_Check(func)) { + event = PyDict_New(); + if (event) { + PyObject *self_obj = python_entity_from_uid(data->self_uid, data->self_type); + + if (self_obj) { + PyDict_SetItemString(event, "self", self_obj); + Py_DECREF(self_obj); + } else { + PyErr_Clear(); + } + PyDict_SetItemString(event, "scheduled", Py_True); + } + if (event) { + script_name = data->script; + if (script_name && !strncmp(script_name, SCRIPTS_PREFIX, strlen(SCRIPTS_PREFIX))) + script_name += strlen(SCRIPTS_PREFIX); + temp_trig.script = (char *)script_name; + ctx.type = data->self_type; + ctx.trig = &temp_trig; + ctx.go = NULL; + if (data->self_type == MOB_TRIGGER) + ctx.go = find_char(data->self_uid); + else if (data->self_type == OBJ_TRIGGER) + ctx.go = python_find_obj(data->self_uid); + else if (data->self_type == WLD_TRIGGER) + ctx.go = python_find_room(data->self_uid); + current_context = &ctx; + PyObject *call_args = PyTuple_Pack(1, event); + PyObject *full_args = PySequence_Concat(call_args, data->args); + Py_DECREF(call_args); + Py_DECREF(event); + if (full_args) { + result = PyObject_Call(func, full_args, data->kwargs); + Py_DECREF(full_args); + } + current_context = NULL; + } + if (!result) { + if (mud_sleep_exc && PyErr_ExceptionMatches(mud_sleep_exc)) { + PyErr_Clear(); + } else { + python_log_exception("call_later"); + } + } + Py_XDECREF(result); + } else { + python_log_message("call_later: function not found"); + } + } + + PyGILState_Release(gstate); + + if (data->args) + Py_DECREF(data->args); + if (data->kwargs) + Py_DECREF(data->kwargs); + free(data->script); + free(data->func); + free(data); + + return 0; +} + +void python_scripts_init(void) +{ + PyObject *sys_path = NULL; + PyObject *scripts_path = NULL; + + if (Py_IsInitialized()) + return; + + if (PyImport_AppendInittab("mud", PyInit_mud) == -1) + return; + + Py_Initialize(); + + if (PySys_AddAuditHook(python_audit_hook, NULL) < 0) + PyErr_Clear(); + + sys_path = PySys_GetObject("path"); + if (sys_path && PyList_Check(sys_path)) { + scripts_path = PyUnicode_FromString("scripts"); + if (scripts_path) { + PyList_Insert(sys_path, 0, scripts_path); + Py_DECREF(scripts_path); + } + } + + mud_module = PyImport_ImportModule("mud"); + + python_rotate_script_log(); + + if (script_log_fp) + fclose(script_log_fp); + script_log_fp = fopen(SCRIPT_LOGFILE, "a"); + if (script_log_fp) + fclose(script_log_fp); + script_log_fp = NULL; + + if (!realpath(SCRIPT_LOGFILE, script_log_path)) + strlcpy(script_log_path, SCRIPT_LOGFILE, sizeof(script_log_path)); +} + +void python_scripts_shutdown(void) +{ + struct py_script_cache *entry = script_cache; + struct dsl_script_cache *dentry = dsl_cache; + struct dsl_state *state = dsl_states; + + if (!Py_IsInitialized()) + return; + + while (entry) { + struct py_script_cache *next = entry->next; + if (entry->globals) + Py_DECREF(entry->globals); + free(entry->path); + free(entry); + entry = next; + } + script_cache = NULL; + + while (dentry) { + struct dsl_script_cache *next = dentry->next; + if (dentry->globals) + Py_DECREF(dentry->globals); + if (dentry->func) + Py_DECREF(dentry->func); + free(dentry->path); + free(dentry); + dentry = next; + } + dsl_cache = NULL; + + while (state) { + struct dsl_state *next = state->next; + if (state->generator) + Py_DECREF(state->generator); + free(state->path); + free(state); + state = next; + } + dsl_states = NULL; + + if (mud_module) { + Py_DECREF(mud_module); + mud_module = NULL; + } + mud_sleep_exc = NULL; + + Py_Finalize(); + + if (script_log_fp) { + fclose(script_log_fp); + script_log_fp = NULL; + } +} + +int python_trigger_run(void *go, struct trig_data *trig, int type, int mode) +{ + PyGILState_STATE gstate; + struct py_script_cache *entry; + PyObject *event = NULL; + PyObject *func = NULL; + PyObject *result = NULL; + int ret = 1; + char path[PATH_MAX]; + + if (!trig || !trig->script || !*trig->script) + return 1; + + snprintf(path, sizeof(path), "%s%s", SCRIPTS_PREFIX, trig->script); + { + char logbuf[MAX_STRING_LENGTH]; + snprintf(logbuf, sizeof(logbuf), "python_trigger_run: vnum=%d script=%s", GET_TRIG_VNUM(trig), path); + python_log_message(logbuf); + } + + if (python_is_dsl_script(path)) { + (void)mode; + return dsl_trigger_run(go, trig, type, path); + } + + gstate = PyGILState_Ensure(); + + entry = python_load_script(path); + if (!entry || !entry->globals) { + PyGILState_Release(gstate); + return 1; + } + + func = PyDict_GetItemString(entry->globals, "on_trigger"); + if (!func || !PyCallable_Check(func)) { + python_log_message("Python script missing on_trigger(event)"); + PyGILState_Release(gstate); + return 1; + } + + event = python_build_event(trig, go, type); + if (!event) { + PyGILState_Release(gstate); + return 1; + } + + { + struct py_trigger_context ctx; + ctx.go = go; + ctx.type = type; + ctx.trig = trig; + current_context = &ctx; + result = PyObject_CallFunctionObjArgs(func, event, NULL); + current_context = NULL; + } + + Py_DECREF(event); + + if (!result) { + if (mud_sleep_exc && PyErr_ExceptionMatches(mud_sleep_exc)) { + PyErr_Clear(); + PyGILState_Release(gstate); + return 1; + } + python_log_exception("on_trigger"); + PyGILState_Release(gstate); + return 1; + } + + if (result == Py_False) + ret = 0; + + Py_DECREF(result); + PyGILState_Release(gstate); + + (void)mode; + + return ret; +} diff --git a/src/py_scripts.h b/src/py_scripts.h new file mode 100644 index 0000000..71c0be5 --- /dev/null +++ b/src/py_scripts.h @@ -0,0 +1,17 @@ +/** +* @file py_scripts.h +* +* This set of code was not originally part of the circlemud distribution. +*/ + +#ifndef _PY_SCRIPTS_H_ +#define _PY_SCRIPTS_H_ + +struct trig_data; + +void python_scripts_init(void); +void python_scripts_shutdown(void); +int python_trigger_run(void *go, struct trig_data *trig, int type, int mode); +void python_debug_log(const char *message); + +#endif /* _PY_SCRIPTS_H_ */ diff --git a/src/dg_triggers.c b/src/py_triggers.c similarity index 97% rename from src/dg_triggers.c rename to src/py_triggers.c index 7cea1ad..315e3ad 100644 --- a/src/dg_triggers.c +++ b/src/py_triggers.c @@ -1,21 +1,13 @@ -/************************************************************************** -* File: dg_triggers.c part of tbaMUD * -* Usage: Contains all the trigger functions for scripts. * -* * -* All rights reserved. See license for complete information. * -* * -* Death's Gate MUD is based on CircleMUD, Copyright (C) 1993, 94. * -* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * -* * -* $Author: galion/Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_triggers.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "utils.h" #include "comm.h" #include "interpreter.h" @@ -23,6 +15,7 @@ #include "db.h" #include "oasis.h" #include "constants.h" +#include "py_scripts.h" #include "spells.h" /* for skill_name() */ #include "act.h" /* for cmd_door[] */ @@ -510,12 +503,23 @@ void load_mtrigger(char_data *ch) trig_data *t; int result = 0; - if (!SCRIPT_CHECK(ch, MTRIG_LOAD)) + if (!SCRIPT_CHECK(ch, MTRIG_LOAD)) { + if (ch && IS_NPC(ch)) { + char buf[MAX_STRING_LENGTH]; + snprintf(buf, sizeof(buf), "load_mtrigger: no MTRIG_LOAD for %s [%d]", GET_SHORT(ch), GET_MOB_VNUM(ch)); + python_debug_log(buf); + } return; + } for (t = TRIGGERS(SCRIPT(ch)); t; t = t->next) { if (TRIGGER_CHECK(t, MTRIG_LOAD) && (rand_number(1, 100) <= GET_TRIG_NARG(t))) { + if (ch && IS_NPC(ch)) { + char buf[MAX_STRING_LENGTH]; + snprintf(buf, sizeof(buf), "load_mtrigger: firing trigger %d for %s [%d]", GET_TRIG_VNUM(t), GET_SHORT(ch), GET_MOB_VNUM(ch)); + python_debug_log(buf); + } result = script_driver(&ch, t, MOB_TRIGGER, TRIG_NEW); break; } diff --git a/src/dg_scripts.h b/src/py_triggers.h similarity index 93% rename from src/dg_scripts.h rename to src/py_triggers.h index 7021957..702521a 100644 --- a/src/dg_scripts.h +++ b/src/py_triggers.h @@ -1,19 +1,10 @@ /** -* @file dg_scripts.h -* Header file for script structures, constants, and function prototypes for -* dg_scripts.c -* -* Part of the core tbaMUD source code distribution, which is a derivative -* of, and continuation of, CircleMUD. -* -* This source code, which was not part of the CircleMUD legacy code, -* was created by the following people: -* $Author: Mark A. Heilpern/egreen/Welcor $ -* $Date: 2004/10/11 12:07:00$ -* $Revision: 1.0.14 $ +* @file py_triggers.h +* +* This set of code was not originally part of the circlemud distribution. */ -#ifndef _DG_SCRIPTS_H_ -#define _DG_SCRIPTS_H_ +#ifndef _PY_TRIGGERS_H_ +#define _PY_TRIGGERS_H_ #include "utils.h" /* To make sure ACMD is defined */ @@ -154,6 +145,7 @@ struct trig_data { byte data_type; /**< type of game_data for trig */ char *name; /**< name of trigger */ long trigger_type; /**< type of trigger (for bitvector) */ + char *script; /**< python script path */ struct cmdlist_element *cmdlist; /**< top of command list */ struct cmdlist_element *curr_state; /**< ptr to current line of trigger */ int narg; /**< numerical argument */ @@ -310,8 +302,6 @@ int trig_is_attached(struct script_data *sc, int trig_num); /* Thanks to Chris Gilbert for reminding me that there are other options. */ int script_driver(void *go_adress, trig_data *trig, int type, int mode); trig_rnum real_trigger(trig_vnum vnum); -void process_eval(void *go, struct script_data *sc, trig_data *trig, - int type, char *cmd); void read_saved_vars(struct char_data *ch); void save_char_vars(struct char_data *ch); void init_lookup_table(void); @@ -328,15 +318,6 @@ void assign_triggers(void *i, int type); /* From dg_variables.c */ void add_var(struct trig_var_data **var_list, const char *name, const char *value, long id); -int item_in_list(char *item, obj_data *list); -char *skill_percent(struct char_data *ch, char *skill); -int char_has_item(char *item, struct char_data *ch); -void var_subst(void *go, struct script_data *sc, trig_data *trig, - int type, char *line, char *buf); -int text_processed(char *field, char *subfield, struct trig_var_data *vd, - char *str, size_t slen); -void find_replacement(void *go, struct script_data *sc, trig_data *trig, - int type, char *var, char *field, char *subfield, char *str, size_t slen); /* From dg_handler.c */ void free_var_el(struct trig_var_data *var); @@ -456,4 +437,4 @@ extern int has_obj_by_uid_in_lookup_table(long uid); #define room_script_id(room) ((long)(room)->number + ROOM_ID_BASE) -#endif /* _DG_SCRIPTS_H_ */ +#endif /* _PY_TRIGGERS_H_ */ diff --git a/src/py_variables.c b/src/py_variables.c new file mode 100644 index 0000000..c5b0f2e --- /dev/null +++ b/src/py_variables.c @@ -0,0 +1,37 @@ +/** +* @file py_variables.c +* +* This set of code was not originally part of the circlemud distribution. +*/ + +#include "conf.h" +#include "sysdep.h" +#include "structs.h" +#include "utils.h" +#include "py_triggers.h" +#include "db.h" + +void add_var(struct trig_var_data **var_list, const char *name, const char *value, long id) +{ + struct trig_var_data *vd; + + if (!name || !*name) + return; + + for (vd = *var_list; vd; vd = vd->next) { + if (!str_cmp(name, vd->name)) { + if (vd->value) + free(vd->value); + vd->value = strdup(value ? value : ""); + vd->context = id; + return; + } + } + + CREATE(vd, struct trig_var_data, 1); + vd->name = strdup(name); + vd->value = strdup(value ? value : ""); + vd->context = id; + vd->next = *var_list; + *var_list = vd; +} diff --git a/src/dg_wldcmd.c b/src/py_wldcmd.c similarity index 96% rename from src/dg_wldcmd.c rename to src/py_wldcmd.c index 51035b9..beb38ec 100644 --- a/src/dg_wldcmd.c +++ b/src/py_wldcmd.c @@ -1,17 +1,14 @@ -/************************************************************************** -* File: dg_wldcmd.c Part of tbaMUD * -* Usage: Contains the command_interpreter for rooms, room commands. * -* * -* $Author: galion/Mark A. Heilpern/egreen/Welcor $ * -* $Date: 2004/10/11 12:07:00$ * -* $Revision: 1.0.14 $ * -**************************************************************************/ +/** +* @file py_wldcmd.c +* +* This set of code was not originally part of the circlemud distribution. +*/ #include "conf.h" #include "sysdep.h" #include "structs.h" #include "screen.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "utils.h" #include "comm.h" #include "interpreter.h" diff --git a/src/redit.c b/src/redit.c index 6cee02d..fb43cbd 100644 --- a/src/redit.c +++ b/src/redit.c @@ -18,7 +18,7 @@ #include "genzon.h" #include "oasis.h" #include "improved-edit.h" -#include "dg_olc.h" +#include "py_olc.h" #include "constants.h" #include "modify.h" diff --git a/src/set.c b/src/set.c index 043bd51..74b1e97 100644 --- a/src/set.c +++ b/src/set.c @@ -35,13 +35,407 @@ #include "modify.h" #include "genobj.h" #include "genmob.h" -#include "dg_scripts.h" +#include "py_triggers.h" #include "fight.h" #include "toml.h" + +#define PY_SCRIPT_EXT ".py" + +static void script_toml_escape(const char *input, char *output, size_t outsz) +{ + size_t len = 0; + const char *p = input ? input : ""; + + if (!outsz) + return; + + while (*p && len + 2 < outsz) { + if (*p == '\\' || *p == '"') { + output[len++] = '\\'; + if (len + 1 >= outsz) + break; + } + output[len++] = *p++; + } + + output[len] = '\0'; +} + +static int script_normalize_name(const char *input, char *output, size_t outsz) +{ + const char *ext; + + if (!input || !*input || !output || outsz < 4) + return 0; + + if (strchr(input, '/')) + return 0; + + strlcpy(output, input, outsz); + ext = strrchr(output, '.'); + if (!ext) + strlcat(output, PY_SCRIPT_EXT, outsz); + + return 1; +} + +static int script_exists(const char *script_name) +{ + char path[PATH_MAX]; + struct stat st; + + if (!script_name || !*script_name) + return 0; + + snprintf(path, sizeof(path), "%s%s", SCRIPTS_PREFIX, script_name); + if (stat(path, &st) < 0) + return 0; + + return S_ISREG(st.st_mode); +} + +static int script_find_trigger_by_script(const char *script, zone_rnum znum, int attach_type) +{ + int i; + int bottom, top; + + if (!script || !*script) + return NOTHING; + + if (znum != NOWHERE) { + bottom = zone_table[znum].bot; + top = zone_table[znum].top; + } else { + bottom = INT_MIN; + top = INT_MAX; + } + + for (i = 0; i < top_of_trigt; i++) { + if (!trig_index[i] || !trig_index[i]->proto) + continue; + if (trig_index[i]->vnum < bottom || trig_index[i]->vnum > top) + continue; + if (trig_index[i]->proto->attach_type != attach_type) + continue; + if (!trig_index[i]->proto->script) + continue; + if (!str_cmp(trig_index[i]->proto->script, script)) + return trig_index[i]->vnum; + } + + return NOTHING; +} + +static int script_find_next_trigger_vnum(zone_rnum znum) +{ + int vnum; + int bottom; + int top; + + if (znum == NOWHERE) + return NOTHING; + + bottom = zone_table[znum].bot; + top = zone_table[znum].top; + + for (vnum = bottom; vnum <= top; vnum++) { + if (real_trigger(vnum) == NOTHING) + return vnum; + } + + return NOTHING; +} + +static int script_default_trigger_type(int attach_type) +{ + switch (attach_type) { + case MOB_TRIGGER: return MTRIG_LOAD; + case OBJ_TRIGGER: return OTRIG_LOAD; + case WLD_TRIGGER: return WTRIG_RESET; + default: return 0; + } +} + +static int script_default_trigger_narg(int attach_type, long trigger_type) +{ + if (attach_type == MOB_TRIGGER && (trigger_type & MTRIG_LOAD)) + return 100; + if (attach_type == OBJ_TRIGGER && (trigger_type & OTRIG_LOAD)) + return 100; + if (attach_type == WLD_TRIGGER && (trigger_type & WTRIG_RESET)) + return 100; + return 0; +} + +static const char *script_default_trigger_name(const char *script) +{ + const char *base; + const char *dot; + + if (!script || !*script) + return "script"; + + base = script; + dot = strrchr(base, '.'); + if (dot && dot > base) + return base; + + return base; +} + +static int script_insert_trigger_index(trig_data *trig, int vnum) +{ + struct index_data **new_index; + struct trig_data *proto; + struct trig_data *live_trig; + trig_rnum i, rnum = NOTHING; + + if (!trig || vnum <= 0) + return NOTHING; + + CREATE(new_index, struct index_data *, top_of_trigt + 1); + + for (i = 0; i < top_of_trigt; i++) { + if (rnum == NOTHING && trig_index[i]->vnum > vnum) { + rnum = i; + CREATE(new_index[rnum], struct index_data, 1); + new_index[rnum]->vnum = vnum; + new_index[rnum]->number = 0; + new_index[rnum]->func = NULL; + proto = trig; + new_index[rnum]->proto = proto; + proto->nr = rnum; + new_index[i + 1] = trig_index[i]; + trig_index[i]->proto->nr = i + 1; + } else { + if (rnum == NOTHING) { + new_index[i] = trig_index[i]; + } else { + new_index[i + 1] = trig_index[i]; + trig_index[i]->proto->nr = i + 1; + } + } + } + + if (rnum == NOTHING) { + rnum = top_of_trigt; + CREATE(new_index[rnum], struct index_data, 1); + new_index[rnum]->vnum = vnum; + new_index[rnum]->number = 0; + new_index[rnum]->func = NULL; + proto = trig; + new_index[rnum]->proto = proto; + proto->nr = rnum; + } + + free(trig_index); + trig_index = new_index; + top_of_trigt++; + + for (live_trig = trigger_list; live_trig; live_trig = live_trig->next_in_world) + GET_TRIG_RNUM(live_trig) += (GET_TRIG_RNUM(live_trig) != NOTHING && GET_TRIG_RNUM(live_trig) >= rnum); + + return rnum; +} + +static int script_append_trigger_toml(int znum, trig_data *trig, int vnum) +{ + char fname[PATH_MAX]; + FILE *fp; + char name_buf[MAX_INPUT_LENGTH]; + char arg_buf[MAX_INPUT_LENGTH]; + char script_buf[MAX_INPUT_LENGTH]; + + if (!trig || vnum <= 0) + return 0; + + snprintf(fname, sizeof(fname), "%s/%d.toml", TRG_PREFIX, znum); + fp = fopen(fname, "a"); + if (!fp) + return 0; + + script_toml_escape(trig->name ? trig->name : "script", name_buf, sizeof(name_buf)); + script_toml_escape(trig->arglist ? trig->arglist : "", arg_buf, sizeof(arg_buf)); + script_toml_escape(trig->script ? trig->script : "", script_buf, sizeof(script_buf)); + + fprintf(fp, "\n[[trigger]]\n"); + fprintf(fp, "vnum = %d\n", vnum); + fprintf(fp, "name = \"%s\"\n", name_buf); + fprintf(fp, "attach_type = %d\n", trig->attach_type); + fprintf(fp, "flags = %ld\n", trig->trigger_type); + fprintf(fp, "narg = %d\n", trig->narg); + fprintf(fp, "arglist = \"%s\"\n", arg_buf); + fprintf(fp, "script = \"%s\"\n", script_buf); + + fclose(fp); + return 1; +} + +static int script_create_trigger_for_zone(zone_rnum znum, int attach_type, const char *script) +{ + trig_data *trig; + int vnum; + int rnum; + + if (znum == NOWHERE || !script || !*script) + return NOTHING; + + vnum = script_find_next_trigger_vnum(znum); + if (vnum == NOTHING) + return NOTHING; + + CREATE(trig, trig_data, 1); + memset(trig, 0, sizeof(*trig)); + trig->name = strdup(script_default_trigger_name(script)); + trig->attach_type = (byte)attach_type; + trig->trigger_type = (long)script_default_trigger_type(attach_type); + trig->narg = script_default_trigger_narg(attach_type, trig->trigger_type); + trig->arglist = strdup(""); + trig->script = strdup(script); + + rnum = script_insert_trigger_index(trig, vnum); + if (rnum == NOTHING) { + if (trig->name) + free(trig->name); + if (trig->arglist) + free(trig->arglist); + if (trig->script) + free(trig->script); + free(trig); + return NOTHING; + } + + if (!script_append_trigger_toml(zone_table[znum].number, trig, vnum)) + return NOTHING; + + return vnum; +} + +static int script_resolve_trigger_vnum(struct char_data *ch, const char *arg, + zone_rnum znum, int attach_type) +{ + char script_name[MAX_INPUT_LENGTH]; + int vnum; + + if (!arg || !*arg) + return NOTHING; + + if (is_number(arg)) + return atoi(arg); + + if (!script_normalize_name(arg, script_name, sizeof(script_name))) { + send_to_char(ch, "Script name is invalid.\r\n"); + return NOTHING; + } + + if (!script_exists(script_name)) { + send_to_char(ch, "Script file %s not found.\r\n", script_name); + return NOTHING; + } + + vnum = script_find_trigger_by_script(script_name, znum, attach_type); + if (vnum != NOTHING) + return vnum; + + vnum = script_create_trigger_for_zone(znum, attach_type, script_name); + if (vnum == NOTHING) { + send_to_char(ch, "No available trigger vnums in this zone.\r\n"); + return NOTHING; + } + + send_to_char(ch, "Created trigger %d for script %s.\r\n", vnum, script_name); + return vnum; +} + +static int script_find_attached_trigger_vnum(struct trig_proto_list *list, + const char *script, int attach_type) +{ + struct trig_proto_list *tp; + int rnum; + + if (!script || !*script) + return NOTHING; + + for (tp = list; tp; tp = tp->next) { + rnum = real_trigger(tp->vnum); + if (rnum == NOTHING || !trig_index[rnum] || !trig_index[rnum]->proto) + continue; + if (trig_index[rnum]->proto->attach_type != attach_type) + continue; + if (!trig_index[rnum]->proto->script) + continue; + if (!str_cmp(trig_index[rnum]->proto->script, script)) + return tp->vnum; + } + + return NOTHING; +} #include "quest.h" #include "set.h" +static int proto_trigger_has(struct trig_proto_list *list, int vnum) +{ + for (; list; list = list->next) + if (list->vnum == vnum) + return 1; + return 0; +} + +static int proto_trigger_add(struct trig_proto_list **list, int vnum) +{ + struct trig_proto_list *trig, *tail; + + if (!list) + return 0; + + if (proto_trigger_has(*list, vnum)) + return 0; + + CREATE(trig, struct trig_proto_list, 1); + trig->vnum = vnum; + trig->next = NULL; + + if (!*list) { + *list = trig; + return 1; + } + + tail = *list; + while (tail->next) + tail = tail->next; + tail->next = trig; + return 1; +} + +static int proto_trigger_remove(struct trig_proto_list **list, int vnum) +{ + struct trig_proto_list *cur, *prev, *next; + int removed = 0; + + if (!list) + return 0; + + prev = NULL; + cur = *list; + while (cur) { + next = cur->next; + if (cur->vnum == vnum) { + if (prev) + prev->next = next; + else + *list = next; + free(cur); + removed = 1; + } else { + prev = cur; + } + cur = next; + } + + return removed; +} + static void rset_show_usage(struct char_data *ch) { send_to_char(ch, @@ -58,6 +452,7 @@ static void rset_show_usage(struct char_data *ch) " rset add hidden \r\n" " rset add forage \r\n" " rset add edesc \r\n" + " rset add script