diff --git a/docs/events.md b/docs/events.md
index 8241379..d95c05d 100644
--- a/docs/events.md
+++ b/docs/events.md
@@ -34,6 +34,34 @@ game events for the module, AoEs, areas, doors, encounters, placeables, stores,
 triggers, traps, and creatures. PCs also have their own creature events that use
 the prefix `OnPC*` instead of `OnCreature*`.
 
+Scripts can also be registered using the `RegisterPluginScripts()` function.
+This function takes the [object to register the script on](#script-sources) and
+a CSV list of glob patterns matching nss filenames.  This function requires
+a specified decorator be applied to each event script in a script library.
+The decorator must reside in a comment immediately preceding the specific
+function and must contain the following:
+
+- Decorator (required):  `@EVENT[...]`
+- Event Reference (required):  `OnModuleLoad`
+- [Priority](#script-priorities) (optional)
+
+Example decorators:
+
+```c
+// @EVENT[OnModuleLoad:first]
+void pw_OnModuleLoad() {...}
+
+// @EVENT[OnPlayerDeath]
+void pw_OnPlayerDeath() {...}
+
+// @EVENT[OnPlayerChat:3.0]
+void pw_OnPlayerChat() {...}
+```
+
+While registering scripts decorated with `@EVENT[...]`, `RegisterPluginScripts()`
+will also register any library scripts decorated with `@LIBRARY[]` in script
+libraries matching the given glob patterns.
+
 ## Script Sources
 Since scripts are not placed directly into an object's event script slots, the
 framework needs to know what scripts should be run for an event. There are
diff --git a/src/core/core_i_framework.nss b/src/core/core_i_framework.nss
index b62cc5b..c2f2cea 100644
--- a/src/core/core_i_framework.nss
+++ b/src/core/core_i_framework.nss
@@ -132,6 +132,22 @@ void ClearEventState(string sEvent = "");
 ///     whether oTarget is a plugin or other object.
 void RegisterEventScript(object oTarget, string sEvent, string sScripts, float fPriority = -1.0);
 
+/// @brief Register all scripts decoarted with @EVENT[...] in any nss file
+///     matching the given glob patterns.
+/// @param oPlugin A plugin's data object.
+/// @param sPatterns A CSV list of glob patterns to match. Supported syntax:
+///     - `*`: match zero or more characters
+///     - `?`: match a single character
+///     - `[abc]` : match any of a, b, or c
+///     - `[a-z]` : match any character from a-z
+///     - other text is matched literally
+/// @param bLoadLibraries If TRUE, will register all scripts decorated with
+///     with @LIBRARY[].
+/// @note Scripts marked with @EVENT[...] will automatically be registered
+///     as library scripts, only use @LIBRARY[] for library scripts not
+///     associated with events.
+void RegisterPluginScripts(object oPlugin, string sPatterns, int bLoadLibraries = TRUE);
+
 /// @brief Run an event, causing all subscribed scripts to trigger.
 /// @param sEvent The name of the event
 /// @param oInit The object triggering the event (e.g, a PC OnClientEnter)
@@ -590,12 +606,73 @@ void RegisterEventScript(object oTarget, string sEvent, string sScripts, float f
     }
 }
 
-// Alias function for backward compatibility.
 void RegisterEventScripts(object oTarget, string sEvent, string sScripts, float fPriority = -1.0)
 {
     RegisterEventScript(oTarget, sEvent, sScripts, fPriority);
 }
 
+void DumpEventScripts(string sEvent, int bSearchAll = FALSE)
+{
+
+}
+
+void RegisterPluginScripts(object oPlugin, string sPatterns, int bLoadLibraries = TRUE)
+{
+    Debug("Loading plugin script libraries matching \"" + sPatterns + "\"");
+    
+    json jLibraries, jScripts;
+    if ((jLibraries = FilterByPatterns(_GetScriptsByPrefix(JSON_ARRAY, "", RESTYPE_NSS), ListToJson(sPatterns))) == JSON_ARRAY)
+    {
+        Debug("No plugin script libraries found matching \"" + sPatterns + "\"");
+        return;
+    }
+
+    string sPriorities = "first|only|last|default";
+    string sReturns    = "void|int|string|json|object|float|sqlquery|talent|effect|location|vector|cassowary|itemproperty";
+    json   jAlternates = ListToJson(sPriorities + "," + sReturns);
+    
+    string sEventRegex = SubstituteString("@EVENT\\[([ -~]*?):?(10\\.0|\\d{1}\\.\\d{1}|$1)?\\](?:.|\\r?\\n)*?(?:$2)\\s+([a-z_]\\w*)\\s*\\(", jAlternates);
+    string sLibRegex   = SubstituteString("@LIBRARY\\[\\](?:.|\\r?\\n)*?(?:$2)\\s+([a-z_]\\w*)\\s*\\(", jAlternates);
+    
+    int i; for (i; i < JsonGetLength(jLibraries); i++)
+    {
+        string sLibrary = JsonGetString(JsonArrayGet(jLibraries, i));
+        string sContent = ResManGetFileContents(sLibrary, RESTYPE_NSS);
+        if (sContent == "")
+        {
+            Debug("Unable to retrieve contents of " + sLibrary + ".nss");
+            continue;
+        }
+
+        if ((jScripts = RegExpIterate(sEventRegex, sContent)) == JSON_ARRAY)
+            Debug("No @EVENT[...] decorators found in " + sLibrary + ".nss");
+        else
+        {
+            int n; for (n; n < JsonGetLength(jScripts); n++)
+            {
+                string sIndex   = IntToString(n);
+                string sEvent   = JsonGetString(JsonPointer(jScripts, "/" + sIndex + "/1"));
+                string sScript  = JsonGetString(JsonPointer(jScripts, "/" + sIndex + "/3"));
+                float fPriority = StringToPriority(JsonGetString(JsonPointer(jScripts, "/" + sIndex + "/2")), GLOBAL_EVENT_PRIORITY);
+
+                RegisterEventScript(oPlugin, sEvent, sScript, fPriority);
+                AddLibraryScript(sLibrary, sScript);
+            }
+        }
+
+        if (bLoadLibraries)
+        {
+            if ((jScripts = RegExpIterate(sLibRegex, sContent)) == JSON_ARRAY)
+                Debug("No @LIBRARY[] decorators found in " + sLibrary + ".nss");
+            else
+            {
+                int n; for (n; n < JsonGetLength(jScripts); n++)
+                    AddLibraryScript(sLibrary, JsonGetString(JsonPointer(jScripts, "/" + IntToString(n) + "/1")));
+            }
+        }
+    }
+}
+
 // Private function. Checks oTarget for a builder-specified event hook string
 // for sEvent and expands it into a list of scripts and priorities on oTarget.
 // An event hook string is a CSV list of scripts and priorities, each specified
@@ -986,6 +1063,8 @@ void HookObjectEvents(object oObject, int bSkipHeartbeat = TRUE, int bStoreOldEv
                 nEnd   = EVENT_SCRIPT_TRIGGER_ON_CLICKED;
                 if (bSkipHeartbeat)
                     nStart++;
+                if (JsonPointer(ObjectToJson(oObject), "/LinkedTo/value") == JsonString(""))
+                    nEnd--;
                 break;
             case OBJECT_TYPE_STORE:
                 nStart = EVENT_SCRIPT_STORE_ON_OPEN;