View Issue Details

IDProjectCategoryView StatusLast Update
0000243LDMud 3.5Efunspublic2018-01-29 21:57
ReporterlarsAssigned ToGnomi  
PrioritynormalSeverityfeatureReproducibilityN/A
Status resolvedResolutionfixed 
Target Version3.5.0Fixed in Version3.5.0 
Summary0000243: add_action() on closures
DescriptionShort: Allow add_actions() on closures
Date: 2001-05-16
From: Gnomi
Type: Feature
State: New

Allow to add actions to closures.
TagsNo tags attached.
Attached Files
closure-actions.diff (22,604 bytes)   
Index: trunk/HISTORY
===================================================================
--- trunk/HISTORY	(Revision 2644)
+++ trunk/HISTORY	(Arbeitskopie)
@@ -5,7 +5,10 @@
 For a detailed list of all changes see the file CHANGELOG.
 
 xx-xxx-2009 (LDMud Team) -- 3.5.0
-    - Removed Efun:
+    - Changed Efuns:
+       + add_action(): The function can be given as a closure. (#243)
+
+    - Removed Efuns:
        + The deprecated efun cat() was removed. It can be easily and more
          flexibly replaced by an sefun, which is supplied with the driver.
          (#637)
Index: trunk/doc/efun.de/add_action.de
===================================================================
--- trunk/doc/efun.de/add_action.de	(Revision 2644)
+++ trunk/doc/efun.de/add_action.de	(Arbeitskopie)
@@ -1,9 +1,9 @@
 SYNOPSIS
         #include <sys/commands.h>
 
-        void add_action(string fun, string cmd);
-        void add_action(string fun, string cmd, int flag);
-        void add_action(string fun);        /* veraltet */
+        void add_action(string|closure fun, string cmd);
+        void add_action(string|closure fun, string cmd, int flag);
+        void add_action(string|closure fun);        /* veraltet */
 
 BESCHREIBUNG
         Setzt eine lokale Funktion fun, die aufgerufen wird, wenn der Spieler
@@ -111,7 +111,7 @@
 AENDERUNGEN
         Das Argument <flag> < 0 wird seit 3.2@127 unterstuetzt, aber erst ab
         LDMud 3.2.8 richtig implementiert. LDMud 3.2.9 fuehrte das AA_IMM_ARGS
-        Flag ein.
+        Flag ein. Ab LDMud 3.5 kann man Closures als Funktionen uebergeben.
 
 SIEHE AUCH
         query_verb(E), query_command(E), add_verb(E), add_xverb(E), init(A)
Index: trunk/doc/efun/add_action
===================================================================
--- trunk/doc/efun/add_action	(Revision 2644)
+++ trunk/doc/efun/add_action	(Arbeitskopie)
@@ -1,8 +1,8 @@
 SYNOPSIS
         #include <sys/commands.h>
 
-        void add_action(string fun, string cmd)
-        void add_action(string fun, string cmd, int flag)
+        void add_action(string|closure fun, string cmd)
+        void add_action(string|closure fun, string cmd, int flag)
 
 DESCRIPTION
         Set up a local function fun to be called when user input
@@ -116,6 +116,7 @@
         really implemented before LDMud 3.2.8.
         LDMud 3.2.9 introduced the AA_IMM_ARGS flag.
         LDMud 3.3 removed the historical add_action(fun) notation.
+        Since LDMud 3.5 the function can be given as a closure.
 
 SEE ALSO
         query_verb(E), query_command(E), remove_action(), init(A)
Index: trunk/src/actions.c
===================================================================
--- trunk/src/actions.c	(Revision 2644)
+++ trunk/src/actions.c	(Arbeitskopie)
@@ -202,9 +202,9 @@
     alloc_action_sent++;
 
     p->verb = NULL;
-    p->function = NULL;
     p->ob = NULL;
     p->shadow_ob = NULL;
+    init_empty_callback(&(p->cb));
     return p;
 } /* new_action_sent() */
 
@@ -221,8 +221,7 @@
         fatal("free_action_sent() received internal sent %d\n", p->sent.type);
 #endif
 
-    if (p->function)
-        free_mstring(p->function);
+    free_callback(&(p->cb));
     if (p->verb)
         free_mstring(p->verb);
     xfree(p);
@@ -333,18 +332,11 @@
 #ifdef DEBUG
             if (d_flag > 1)
             {
-                if (tmp->function && tmp->verb)
-                    debug_message("%s --Unlinking sentence fun='%s', verb='%s'\n"
-                                 , time_stamp(), get_txt(tmp->function)
-                                 , get_txt(tmp->verb));
-                else if (tmp->function)
-                    debug_message("%s --Unlinking sentence fun='%s', verb=0\n"
-                                 , time_stamp(), get_txt(tmp->function));
-                else if (tmp->verb)
-                    debug_message("%s --Unlinking sentence fun=0, verb='%s'\n"
+                if (tmp->verb)
+                    debug_message("%s --Unlinking sentence verb='%s'\n"
                                  , time_stamp(), get_txt(tmp->verb));
                 else
-                    debug_message("%s --Unlinking sentence fun=0, verb=0\n"
+                    debug_message("%s --Unlinking sentence verb=0\n"
                                  , time_stamp());
             }
 #endif
@@ -385,18 +377,11 @@
 #ifdef DEBUG
             if (d_flag > 1)
             {
-                if (tmp->function && tmp->verb)
-                    debug_message("%s --Unlinking sentence fun='%s', verb='%s'\n"
-                                 , time_stamp(), get_txt(tmp->function)
-                                 , get_txt(tmp->verb));
-                else if (tmp->function)
-                    debug_message("%s --Unlinking sentence fun='%s', verb=0\n"
-                                 , time_stamp(), get_txt(tmp->function));
-                else if (tmp->verb)
-                    debug_message("%s --Unlinking sentence fun=0, verb='%s'\n"
+                if (tmp->verb)
+                    debug_message("%s --Unlinking sentence verb='%s'\n"
                                  , time_stamp(), get_txt(tmp->verb));
                 else
-                    debug_message("%s --Unlinking sentence fun=0, verb=0\n"
+                    debug_message("%s --Unlinking sentence verb=0\n"
                                  , time_stamp());
             }
 #endif
@@ -443,8 +428,14 @@
 
 #ifdef DEBUG
                 if (d_flag > 1)
-                    debug_message("%s --Unlinking sentence %s\n"
-                                 , time_stamp(), get_txt(s->function));
+                {
+                    if (tmp->verb)
+                        debug_message("%s --Unlinking sentence verb='%s'\n"
+                                     , time_stamp(), get_txt(tmp->verb));
+                    else
+                        debug_message("%s --Unlinking sentence verb=0\n"
+                                     , time_stamp());
+                }
 #endif
                 tmp = s;
                 s = (action_t *)s->sent.next;
@@ -993,8 +984,7 @@
 
 #ifdef DEBUG
         if (d_flag > 1)
-            debug_message("%s Local command %s on %s\n", time_stamp()
-                         , get_txt(sa->function)
+            debug_message("%s Local command on %s\n", time_stamp()
                          , get_txt(sa->ob->name));
 #endif
 
@@ -1055,8 +1045,8 @@
          */
         marker_sent->sent.type = SENT_MARKER;
         marker_sent->verb = NULL;
-        marker_sent->function = NULL;
         marker_sent->shadow_ob = NULL;
+        init_empty_callback(&(marker_sent->cb));
 
         /* Push the argument and call the command function.
          */
@@ -1065,11 +1055,11 @@
             if (strlen(buff) > mstrsize(sa->verb))
             {
                 push_c_string(inter_sp, &buff[mstrsize(sa->verb)]);
-                ret = sapply(sa->function, sa->ob, 1);
+                ret = execute_callback(&(sa->cb), 1, MY_TRUE, save_current_object == NULL);
             }
             else
             {
-                ret = sapply(sa->function, sa->ob, 0);
+                ret = execute_callback(&(sa->cb), 0, MY_TRUE, save_current_object == NULL);
             }
         }
         else if (s->type == SENT_NO_SPACE)
@@ -1090,30 +1080,25 @@
                 last_verb = new_tabled(buff);
                 buff[len] = ch;
                 push_c_string(inter_sp, &buff[len]);
-                ret = sapply(sa->function, sa->ob, 1);
+                ret = execute_callback(&(sa->cb), 1, MY_TRUE, save_current_object == NULL);
                 free_mstring(last_verb);
                 last_verb = inter_sp->u.str; inter_sp--;
             }
             else
             {
-                ret = sapply(sa->function, sa->ob, 0);
+                ret = execute_callback(&(sa->cb), 0, MY_TRUE, save_current_object == NULL);
             }
         }
         else if (buff[length] == ' ')
         {
             push_c_string(inter_sp, &buff[length+1]);
-            ret = sapply(sa->function, sa->ob, 1);
+            ret = execute_callback(&(sa->cb), 1, MY_TRUE, save_current_object == NULL);
         }
         else
         {
-            ret = sapply(sa->function, sa->ob, 0);
+            ret = execute_callback(&(sa->cb), 0, MY_TRUE, save_current_object == NULL);
         }
 
-        if (ret == 0)
-        {
-            errorf("function %s not found.\n", get_txt(sa->function));
-        }
-
         /* Restore the old current_object and command_giver */
         current_object = save_current_object;
         command_giver  = save_command_giver;
@@ -1181,8 +1166,8 @@
      */
     marker_sent->sent.type = SENT_MARKER;
     marker_sent->verb = NULL;
-    marker_sent->function = NULL;
     marker_sent->shadow_ob = NULL;
+    init_empty_callback(&(marker_sent->cb));
     command_marker = marker_sent;
 
     /* If the command was not found, notify the failure */
@@ -1289,6 +1274,7 @@
     action_t *p;
     object_t *ob, *shadow_ob;
     string_t *str;
+    int error_index;
 
     /* Can't take actions from destructed objects */
     if (current_object->flags & O_DESTRUCTED)
@@ -1298,7 +1284,8 @@
     ob = current_object;
 
     /* Check if the call comes from a shadow of the current object */
-    if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing)
+    if (func->type == T_STRING
+     && ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing)
     {
         shadow_ob = ob;
         str = find_tabled(func->u.str);
@@ -1332,31 +1319,58 @@
 
 #ifdef DEBUG
     if (d_flag > 1)
-        debug_message("%s --Add action %s\n", time_stamp(), get_txt(func->u.str));
+        debug_message("%s --Add action for %s\n", time_stamp(), get_txt(cmd->u.str));
 #endif
 
-    /* Sanity checks */
-    if (get_txt(func->u.str)[0] == ':')
-        errorf("Illegal function name: %s\n", get_txt(func->u.str));
+    /* Allocate and initialise a new sentence */
+    p = new_action_sent();
 
-    if (compat_mode)
+    if (func->type == T_STRING)
     {
-        char *s;
-        s = get_txt(func->u.str);
-        if (*s++=='e' && *s++=='x' && *s++=='i' && *s++=='t'
-         && (!*s || mstrsize(func->u.str) == 4))
+        /* Sanity checks */
+        if (get_txt(func->u.str)[0] == ':')
         {
-            errorf("Illegal to define a command to the exit() function.\n");
-            /* NOTREACHED */
-            return MY_TRUE;
+            free_action_sent(p);
+            errorf("Illegal function name: %s\n", get_txt(func->u.str));
         }
+
+        if (compat_mode)
+        {
+            char *s;
+            s = get_txt(func->u.str);
+            if (*s++=='e' && *s++=='x' && *s++=='i' && *s++=='t'
+             && (!*s || mstrsize(func->u.str) == 4))
+            {
+                free_action_sent(p);
+                errorf("Illegal to define a command to the exit() function.\n");
+                /* NOTREACHED */
+                return MY_TRUE;
+            }
+        }
+
+        error_index = setup_function_callback(&(p->cb), ob, func->u.str
+                                             , 0, NULL, MY_TRUE
+                                             );
     }
+    else if (func->type == T_CLOSURE)
+    {
+        error_index = setup_closure_callback(&(p->cb), func
+                                             , 0, NULL, MY_TRUE
+                                             );
+    }
+    else
+    {
+        error_index = 0;
+    }
 
-    /* Allocate and initialise a new sentence */
-    p = new_action_sent();
+    if (error_index >= 0)
+    {
+        free_action_sent(p);
+        vefun_bad_arg(error_index + 1, inter_sp);
+        /* NOTREACHED */
+        return MY_TRUE;
+    }
 
-    /* Set ->function to the function name, made tabled */
-    p->function = make_tabled(func->u.str); func->type = T_NUMBER;
     p->ob = ob;
     p->shadow_ob = shadow_ob;
 
@@ -1615,7 +1629,7 @@
 
 /* EFUN match_command()
  *
- *   mixed * execute_command (string command, object origin)
+ *   mixed * match_command (string command, object origin)
  *
  * Take the command <command>, parse it, and return an array of all
  * matching actions added to <origin> (read: <origin> is the object
@@ -1626,8 +1640,8 @@
  *   string [CMDM_VERB]:   The matched verb.
  *   string [CMDM_ARG]:    The argument string remaining, or 0 if none.
  *   object [CMDM_OBJECT]: The object defining the action.
- *   string [CMDM_FUN]:    The name of the function to call in CMDM_OBJECT,
- *                         which may be static.
+ *   mixed  [CMDM_FUN]:    The name of the function to call in CMDM_OBJECT,
+ *                         which may be static, or a closure.
  *
  * The efun is useful for both debugging, and for implementing your
  * own H_COMMAND handling.
@@ -1656,7 +1670,7 @@
         struct cmd_s *next;
         string_t     *verb;  /* The verb */
         string_t     *arg;   /* The arg string, or NULL */
-        string_t     *fun;   /* The function to call */
+        svalue_t      fun;   /* The function to call */
         object_t     *ob;    /* The object to call */
         sentence_t   *s;     /* The sentence */
     };
@@ -1767,7 +1781,7 @@
         new_cmd->next = NULL;
         new_cmd->s = s;
         new_cmd->ob = ref_object(sa->ob, "match_command");
-        new_cmd->fun = ref_mstring(sa->function);
+        transfer_svalue_no_free(&(new_cmd->fun), callback_function(&(sa->cb)));
 
         /* Fill in the verb and arg information of the cmd_s structure.
          */
@@ -1858,7 +1872,7 @@
         if (pcmd->arg)
             put_string(&(sub->item[CMDM_ARG]), pcmd->arg);
         /* else: entry is svalue-0 already */
-        put_string(&(sub->item[CMDM_FUN]), pcmd->fun);
+        transfer_svalue_no_free(&(sub->item[CMDM_FUN]), &(pcmd->fun));
         put_object(&(sub->item[CMDM_OBJECT]), pcmd->ob);
 
         put_array(&(rc->item[i]), sub);
@@ -2030,7 +2044,7 @@
         p++;
         put_ref_object(p, sa->ob, "get_action");
         p++;
-        put_ref_string(p, sa->function);
+        transfer_svalue_no_free(p, callback_function(&(sa->cb)));
 
         return v;
     }
@@ -2110,7 +2124,7 @@
         }
         if (mask & QA_FUNCTION)
         {
-            put_ref_string(p, sa->function);
+            transfer_svalue_no_free(p, callback_function(&(sa->cb)));
             p++;
         }
     }
@@ -2161,7 +2175,7 @@
             put_ref_string(p, sa->verb);
             p++;
 
-            put_ref_string(p, sa->function);
+            transfer_svalue_no_free(p, callback_function(&(sa->cb)));
             p++;
         }
     }
Index: trunk/src/actions.h
===================================================================
--- trunk/src/actions.h	(Revision 2644)
+++ trunk/src/actions.h	(Arbeitskopie)
@@ -4,7 +4,51 @@
 #include "driver.h"
 
 #include "typedefs.h"  /* object_t */
+#include "sent.h"
+#include "simulate.h"
 
+/* --- struct action_s: the action sentence structure ---
+ *
+ * Sentences of this type are used to hold the actions (verbs+functions)
+ * available to one object.
+ *
+ * A special case are SENT_MARKER sentences which are used to
+ * mark the progress of a command search.
+ */
+
+struct action_s
+{
+    sentence_t sent;  /* The basic sentence */
+    string_t *verb;
+      /* Shared string: the defined verb.
+       * For SENT_PLAIN and SENT_SHORT_VERB, this is the whole verb.
+       * For SENT_NO_SPACE, only the first letters of the command have
+       *   to match this verb.
+       */
+
+    object_t *ob;
+      /* Object defining this sentence. This value is used for comparisons
+       * only, and in case of SENT_MARKER it is in fact a *rt_context_t.
+       * The reference is not counted.
+       */
+
+    object_t *shadow_ob;
+      /* If the action originates from an object shadow, .ob will be the
+       * shadowed object (as the action has to seem to come from there),
+       * and this will be the actual shadow object defining the object.
+       * Otherwise, this entry is NULL.
+       */
+
+    callback_t cb;
+      /* The function that should actually be called.
+       */
+
+    unsigned short short_verb;
+      /* SENT_SHORT_VERB: the number of characters which have to
+       *   match at minimum.
+       */
+};
+
 /* --- Variables --- */
 
 extern object_t *command_giver;
Index: trunk/src/version.sh
===================================================================
--- trunk/src/version.sh	(Revision 2644)
+++ trunk/src/version.sh	(Arbeitskopie)
@@ -17,7 +17,7 @@
 # A timestamp, to be used by bumpversion and other scripts.
 # It can be used, for example, to 'touch' this file on every build, thus
 # forcing revision control systems to add it on every checkin automatically.
-version_stamp="2009-05-30 12:00:00"
+version_stamp="Mo 15. Jun 10:23:07 CEST 2009"
 
 # The version number information
 version_micro=0
Index: trunk/src/sent.h
===================================================================
--- trunk/src/sent.h	(Revision 2644)
+++ trunk/src/sent.h	(Arbeitskopie)
@@ -65,43 +65,6 @@
 };
 
 
-/* --- struct action_s: the action sentence structure ---
- *
- * Sentences of this type are used to hold the actions (verbs+functions)
- * available to one object.
- *
- * A special case are SENT_MARKER sentences which are used to
- * mark the progress of a command search.
- */
-
-struct action_s
-{
-    sentence_t sent;  /* The basic sentence */
-    string_t *verb;
-      /* Shared string: the defined verb.
-       * For SENT_PLAIN and SENT_SHORT_VERB, this is the whole verb.
-       * For SENT_NO_SPACE, only the first letters of the command have
-       *   to match this verb.
-       */
-    object_t *ob;
-      /* Object defining this sentence. This value is used for comparisons
-       * only, and in case of SENT_MARKER it is in fact a *rt_context_t.
-       * The reference is not counted.
-       */
-    object_t *shadow_ob;
-      /* If the action originates from an object shadow, .ob will be the
-       * shadowed object (as the action has to seem to come from there),
-       * and this will be the actual shadow object defining the object.
-       * The reference is not counted.
-       * Otherwise, this entry is NULL.
-       */
-    string_t *function;             /* the name of the action function */
-    unsigned short short_verb;
-      /* SENT_SHORT_VERB: the number of characters which have to
-       *   match at minimum.
-       */
-};
-
 /* --- struct shadow_s: the action sentence structure ---
  *
  * Main purpose of the shadow sentence is to link together the shadowing
Index: trunk/src/func_spec
===================================================================
--- trunk/src/func_spec	(Revision 2644)
+++ trunk/src/func_spec	(Arbeitskopie)
@@ -647,7 +647,7 @@
 
         /* Verbs and Commands (TODO: should be optional) */
 
-void    add_action(string, string, void|int);
+void    add_action(string|closure, string, void|int);
 int     command(string, void|object);
 mixed  *command_stack();
 int     command_stack_depth();
Index: trunk/src/gcollect.c
===================================================================
--- trunk/src/gcollect.c	(Revision 2644)
+++ trunk/src/gcollect.c	(Arbeitskopie)
@@ -1582,6 +1582,20 @@
 
 /*-------------------------------------------------------------------------*/
 static void
+clear_action_ref (action_t *p)
+
+/* Clear the refs of all sentences in list <p>.
+ */
+
+{
+    do
+    {
+        clear_ref_in_callback(&(p->cb));
+    } while ( NULL != (p = (action_t *)p->sent.next) );
+}
+
+/*-------------------------------------------------------------------------*/
+static void
 gc_note_action_ref (action_t *p)
 
 /* Mark the strings of function and verb of all sentences in list <p>.
@@ -1589,8 +1603,7 @@
 
 {
     do {
-        if (p->function)
-            MARK_MSTRING_REF(p->function);
+        count_ref_in_callback(&(p->cb));
         if (p->verb)
             MARK_MSTRING_REF(p->verb);
         note_ref(p);
@@ -1984,6 +1997,19 @@
         ob->ref = 0;
         clear_string_ref(ob->name);
         clear_ref_in_vector(ob->variables, ob->prog->num_variables);
+
+        if (ob->sent)
+        {
+            sentence_t *sent;
+
+            sent = ob->sent;
+            if (ob->flags & O_SHADOW)
+                sent = sent->next;
+            if (sent)
+                clear_action_ref((action_t *)sent);
+        }
+
+
         if (was_swapped)
         {
             swap(ob, was_swapped);
Index: trunk/src/simulate.c
===================================================================
--- trunk/src/simulate.c	(Revision 2644)
+++ trunk/src/simulate.c	(Arbeitskopie)
@@ -3948,6 +3948,27 @@
 
 /*-------------------------------------------------------------------------*/
 svalue_t *
+callback_function (callback_t *cb)
+
+/* Returns the function to call from the callback structure <cb>.
+ * It returns a pointer to a statically allocated svalue_t,
+ * which contains either a T_STRING or T_CLOSURE.
+ * It's the caller's responsibility to free its contents.
+ */
+
+{
+    static svalue_t fun;
+
+    if (cb->is_lambda)
+        assign_svalue_no_free(&fun, &(cb->function.lambda));
+    else
+        put_ref_string(&fun, cb->function.named.name);
+
+    return &fun;
+} /* callback_object() */
+
+/*-------------------------------------------------------------------------*/
+svalue_t *
 execute_callback (callback_t *cb, int nargs, Bool keep, Bool toplevel)
 
 /* Call the callback <cb> with the <nargs> arguments already pushed
Index: trunk/src/simulate.h
===================================================================
--- trunk/src/simulate.h	(Revision 2644)
+++ trunk/src/simulate.h	(Arbeitskopie)
@@ -252,6 +252,7 @@
 #define apply_callback(cb,nargs)   execute_callback(cb,nargs,MY_TRUE,MY_FALSE)
 #define backend_callback(cb,nargs) execute_callback(cb,nargs,MY_FALSE,MY_TRUE)
 extern object_t *callback_object(callback_t *cb);
+extern svalue_t *callback_function (callback_t *cb);
 extern void callback_change_object (callback_t *cb, object_t *obj);
 #ifdef DEBUG
 extern void count_callback_extra_refs (callback_t *cb);
Index: trunk/CHANGELOG
===================================================================
--- trunk/CHANGELOG	(Revision 2644)
+++ trunk/CHANGELOG	(Arbeitskopie)
@@ -1,6 +1,10 @@
 This file lists all changes made to the game driver in all glory detail.
 See the file HISTORY for a user-oriented summary of all the changes.
 
+??-Jun-2009 (Gnomi)
+  - Allow closures as actions. (Bug #243)
+    (actions.c)
+
 15-Jun-2009 (Fuchur)
   - (parse.c) Fix off-by-one error in find_string(). It returned a wrong index.
 
closure-actions.diff (22,604 bytes)   

Activities

Gnomi

2009-06-15 04:30

manager   ~0001206

I attached a patch, that uses a callback_t for saving the function to call upon an action (thus allowing a closure). There is some overhead (we have no arguments to save, on named functions the object to be called is saved twice), but we can rely on the existing callback infrastructure.

As a side effect actions on static functions called using command() don't throw errors anymore (because execute_callback ignores them).

Gnomi

2009-06-18 01:27

manager   ~0001221

Committed as r2662.

Issue History

Date Modified Username Field Change
2004-11-26 22:54 lars New Issue
2009-06-15 04:23 Gnomi Project LDMud => LDMud 3.5
2009-06-15 04:23 Gnomi Status new => assigned
2009-06-15 04:23 Gnomi Assigned To => Gnomi
2009-06-15 04:24 Gnomi File Added: closure-actions.diff
2009-06-15 04:25 Gnomi File Deleted: closure-actions.diff
2009-06-15 04:25 Gnomi File Added: closure-actions.diff
2009-06-15 04:30 Gnomi Note Added: 0001206
2009-06-18 01:27 Gnomi Note Added: 0001221
2009-06-18 01:27 Gnomi Status assigned => resolved
2009-06-18 01:27 Gnomi Fixed in Version => 3.5.0
2009-06-18 01:27 Gnomi Resolution open => fixed
2010-11-16 09:42 Gnomi Source_changeset_attached => ldmud.git master 1781017a
2011-02-13 21:38 zesstra Target Version => 3.5.0
2018-01-29 18:59 Gnomi Source_changeset_attached => ldmud.git master 1781017a
2018-01-29 21:57 Gnomi Source_changeset_attached => ldmud.git master 1781017a