commit 0e25cc9a738da3c3dd75cf2384ea07302e05c2c0
Author: Rauru <38548565+AardRauru@users.noreply.github.com>
Date: Sat Nov 17 15:08:27 2018 -0700
reupload
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a43a58b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.db
+/db_backups/
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1f1da25
--- /dev/null
+++ b/README.md
@@ -0,0 +1,78 @@
+# Mobber
+
+A mob database plugin for campaigns and global quests.
+
+## Instructions
+
+### Prerequisites
+
+```
+Mushclient version 5.06+
+Aardmush r1916 snapshot+
+```
+
+### Installing
+
+- [x] File -> Plugins -> Add -> Rauru_Mobber.xml
+- [ ] File -> Plugins -> Add -> Rauru_Mobber_Miniwindow.xml (optional)
+
+## New Installation Setup
+
+A few commands are suggested to start on the right foot.
+
+```
+1. Enter 'mobber developer' to enable developer mode.
+2. Enter 'mobber update areas' to add the default areas to mobber.
+3. Enter 'mobber update rooms' to import your rooms from Aardwolf.db to mobber.
+4. Enter 'mobber developer' to disable developer mode.
+5. Enter 'mobber show areas' to verify areas and rooms were added.
+6. Check 'mobber help' for all of the commands and to start logging mobs.
+```
+
+## Frequently Asked Questions
+
+1. How do I upgrade to a new version of mobber?
+ - Download the new version of mobber.
+ - Drag and drop the mobber database and backups folder into the new version folder.
+ - Delete the old folder.
+ - Put the new version folder into your plugins folder.
+
+2. Why are some mobs in cp/gq check different colors?
+ - Red: The mob is currently dead.
+ - Yellow: The area or room has not been logged.
+ - Gray: The matching mob or room is outside of your level range.
+ - Blue: A matching area was found.
+ - Light Blue: A matching mob or room was found within your level range.
+
+3. Why am I getting error prompts when trying to add a mob to a room?
+ - You are likely trying to add a mob to a room or zone that you have not
+ added to the mobber database yet.
+
+## Developer Commands - Further Explained
+
+1. mobber backup
+ - Forces a manual backup of the database into /mobber/db_backups/
+2. mobber vacuum
+ - Defragments and frees up unused space from the database.
+3. mobber remove zone
+ - Removes the current zone and any rooms and mobs that belong to it.
+ - Usually 'mobber removemob zone' command is sufficient enough.
+4. mobber update keywords
+ - Explicitly generates and adds mob keywords to the database.
+ - This command must be used before manually assigning keywords.
+
+# Author
+
+* **Rauru** - *Plugin Author*
+
+## Acknowledgments
+
+* Anymouse - *Tester*
+* Castiel - *Tester*
+* Crowley - *Tester*
+* Gizmmo - *Tester*
+
+![help](https://i.imgur.com/IywOEBw.png)
+![menu1](https://i.imgur.com/AlbjeXo.png)
+![menu2](https://i.imgur.com/GEf13X8.png)
+![areas](https://i.imgur.com/VG0sAaZ.png)
\ No newline at end of file
diff --git a/Rauru_Mobber.xml b/Rauru_Mobber.xml
new file mode 100644
index 0000000..f4fcbe0
--- /dev/null
+++ b/Rauru_Mobber.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Rauru_Mobber_Miniwindow.xml b/Rauru_Mobber_Miniwindow.xml
new file mode 100644
index 0000000..df97c44
--- /dev/null
+++ b/Rauru_Mobber_Miniwindow.xml
@@ -0,0 +1,772 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/changelog.txt b/changelog.txt
new file mode 100644
index 0000000..19bb44d
--- /dev/null
+++ b/changelog.txt
@@ -0,0 +1,71 @@
+Version 2.4.5:
+--------------
+*Miniwindow buttons will now default to on
+*Autokill can now have stacked commands delimited by a double semicolon ;;
+
+Version 2.4.4:
+--------------
+*Fixed a bug where mw was defaulting to collapsed (Castiel)
+
+Version 2.4.3:
+--------------
+*Added 'mobber quest' command to the help file
+*Added 'mobber noexp ' command to auto-handle noexp on campaigns
+
+Version 2.4.2:
+--------------
+*Toggle ak state will now save. (Crowley/Zexe)
+*Added newline to end of scan highlighting
+*Merged addmob redundant functions
+*Added 'mobber quest' command to show quest target/rooms (Crowley)
+
+Version 2.4.1:
+--------------
+*Miniwindow fixed resize issue when autoresize was enabled and the height of window increased
+*Miniwindow fixed save state of buttons enabled/disabled
+
+Version 2.4:
+--------------
+*Plugin ids were changed to wipe state files for compatibility
+*Mobber find commands (mf, mf , mfa ) now have condensed outputs
+*Added lookups for out-of-level-range campaigns
+**If a mob or room match is found out-of-level-range the mob will be darkgray
+**Changed lookup order for matching mobs, should be slightly more accurate
+*Re-wrote miniwindow plugin for better control
+**Miniwindow now has more descriptive messages for campaigns and global quest status
+**Miniwindow now has a countdown timer for next campaign available
+**Miniwindow now has buttons for sending cp/gq commands
+**Miniwindow button colors can be changed with 'mobber btn mw' command
+**Miniwindow right-click options now include: collapse/expand, autoresize, hide/show btns
+
+Version 2.3.3:
+--------------
+*Global quest alert sound will only play if 'mobber mute quest' is set to off. (Castiel)
+
+Version 2.3.2:
+--------------
+*Fixed global quest complete triggers (Gizmmo)
+*Added immhomes to default zone list and changed duplicate roomid of uplanes/lplanes
+
+Version 2.3.1:
+--------------
+*Fixed quest fail message (Castiel)
+
+Version 2.3:
+------------
+*Fixed an nil error in miniwin uncaught due to state files
+*Changed miniwin bg
+*Changed a column in area table to unique, should have no affect on existing db's
+
+Version 2.2:
+------------
+*Added default target to quick-where
+*Right-click popup on miniwindow targets
+*Fixed miniwindow reset/hotspot deletion
+
+Version 2.1:
+------------
+*Added hyperlinks to most outputs
+*Added "s&d" alias names
+*Added a miniwindow plugin
+*Fixed level range searches
\ No newline at end of file
diff --git a/lua/mobber.lua b/lua/mobber.lua
new file mode 100644
index 0000000..09c8b42
--- /dev/null
+++ b/lua/mobber.lua
@@ -0,0 +1,3662 @@
+----------------------------------
+-- Mobber
+----------------------------------
+
+require("gmcphelper");
+require("sqlitedb");
+require("checkplugin");
+require("stringutils");
+require("tprint");
+require("serialize");
+require("var");
+
+local next = next;
+
+----------------------------------
+-- Campaigns
+----------------------------------
+
+campaign = {};
+
+function campaign.initialize()
+ AddAlias("alias_campaign_check", "^cp (?:c|ch|che|chec|check)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "campaign.startTargetCapture"
+ );
+
+ AddTriggerEx("trigger_campaign_store_target",
+ "^You still have to kill \\* (?.+?) \\((?.+?)(? - Dead)?\\)$",
+ "", trigger_flag.RegularExpression + trigger_flag.OmitFromOutput + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "campaign.storeTarget", sendto.script
+ );
+
+ AddTriggerEx("trigger_campaign_inactive",
+ "^You are not currently on a campaign\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "campaign.endInactive", sendto.script
+ );
+
+ AddTriggerEx("trigger_campaign_end",
+ "^(?!You)",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary + trigger_flag.OmitFromOutput, custom_colour.NoChange, 0, "",
+ "campaign.endTargetCapture", sendto.script
+ );
+
+ AddTriggerEx("trigger_campaign_update",
+ "^Congratulations, that was one of your CAMPAIGN mobs!$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "campaign.update", sendto.script
+ );
+
+ AddTriggerEx("trigger_campaign_reset",
+ "^CONGRATULATIONS! You have completed your campaign\\.$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "campaign.reset", sendto.script
+ );
+
+ campaign.targets = {};
+ campaign.currentTargetIndex = nil;
+end
+
+function campaign.startTargetCapture()
+ campaign.reset();
+
+ EnableTrigger("trigger_campaign_store_target", true);
+ EnableTrigger("trigger_campaign_inactive", true);
+
+ Execute("campaign check");
+end
+
+function campaign.storeTarget(name, line, wildcards)
+ EnableTrigger("trigger_campaign_end", true);
+ EnableTrigger("trigger_campaign_inactive", false);
+
+ local target = mobTarget:new{
+ name = wildcards.mob_name:lower(),
+ location = wildcards.location,
+ isDead = wildcards.is_dead ~= "" and true or false,
+ amount = 1
+ };
+
+ target:initialize();
+
+ table.insert(campaign.targets, target);
+end
+
+function campaign.endTargetCapture()
+ EnableTrigger("trigger_campaign_store_target", false);
+ EnableTrigger("trigger_campaign_end", false);
+
+ table.sort(campaign.targets, campaign.sort);
+ display.printTargets(campaign.targets, "CP");
+end
+
+function campaign.endInactive()
+ EnableTrigger("trigger_campaign_store_target", false);
+ EnableTrigger("trigger_campaign_inactive", false);
+ campaign.reset();
+ display.printTargets(nil, "CP");
+end
+
+function campaign.update()
+ local targets = campaign.targets;
+ local index = campaign.currentTargetIndex;
+
+ if (next(targets)) then
+ if (index and targets[index]) then
+ local target = targets[index];
+
+ local playerEnemy = utility.gmcp.getPlayerEnemy();
+ local playerZone = utility.gmcp.getPlayerZone();
+
+ if (target.name == playerEnemy or target.rooms[1].zone_name == playerZone) then
+ table.remove(targets, index);
+
+ roomHandler.reset();
+ display.printTargets(targets, "CP");
+ else
+ campaign.startTargetCapture();
+ end
+ else
+ campaign.startTargetCapture();
+ end
+ end
+end
+
+function campaign.sort(mob1, mob2)
+ local isMob1Dead = mob1.isDead and 1 or 0;
+ local isMob2Dead = mob2.isDead and 1 or 0;
+
+ local mob1Zone = mob1.rooms[1].zone_name:lower();
+ local mob2Zone = mob2.rooms[1].zone_name:lower();
+
+ if (isMob1Dead == isMob2Dead and mob1Zone == mob2Zone) then
+ return mob1.name < mob2.name;
+ elseif (isMob1Dead == isMob2Dead) then
+ return mob1Zone < mob2Zone;
+ else
+ return isMob1Dead < isMob2Dead;
+ end
+end
+
+function campaign.reset()
+ campaign.targets = {};
+ campaign.currentTargetIndex = nil;
+ roomHandler.reset();
+end
+
+----------------------------------
+-- Global Quests
+----------------------------------
+
+globalQuest = {};
+
+function globalQuest.initialize()
+ AddAlias("alias_global_quest_check", "^gq (?:c|ch|che|chec|check)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "globalQuest.startTargetCapture"
+ );
+
+ AddTriggerEx("trigger_global_quest_store_target",
+ "^You still have to kill (?\\d+) \\* (?.+?) \\((?.+?)\\)$",
+ "", trigger_flag.RegularExpression + trigger_flag.OmitFromOutput + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.storeTarget", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_end",
+ "^(?!You)",
+ "", trigger_flag.RegularExpression + trigger_flag.OmitFromOutput + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.endTargetCapture", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_inactive",
+ "^(?:" ..
+ "You are not (in|on) a global quest\\." ..
+ "|" ..
+ "The global quest has not yet started\\." ..
+ "|" ..
+ "No global quest is currently being run\\." ..
+ ")$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.endInactive", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_update",
+ "^Congratulations, that was one of the GLOBAL QUEST mobs!$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.update", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_reset1",
+ "^Global Quest: Global quest # \\d+? \\(extended\\) is now over\\.$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.reset", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_reset2",
+ "^Global Quest: (?.+?) has completed global quest # \\d+?\\.$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.reset", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_reset3",
+ "^Global Quest: Global quest # \\d+? has been won by (?.+?) \\- \\d+?(?:nd|st|rd|th) win\\.$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.reset", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_started",
+ "^Global Quest: Global quest # \\d+? for levels \\d+? to \\d+? has now started\\.$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.startTargetCapture", sendto.script
+ );
+
+ AddTriggerEx("trigger_global_quest_alert",
+ "^Global Quest: Global quest # \\d+? has been declared for levels (?\\d+?) to (?\\d+?)\\.$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "globalQuest.alert", sendto.script
+ );
+
+ globalQuest.targets = {};
+ globalQuest.currentTargetIndex = nil;
+end
+
+function globalQuest.startTargetCapture()
+ globalQuest.reset();
+
+ EnableTrigger("trigger_global_quest_store_target", true);
+ EnableTrigger("trigger_global_quest_inactive", true);
+
+ Execute("gquest check");
+end
+
+function globalQuest.storeTarget(name, line, wildcards)
+ EnableTrigger("trigger_global_quest_end", true);
+ EnableTrigger("trigger_global_quest_inactive", false);
+
+ local target = mobTarget:new{
+ name = wildcards.mob_name:lower(),
+ location = wildcards.location,
+ amount = tonumber(wildcards.amount);
+ };
+
+ target:initialize();
+
+ table.insert(globalQuest.targets, target);
+end
+
+function globalQuest.endTargetCapture()
+ EnableTrigger("trigger_global_quest_store_target", false);
+ EnableTrigger("trigger_global_quest_end", false);
+
+ table.sort(globalQuest.targets, globalQuest.sort);
+ display.printTargets(globalQuest.targets, "GQ");
+end
+
+function globalQuest.endInactive()
+ EnableTrigger("trigger_global_quest_store_target", false);
+ EnableTrigger("trigger_global_quest_inactive", false);
+ globalQuest.reset();
+ display.printTargets(nil, "GQ");
+end
+
+function globalQuest.update()
+ local targets = globalQuest.targets;
+ local index = globalQuest.currentTargetIndex;
+
+ if (next(globalQuest.targets)) then
+ if (index and targets[index]) then
+ local target = targets[index];
+
+ local playerEnemy = utility.gmcp.getPlayerEnemy();
+ local playerZone = utility.gmcp.getPlayerZone();
+
+ if (target.name == playerEnemy or target.rooms[1].zone_name == playerZone) then
+ if (target.amount > 1) then
+ target.amount = target.amount - 1;
+ else
+ table.remove(targets, index);
+ end
+
+ roomHandler.reset();
+ display.printTargets(targets, "GQ");
+ else
+ globalQuest.startTargetCapture();
+ end
+ else
+ globalQuest.startTargetCapture();
+ end
+ end
+end
+
+function globalQuest.sort(mob1, mob2)
+ local mob1Priority = mob1.rooms[1].mob_priority or 0;
+ local mob2Priority = mob2.rooms[1].mob_priority or 0;
+
+ local mob1Zone = mob1.rooms[1].zone_name:lower();
+ local mob2Zone = mob2.rooms[1].zone_name:lower();
+
+ if (mob1Priority == mob2Priority) then
+ return mob1Zone < mob2Zone;
+ else
+ return mob1Priority > mob2Priority;
+ end
+end
+
+function globalQuest.reset(name, line, wildcards)
+ local playerName = utility.gmcp.getPlayerName();
+
+ if (wildcards and wildcards.player_name and wildcards.player_name ~= playerName) then
+ return;
+ end
+
+ globalQuest.targets = {};
+ globalQuest.currentTargetIndex = nil;
+ roomHandler.reset();
+end
+
+function globalQuest.alert(name, line, wildcards)
+ local minLvl = tonumber(wildcards.minLvl);
+ local maxLvl = tonumber(wildcards.maxLvl);
+ local playerLevel = utility.gmcp.getPlayerLevel();
+
+ if (not quest.isMuted and playerLevel >= minLvl and playerLevel <= maxLvl) then
+ Sound(GetInfo(66) .. "\\sounds\\global_quest.wav");
+ end
+end
+
+----------------------------------
+-- Room Handler
+----------------------------------
+
+roomHandler = {};
+
+function roomHandler.initialize()
+ AddAlias("alias_room_handler_run_to_area", "^xrt (?[a-zA-Z\\d ]+?)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomHandler.runToArea"
+ );
+
+ AddAlias("alias_room_handler_select_cp_target", "^x?cp (?\\d+)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomHandler.selectCampaignTarget"
+ );
+
+ AddAlias("alias_room_handler_select_gq_target", "^x?gq (?\\d+)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomHandler.selectGlobalQuestTarget"
+ );
+
+ AddAlias("alias_room_handler_select_room", "^x?go(?: (?\\d+?))?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomHandler.selectRoom"
+ );
+
+ AddAlias("alias_room_handler_select_next_room", "^x?(?:next|nex|nx)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomHandler.selectNextRoom"
+ );
+
+ AddAlias("alias_room_handler_select_previous_room", "^x?(?:prev|pre|pr|nx-)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomHandler.selectPreviousRoom"
+ );
+
+ roomHandler.rooms = {};
+ roomHandler.index = nil;
+end
+
+function roomHandler.runToArea(name, line, wildcards)
+ local location = wildcards.location;
+
+ local sql = [[
+ SELECT room_id
+ FROM area
+ WHERE zone_name = %s
+ LIMIT 1;
+ ]];
+
+ sql = sql:format(fixsql(location));
+
+ local room = database.mobber:gettable(sql);
+
+ if (not next(room)) then
+ sql = [[
+ SELECT room_id
+ FROM area
+ WHERE zone_name LIKE %s
+ OR area_name LIKE %s
+ LIMIT 1;
+ ]];
+
+ local wildcardLocation = fixsql(location, "both");
+
+ sql = sql:format(wildcardLocation, wildcardLocation);
+
+ room = database.mobber:gettable(sql);
+ end
+
+ if (next(room)) then
+ Execute("mapper goto " .. room[1].room_id);
+ else
+ local AYLOR_ROOM_ID = 32418;
+ Execute("mapper goto " .. AYLOR_ROOM_ID .. ";runto " .. location);
+ end
+end
+
+function roomHandler.selectCampaignTarget(name, line, wildcards)
+ local targets = campaign.targets;
+
+ if (next(targets)) then
+ local index = tonumber(wildcards.index);
+ local target = targets[index];
+
+ if (target) then
+ campaign.currentTargetIndex = index;
+
+ roomHandler.processTarget(target)
+ else
+ utility.print("That campaign target does not exist!");
+ end
+ else
+ utility.print("No campaign targets processed!");
+ end
+end
+
+function roomHandler.selectGlobalQuestTarget(name, line, wildcards)
+ local targets = globalQuest.targets;
+
+ if (next(targets)) then
+ local index = tonumber(wildcards.index);
+ local target = targets[index];
+
+ if (target) then
+ globalQuest.currentTargetIndex = index;
+
+ roomHandler.processTarget(target)
+ else
+ utility.print("That global quest target does not exist!");
+ end
+ else
+ utility.print("No global quest targets processed!");
+ end
+end
+
+function roomHandler.processTarget(target)
+ utility.setTarget(target.name, target.keyword);
+
+ roomHandler.rooms = {};
+
+ if (target.lookupType == "mobMatchInLvlRange" or target.lookupType == "roomMatchInLvlRange"
+ or target.lookupType == "mobMatchOutOfLvlRange" or target.lookupType == "roomMatchOutOfLvlRange") then
+ roomHandler.rooms = target.rooms;
+ display.printRooms(roomHandler.rooms);
+ elseif (target.lookupType == "areaMatch") then
+ local zoneName = target.rooms[1].zone_name;
+ local playerZone = utility.gmcp.getPlayerZone();
+
+ if (playerZone ~= zoneName) then
+ local roomId = target.rooms[1].room_id;
+
+ if (utility.runToRoomId(roomId)) then
+ Execute("ht " .. target.keyword);
+ end
+ else
+ Execute("ht " .. target.keyword);
+ end
+ else
+ local AYLOR_RECALL_ROOM_ID = 32418;
+
+ if (utility.runToRoomId(AYLOR_RECALL_ROOM_ID)) then
+ local zoneName = target.rooms[1].zone_name;
+
+ Execute("runto " .. zoneName);
+ end
+ end
+end
+
+function roomHandler.selectRoom(name, line, wildcards)
+ local rooms = roomHandler.rooms;
+
+ if (next(rooms)) then
+ local index = tonumber(wildcards.index) or 1;
+ local room = rooms[index];
+
+ if (room) then
+ roomHandler.index = index;
+ utility.runToRoomId(room.room_id);
+ quickScan.scan("", "", {mob_name = ""});
+ else
+ utility.print("No more rooms!");
+ end
+ else
+ utility.print("No room list exists.");
+ end
+end
+
+function roomHandler.selectNextRoom()
+ local rooms = roomHandler.rooms;
+
+ if (next(rooms)) then
+ local index = roomHandler.index or 1;
+ local room = rooms[index];
+
+ if (room and index < #rooms) then
+ if (room.room_id == utility.gmcp.getPlayerRoomId()) then
+ index = index + 1;
+ roomHandler.index = index;
+ room = rooms[index];
+ end
+
+ utility.runToRoomId(room.room_id);
+ quickScan.scan("", "", {mob_name = ""});
+ else
+ utility.print("No more rooms!");
+ end
+ else
+ utility.print("No room list exists.");
+ end
+end
+
+function roomHandler.selectPreviousRoom()
+ local rooms = roomHandler.rooms;
+
+ if (next(rooms)) then
+ local index = roomHandler.index or 1;
+ local room = rooms[index];
+
+ if (room and index > 1 and index <= #rooms) then
+ if (room.room_id == utility.gmcp.getPlayerRoomId()) then
+ index = index - 1;
+ roomHandler.index = index;
+ room = rooms[index];
+ end
+
+ utility.runToRoomId(room.room_id);
+ quickScan.scan("", "", {mob_name = ""});
+ else
+ utility.print("No more rooms!");
+ end
+ else
+ utility.print("No room list exists.");
+ end
+end
+
+function roomHandler.reset()
+ roomHandler.index = nil;
+ roomHandler.rooms = {};
+end
+
+----------------------------------
+-- Mob Search
+----------------------------------
+
+mobSearch = {};
+
+function mobSearch.initialize()
+ AddAlias("alias_mob_search_find", "^mf(?: (?.+?)(?: (?:zone|area) (?.+?))?)?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "mobSearch.findMob"
+ );
+
+ AddAlias("alias_mob_search_find_all", "^mfa (?.+?)(?: (?:lvl|level) (?\\d+?) (?\\d+?))?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "mobSearch.findAllMob"
+ );
+
+ mobSearch.MIN_LVL = 1;
+ mobSearch.MAX_LVL = 220;
+end
+
+function mobSearch.findMob(name, line, wildcards)
+ local mobName = wildcards.mob_name ~= "" and wildcards.mob_name or "%";
+ local zoneName = wildcards.zone_name ~= "" and wildcards.zone_name or utility.gmcp.getPlayerZone();
+
+ roomHandler.reset();
+
+ if (zoneName ~= "") then
+ local sql;
+
+ if (mobName ~= "%") then
+ sql = [[
+ SELECT *
+ FROM mob
+ WHERE zone_name = %s
+ AND mob_name LIKE %s
+ ORDER BY mob_name ASC, room_id ASC;
+ ]];
+ else
+ sql = [[
+ SELECT DISTINCT mob.mob_name, COUNT(mob_name) AS room_count, mob.mob_priority, mob.zone_name
+ FROM mob
+ INNER JOIN area ON mob.zone_name = area.zone_name
+ WHERE mob.zone_name = %s
+ AND mob.mob_name LIKE %s
+ GROUP BY mob_name, mob.zone_name
+ ORDER BY mob.mob_name;
+ ]];
+
+ end
+
+ sql = sql:format(fixsql(zoneName), fixsql(mobName, "both"));
+
+ local results = database.mobber:gettable(sql);
+
+ utility.setTarget(mobName, mobName);
+
+ if (next(results)) then
+ if (mobName ~= "%") then
+ roomHandler.rooms = results;
+ display.printMobRooms(roomHandler.rooms);
+ else
+ display.printMobs(results);
+ end
+ else
+ utility.print("Found 0 mobs in " .. zoneName .. " with the name: " .. mobName, "yellow");
+ end
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+end
+
+function mobSearch.findAllMob(name, line, wildcards)
+ local mobName = wildcards.mob_name;
+
+ local minLvl = tonumber(wildcards.min_level) or mobSearch.MIN_LVL;
+ local maxLvl = tonumber(wildcards.max_level) or mobSearch.MAX_LVL;
+
+ roomHandler.reset();
+
+ local sql = [[
+ SELECT DISTINCT mob.mob_name, COUNT(mob_name) AS room_count, mob.mob_priority, mob.zone_name
+ FROM mob
+ INNER JOIN area ON mob.zone_name = area.zone_name
+ WHERE area.min_lvl >= %d
+ AND area.max_lvl <= %d
+ AND mob.mob_name LIKE %s
+ GROUP BY mob_name, mob.zone_name
+ ORDER BY mob.zone_name;
+ ]];
+
+ sql = sql:format(minLvl, maxLvl, fixsql(mobName, "both"));
+
+ local mobs = database.mobber:gettable(sql);
+
+ utility.setTarget(mobName, mobName);
+
+ if (next(mobs)) then
+ display.printMobs(mobs);
+ else
+ utility.print("Found 0 mobs (L" .. minLvl .. "-" .. maxLvl .. ") with the name: " .. mobName, "yellow");
+ end
+end
+
+----------------------------------
+-- Room Search
+----------------------------------
+
+roomSearch = {};
+
+function roomSearch.initialize()
+ AddAlias("alias_room_search_zone", "^(?:rf|xm)(?: (?.+?)(?: (?:zone|area) (?.+?))?)?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomSearch.searchZone"
+ );
+
+ AddAlias("alias_room_search_all", "^(?:rfa|xma) (?.+?)(?: (?:lvl|level) (?\\d+?) (?\\d+?))?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "roomSearch.searchAll"
+ );
+
+ roomSearch.MIN_LVL = 1;
+ roomSearch.MAX_LVL = 220;
+end
+
+function roomSearch.searchZone(name, line, wildcards)
+ local roomName = wildcards.room_name;
+ local zoneName = wildcards.zone_name ~= "" and wildcards.zone_name or utility.gmcp.getPlayerZone();
+
+ roomHandler.reset();
+
+ if (zoneName ~= "") then
+ local sql = [[
+ SELECT room_name, room_id, zone_name
+ FROM room
+ WHERE zone_name = %s
+ AND room_name LIKE %s
+ ORDER BY room_id ASC;
+ ]];
+
+ sql = sql:format(fixsql(zoneName), fixsql(roomName, "both"));
+
+ local rooms = database.mobber:gettable(sql);
+
+ if (next(rooms)) then
+ roomHandler.rooms = rooms;
+ display.printRooms(rooms);
+ else
+ utility.print("Found 0 rooms in " .. zoneName .. " with the name: " .. roomName, "yellow");
+ end
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+end
+
+function roomSearch.searchAll(name, line, wildcards)
+ local roomName = wildcards.room_name;
+
+ local minLvl = tonumber(wildcards.min_level) or roomSearch.MIN_LVL;
+ local maxLvl = tonumber(wildcards.max_level) or roomSearch.MAX_LVL;
+
+ roomHandler.reset();
+
+ local sql = [[
+ SELECT room.room_name, room.room_id, room.zone_name
+ FROM room
+ INNER JOIN area ON room.zone_name = area.zone_name
+ WHERE area.min_lvl >= %d
+ AND area.max_lvl <= %d
+ AND room.room_name LIKE %s
+ ORDER BY room.zone_name ASC, room.room_id ASC;
+ ]];
+
+ sql = sql:format(minLvl, maxLvl, fixsql(roomName, "both"));
+
+ local rooms = database.mobber:gettable(sql);
+
+ if (next(rooms)) then
+ roomHandler.rooms = rooms;
+ display.printRooms(rooms);
+ else
+ utility.print("Found 0 rooms (L" .. minLvl .. "-" .. maxLvl .. ") with the name: " .. roomName, "yellow");
+ end
+end
+
+----------------------------------
+-- Display
+----------------------------------
+
+display = {};
+
+function display.printTargets(targets, questType)
+ targets = targets or {};
+
+ local msg = questType == "CP" and 420 or 69;
+ BroadcastPlugin(msg, serialize.save_simple(targets));
+
+ ColourNote(pluginPalette.border, "", string.format("\r\n+%s+", string.rep("-", 49)));
+
+ ColourNote(
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-19s%s%-19s", " ", "Target List", " "),
+ pluginPalette.border, "", "|"
+ );
+
+ ColourNote(pluginPalette.border, "", string.format("+%s+", string.rep("-", 49)));
+
+ if (next(targets)) then
+ for i = 1, #targets do
+ local target = targets[i];
+
+ local mobName = toPascalCase(target.name);
+ local zoneName = toPascalCase(target.rooms[1].zone_name);
+ local amount = target.amount > 1 and target.amount or " ";
+ local lookupType = target.lookupType;
+
+ local color;
+
+ if (target.isDead) then
+ color = pluginPalette.deadMob;
+ elseif (lookupType == "mobMatchInLvlRange" or lookupType == "roomMatchInLvlRange") then
+ color = pluginPalette.foundMobRoom;
+ elseif (lookupType == "areaMatch") then
+ color = pluginPalette.foundArea;
+ elseif (lookupType == "mobMatchOutOfLvlRange" or lookupType == "roomMatchOutOfLvlRange") then
+ color = pluginPalette.roomOrMobAll;
+ else
+ color = pluginPalette.missingAreaRoom;
+ end
+
+ local idxLine = string.format("%3.3s", i .. ".");
+ local mobLine;
+ local zoneLine;
+
+ if (color ~= pluginPalette.missingAreaRoom) then
+ mobLine = string.format(" %-29.28s%s ", mobName, amount);
+ zoneLine = string.format(" %-13.12s", "("..zoneName..") ");
+ else
+ mobLine = string.format(" %-31.30s%14s", zoneName, "Missing ");
+ zoneLine = "";
+ end
+
+ local action = questType == "CP" and "xcp" or "xgq";
+
+ ColourTell(pluginPalette.border, "", "|", pluginPalette.indexNumbering, "", idxLine);
+ Hyperlink(action .. " " .. i, mobLine .. zoneLine, mobName .. " (" .. zoneName .. ")", color, "", false, true);
+ ColourTell(pluginPalette.border, "", "|\r\n");
+ end
+ else
+ ColourNote(
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-17s%s%-17s", " ", "No " .. questType .. " Available", " "),
+ pluginPalette.border, "", "|"
+ );
+ end
+
+ ColourNote(pluginPalette.border, "", string.format("+%s+", string.rep("-", 49)));
+end
+
+function display.printRooms(rooms)
+ ColourNote(pluginPalette.border, "", string.format("\r\n+%s+", string.rep("-", 48)));
+
+ ColourNote(
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-19s%s%-19s", " ", "Rooms List", " "),
+ pluginPalette.border, "", "|"
+ );
+
+ ColourNote(pluginPalette.border, "", string.format("+%s+", string.rep("-", 48)));
+
+ if (next(rooms)) then
+ local zone;
+
+ for i = 1, #rooms do
+ local zoneName = toPascalCase(rooms[i].zone_name);
+ local roomName = toPascalCase(rooms[i].room_name);
+ local roomId = rooms[i].room_id;
+
+ if (zone ~= zoneName) then
+ zone = zoneName;
+
+ if (i ~= 1) then
+ ColourNote(pluginPalette.border, "", string.format("+%s+", string.rep("-", 48)));
+ end
+
+ ColourNote(
+ pluginPalette.border, "", "|",
+ "silver", "", ">> ",
+ "darkgray", "", string.format("%-45s", zoneName),
+ pluginPalette.border, "", "|"
+ );
+ end
+
+ ColourTell(pluginPalette.border, "", "|", pluginPalette.indexNumbering, "", string.format("%3.3s", i .. "."));
+ Hyperlink("mapper goto " .. roomId, string.format(" %-36.35s", roomName), roomName, pluginPalette.rooms, "", false, true);
+ Hyperlink("mapper goto " .. roomId, string.format(" %-7.7s", "("..roomId..")"), roomName, "silver", "", false, true);
+ ColourTell(pluginPalette.border, "", "|\r\n");
+ end
+ else
+ ColourNote(
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-17s%s%-17s", " ", "No Rooms Found", " "),
+ pluginPalette.border, "", "|"
+ );
+ end
+
+ ColourNote(pluginPalette.border, "", string.format("+%s+", string.rep("-", 48)));
+end
+
+function display.printMobs(mobs)
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+",
+ string.rep("-", 10),
+ string.rep("-", 26),
+ string.rep("-", 2),
+ string.rep("-", 2)
+ )
+ );
+
+ ColourNote(
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-10s", "Zone"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format(" %-25s", "Mob Name"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-2s", "Rm"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-2s", "Pr"),
+ pluginPalette.border, "", "|"
+ );
+
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+",
+ string.rep("-", 10),
+ string.rep("-", 26),
+ string.rep("-", 2),
+ string.rep("-", 2)
+ )
+ );
+
+ local zone;
+
+ for k,v in ipairs(mobs) do
+ ColourTell(pluginPalette.border, "", "|");
+
+ Hyperlink("xrt " .. v.zone_name, string.format("%-10.10s", v.zone_name), "Goto: " .. v.zone_name, "teal", "", false, true);
+
+ ColourTell(pluginPalette.border, "", "|");
+
+ Hyperlink("mf " .. v.mob_name .. " zone " .. v.zone_name, string.format(" %-25.24s", v.mob_name), "Show rooms for: " .. v.mob_name, "paleturquoise", "", false, true);
+
+ ColourTell(
+ pluginPalette.border, "", "|",
+ "yellow", "", string.format("%-2.2s", v.room_count),
+ pluginPalette.border, "", "|",
+ "green", "", string.format("%-2.2s", v.mob_priority),
+ pluginPalette.border, "", "|\r\n"
+ );
+ end
+
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+",
+ string.rep("-", 10),
+ string.rep("-", 26),
+ string.rep("-", 2),
+ string.rep("-", 2)
+ )
+ );
+
+ utility.print("Click a mob to see its rooms.", "silver");
+end
+
+function display.printMobRooms(mobRooms)
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+%s+",
+ string.rep("-", 24),
+ string.rep("-", 29),
+ string.rep("-", 2),
+ string.rep("-", 5),
+ string.rep("-", 10)
+ )
+ );
+
+ ColourNote(
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-24s", "Mob Name"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format(" %-28s", "Room Name"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-2s", "Pr"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-5s", "Room"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-10s", "Zone"),
+ pluginPalette.border, "", "|"
+ );
+
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+%s+",
+ string.rep("-", 24),
+ string.rep("-", 29),
+ string.rep("-", 2),
+ string.rep("-", 5),
+ string.rep("-", 10)
+ )
+ );
+
+ local zone;
+
+ for k,v in ipairs(mobRooms) do
+ ColourTell(pluginPalette.border, "", "|", pluginPalette.indexNumbering, "", string.format("%-3.3s", k .. "."));
+
+ Hyperlink("mapper goto " .. v.room_id, string.format(" %-20.19s", v.mob_name), "Goto: " .. v.mob_name, "lightblue", "", false, true);
+
+ ColourTell(pluginPalette.border, "", "|");
+
+ Hyperlink("mapper goto " .. v.room_id, string.format(" %-28.27s", v.room_name), "Goto: " .. v.room_name, "silver", "", false, true);
+
+ ColourTell(
+ pluginPalette.border, "", "|",
+ "green", "", string.format("%-2.2s", v.mob_priority),
+ pluginPalette.border, "", "|"
+ );
+
+ Hyperlink("mapper goto " .. v.room_id, string.format("%-5.5s", v.room_id), "Goto: " .. v.room_id, "orange", "", false, true);
+
+ ColourTell(pluginPalette.border, "", "|");
+
+ Hyperlink("xrt " .. v.zone_name, string.format("%-10.10s", v.zone_name), "Goto: " .. v.zone_name, "teal", "", false, true);
+
+ ColourTell(
+ pluginPalette.border, "", "|\r\n"
+ );
+ end
+
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+%s+",
+ string.rep("-", 24),
+ string.rep("-", 29),
+ string.rep("-", 2),
+ string.rep("-", 5),
+ string.rep("-", 10)
+ )
+ );
+
+ utility.print("Click a mob to go to its room.", "silver");
+end
+
+function display.printAreas(areas)
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+%s+%s+%s+",
+ string.rep("-", 10),
+ string.rep("-", 34),
+ string.rep("-", 3),
+ string.rep("-", 3),
+ string.rep("-", 5),
+ string.rep("-", 4),
+ string.rep("-", 3)
+ )
+ );
+
+ ColourNote(
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-10s", "Zone Name"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-34s", "Area Name"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-3s", "Min"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-3s", "Max"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-5s", "Rm Id"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-4s", "#Rm"),
+ pluginPalette.border, "", "|",
+ pluginPalette.header, "", string.format("%-3s", "#Mb"),
+ pluginPalette.border, "", "|"
+ );
+
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+%s+%s+%s+",
+ string.rep("-", 10),
+ string.rep("-", 34),
+ string.rep("-", 3),
+ string.rep("-", 3),
+ string.rep("-", 5),
+ string.rep("-", 4),
+ string.rep("-", 3)
+ )
+ );
+
+ for k,v in ipairs(areas) do
+ ColourNote(
+ pluginPalette.border, "", "|",
+ "teal", "", string.format("%-10.10s", v.zone_name),
+ pluginPalette.border, "", "|",
+ "white", "", string.format("%-34.34s", v.area_name),
+ pluginPalette.border, "", "|",
+ "darkgray", "", string.format("%-3.3s", v.min_lvl),
+ pluginPalette.border, "", "|",
+ "darkgray", "", string.format("%-3.3s", v.max_lvl),
+ pluginPalette.border, "", "|",
+ "orange", "", string.format("%-5.5s", v.room_id),
+ pluginPalette.border, "", "|",
+ "yellow", "", string.format("%-4.4s", v.room_count),
+ pluginPalette.border, "", "|",
+ "green", "", string.format("%-3.3s", v.mob_count),
+ pluginPalette.border, "", "|"
+ );
+ end
+
+ ColourNote(
+ pluginPalette.border, "",
+ string.format(
+ "+%s+%s+%s+%s+%s+%s+%s+",
+ string.rep("-", 10),
+ string.rep("-", 34),
+ string.rep("-", 3),
+ string.rep("-", 3),
+ string.rep("-", 5),
+ string.rep("-", 4),
+ string.rep("-", 3)
+ )
+ );
+
+ ColourNote("yellow", "", "* Excludes nomap rooms\r\n", "green", "", "* Unique mob names");
+end
+
+----------------------------------
+-- Quick Scan
+----------------------------------
+
+quickScan = {};
+
+function quickScan.initialize()
+ AddAlias("alias_quick_scan", "^qs(?: (?.+?))?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "quickScan.scan"
+ );
+
+ AddTriggerEx("trigger_quick_scan_tag_start",
+ "^\\{scan\\}$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "quickScan.enableCapture", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_quick_scan_capture",
+ "^\\s{5}-\\s(?:\\([A-Za-z ]+\\)\\s?)*(?.+?)$",
+ "", trigger_flag.RegularExpression + trigger_flag.OmitFromOutput + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "quickScan.highlight", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_quick_scan_tag_end",
+ "^\\{\\/scan\\}$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "quickScan.disableCapture", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_quick_scan_tag_start_gag",
+ "^\\{scan\\}$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.KeepEvaluating + trigger_flag.OmitFromOutput + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "", sendto.script, 99
+ );
+
+ AddTriggerEx("trigger_quick_scan_tag_end_gag",
+ "^\\{\\/scan\\}$",
+ "", trigger_flag.Enabled + trigger_flag.RegularExpression + trigger_flag.KeepEvaluating + trigger_flag.OmitFromOutput + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "", sendto.script, 99
+ );
+end
+
+function quickScan.scan(name, line, wildcards)
+ if (wildcards.mob_name ~= "") then
+ utility.targetName = wildcards.mob_name;
+ utility.targetKeyword = utility.getMobKeyword(wildcards.mob_name);
+ EnableTrigger("trigger_quick_scan_tag_start", true);
+ Execute("scan");
+ else
+ local target = utility.targetKeyword;
+
+ if (target) then
+ EnableTrigger("trigger_quick_scan_tag_start", true);
+ Execute("scan " .. target);
+ else
+ Execute("scan");
+ end
+ end
+end
+
+function quickScan.enableCapture()
+ EnableTrigger("trigger_quick_scan_tag_start", false);
+ EnableTrigger("trigger_quick_scan_capture", true);
+ EnableTrigger("trigger_quick_scan_tag_end", true);
+end
+
+function quickScan.highlight(name, line, wildcards, styles)
+ local mobName = wildcards.mob_name:lower();
+ local target = utility.targetName:lower();
+
+ if (mobName == target) then
+ ColourTell(pluginPalette.highlightTargetParentheses, "", "(");
+ ColourTell(pluginPalette.highlightTarget, "", "TARGET");
+ ColourTell(pluginPalette.highlightTargetParentheses, "", ") ");
+ ColourTell("white", "", toPascalCase(target) .. "\r\n");
+ else
+ for k,v in ipairs(styles) do
+ ColourTell(RGBColourToName(v.textcolour), v.backcolour, v.text);
+ end
+
+ print();
+ end
+end
+
+function quickScan.disableCapture()
+ EnableTrigger("trigger_quick_scan_capture", false);
+ EnableTrigger("trigger_quick_scan_tag_end", false);
+end
+
+----------------------------------
+-- Quick Where
+----------------------------------
+
+quickWhere = {};
+
+function quickWhere.initialize()
+ AddAlias("alias_quick_where", "^qw(?: ((?\\d+?)\\.)?(?.+?))?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "quickWhere.doWhere"
+ );
+
+ AddTriggerEx("trigger_quick_where",
+ "^(?.{30}) (?.+?)$",
+ "", trigger_flag.RegularExpression + trigger_flag.KeepEvaluating + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "quickWhere.matchTarget", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_quick_where_end",
+ "^There is no .+? around here\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "EnableTriggerGroup('trigger_group_quick_where', false)", sendto.script, 99
+ );
+
+ SetTriggerOption("trigger_quick_where", "group", "trigger_group_quick_where");
+ SetTriggerOption("trigger_quick_where_end", "group", "trigger_group_quick_where");
+end
+
+function quickWhere.doWhere(name, line, wildcards)
+ local index = tonumber(wildcards.index) or 1;
+ local targetName = wildcards.target_name:lower();
+
+ if (targetName ~= "") then
+ utility.setTarget(targetName, targetName);
+ else
+ if (utility.targetKeyword) then
+ targetName = utility.targetKeyword;
+ else
+ return utility.print("No target mob has been set to quick where.", "red");
+ end
+ end
+
+ EnableTriggerGroup("trigger_group_quick_where", true);
+
+ Execute("where " .. index .. "." .. targetName);
+ DoAfterSpecial(10, "EnableTriggerGroup('trigger_group_quick_where', false)", 12);
+end
+
+function quickWhere.matchTarget(name, line, wildcards)
+ if (utility.targetName) then
+ local targetName = Trim(wildcards.target_name):lower();
+
+ for token in utility.targetName:gmatch("[^ ]+") do
+ if (string.find(targetName, token, 1, true)) then
+ local roomName = wildcards.room_name;
+ local zoneName = utility.gmcp.getPlayerZone();
+
+ if (zoneName ~= "") then
+ local sql = [[
+ SELECT room_name, room_id, zone_name
+ FROM room
+ WHERE room_name = %s
+ AND zone_name = %s
+ ORDER BY room_id;
+ ]];
+
+ sql = sql:format(fixsql(roomName), fixsql(zoneName));
+
+ local rooms = database.mobber:gettable(sql);
+
+ if (next(rooms)) then
+ roomHandler.rooms = rooms;
+ end
+
+ display.printRooms(rooms);
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+
+ EnableTriggerGroup("trigger_group_quick_where", false);
+
+ break;
+ end
+ end
+ end
+end
+
+----------------------------------
+-- Hunt Trick
+----------------------------------
+
+huntTrick = {};
+
+function huntTrick.initialize()
+ -- Aliases
+
+ AddAlias("alias_hunt_trick_start", "^ht (?:(?\\d+?)\\.)?(?.+)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "huntTrick.startHuntTrick"
+ );
+
+ AddAlias("alias_hunt_trick_stop", "^ht$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "huntTrick.stopHuntTrick"
+ );
+
+ -- Triggers
+
+ AddTriggerEx("trigger_hunt_trick_continue",
+ "^(?:" ..
+ "You are certain that .+? is .+?\\." ..
+ "|" ..
+ "You are almost certain that .+? is (?:north|east|south|west|up|down) from here\\." ..
+ "|" ..
+ "You are confident that .+? passed through here, heading (north|east|south|west|up|down)\\." ..
+ "|" ..
+ "The trail of .+? is confusing, but you're reasonably sure .+? headed (?:north|east|south|west|up|down)\\." ..
+ "|" ..
+ "There are traces of .+? having been here\\. Perhaps they lead (?:north|east|south|west|up|down)\\?" ..
+ "|" ..
+ "You have no idea what you're doing, but maybe .+? left (?:north|east|south|west|up|down)\\?" ..
+ "|" ..
+ "You couldn't find a path to .+? from here\\." ..
+ "|" ..
+ ".+? is here!" ..
+ "|" ..
+ "You have no idea what you're doing, but maybe (.+?) is (north|east|south|west|up|down)\\?" ..
+ ")$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "huntTrick.continueHuntTrick", sendto.script
+ );
+
+ AddTriggerEx("trigger_hunt_trick_complete",
+ "^You seem unable to hunt that target for some reason\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "huntTrick.completeHuntTrick", sendto.script
+ );
+
+ AddTriggerEx("trigger_hunt_trick_stop",
+ "^(?:" ..
+ "You couldn't find a path to .+? from here\\." ..
+ "|" ..
+ "No one in this area by that name\\." ..
+ "|" ..
+ "No one in this area by the name '.+?'\\." ..
+ "|" ..
+ "There is no .+? around here\\." ..
+ "|" ..
+ "Not while you are fighting!" ..
+ ")$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "huntTrick.stopHuntTrick", sendto.script
+ );
+
+ SetTriggerOption("trigger_hunt_trick_continue", "group", "trigger_group_hunt_trick");
+ SetTriggerOption("trigger_hunt_trick_complete", "group", "trigger_group_hunt_trick");
+ SetTriggerOption("trigger_hunt_trick_stop", "group", "trigger_group_hunt_trick");
+
+ huntTrick.index = nil;
+ huntTrick.targetName = nil;
+end
+
+function huntTrick.startHuntTrick(name, line, wildcards)
+ local STATE_ACTIVE = 3;
+
+ if (tonumber(gmcp("char.status.state")) == STATE_ACTIVE) then
+ EnableTriggerGroup("trigger_group_hunt_trick", true);
+
+ huntTrick.index = tonumber(wildcards.index) or 1;
+
+ huntTrick.targetName = wildcards.targetName;
+
+ huntTrick.huntTarget(huntTrick.index, huntTrick.targetName);
+ else
+ ColourNote("white", "", "(", "maroon", "", "Mobber", "white", "", ") You are too busy to hunt.");
+ end
+end
+
+function huntTrick.continueHuntTrick()
+ if (huntTrick.index and huntTrick.targetName) then
+ huntTrick.index = huntTrick.index + 1;
+
+ huntTrick.huntTarget(huntTrick.index, huntTrick.targetName);
+ else
+ huntTrick.stopHuntTrick();
+ end
+end
+
+function huntTrick.completeHuntTrick()
+ huntTrick.stopHuntTrick();
+ Execute("qw " .. huntTrick.index .. "." .. huntTrick.targetName);
+end
+
+function huntTrick.stopHuntTrick()
+ EnableTriggerGroup("trigger_group_hunt_trick", false);
+
+ ColourNote("white", "", "(", "maroon", "", "Mobber", "white", "", ") Hunt-tricking stopped.");
+end
+
+function huntTrick.huntTarget(index, targetName)
+ Execute("hunt " .. index .. "." .. targetName);
+end
+
+----------------------------------
+-- Auto Hunt
+----------------------------------
+
+autoHunt = {};
+
+function autoHunt.initialize()
+ -- Aliases
+
+ AddAlias("alias_auto_hunt_start", "^ah (?.+)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "autoHunt.startHunt"
+ );
+
+ AddAlias("alias_auto_hunt_abort", "^ah$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "autoHunt.stopHunt"
+ );
+
+ -- Triggers
+
+ AddTriggerEx("trigger_auto_hunt_door",
+ "^Magical wards .+? bounce you back\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary + trigger_flag.KeepEvaluating, custom_colour.NoChange, 0, "",
+ "autoHunt.hunt", sendto.script, 99
+ );
+
+ AddTriggerEx("trigger_auto_hunt_continue1",
+ "^You are confident that .+? passed through here, heading (north|east|south|west|up|down)\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "autoHunt.hunt", sendto.script
+ );
+
+ AddTriggerEx("trigger_auto_hunt_continue2",
+ "^The trail of .+? is confusing, but you're reasonably sure .+? headed (north|east|south|west|up|down)\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "autoHunt.hunt", sendto.script
+ );
+
+ AddTriggerEx("trigger_auto_hunt_continue3",
+ "^You are certain that .+? is (north|east|south|west|up|down) from here\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "autoHunt.hunt", sendto.script
+ );
+
+ AddTriggerEx("trigger_auto_hunt_continue4",
+ "^There are traces of .+? having been here\\. Perhaps they lead (north|east|south|west|up|down)\\?",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "autoHunt.hunt", sendto.script
+ );
+
+ AddTriggerEx("trigger_auto_hunt_continue5",
+ "^The trail of .+? is confusing, but you're reasonably sure .+? is (north|east|south|west|up|down)\\.",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "autoHunt.hunt", sendto.script
+ );
+
+ AddTriggerEx("trigger_auto_hunt_continue6",
+ "^You are almost certain that .+? is (north|east|south|west|up|down) from here\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "autoHunt.hunt", sendto.script
+ );
+
+ AddTriggerEx("trigger_auto_hunt_abort",
+ "^(?:" ..
+ "You seem unable to hunt that target for some reason\\." ..
+ "|" ..
+ "You couldn't find a path to .+? from here\\." ..
+ "|" ..
+ ".+? is here!" ..
+ "|" ..
+ "Not while you are fighting!" ..
+ "|" ..
+ "No one in this area by the name '.+?'\\." ..
+ ")$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "autoHunt.stopHunt", sendto.script
+ );
+
+ SetTriggerOption("trigger_auto_hunt_door", "group", "trigger_group_auto_hunt");
+ SetTriggerOption("trigger_auto_hunt_abort", "group", "trigger_group_auto_hunt");
+
+ for i = 1, 6 do
+ SetTriggerOption("trigger_auto_hunt_continue" .. i, "group", "trigger_group_auto_hunt");
+ end
+
+ autoHunt.targetName = nil;
+ autoHunt.lastDirection = nil;
+end
+
+function autoHunt.startHunt(name, line, wildcards)
+ local STATE_ACTIVE = 3;
+
+ if (tonumber(gmcp("char.status.state")) == STATE_ACTIVE) then
+ EnableTriggerGroup("trigger_group_auto_hunt", true);
+
+ autoHunt.targetName = wildcards.targetName;
+
+ SendNoEcho("hunt " .. autoHunt.targetName);
+ else
+ ColourNote("white", "", "(", "maroon", "", "Mobber", "white", "", ") You are too busy to auto-hunt.");
+ end
+end
+
+function autoHunt.hunt(name, line, wildcards)
+ if (name == "trigger_auto_hunt_door") then
+ if (autoHunt.lastDirection) then
+ Execute("open " .. autoHunt.lastDirection);
+ else
+ Execute("hunt " .. autoHunt.targetName);
+ end
+ else
+ autoHunt.lastDirection = wildcards[1];
+
+ Execute(wildcards[1]);
+ SendNoEcho("hunt " .. autoHunt.targetName);
+ end
+end
+
+function autoHunt.stopHunt()
+ EnableTriggerGroup("trigger_group_auto_hunt", false);
+ autoHunt.targetName = nil;
+ autoHunt.lastDirection = nil;
+ ColourNote("white", "", "(", "maroon", "", "Mobber", "white", "", ") Auto-hunt stopped.");
+end
+
+----------------------------------
+-- Autokill
+----------------------------------
+
+autoKill = {};
+
+function autoKill.initialize()
+ AddAlias("alias_auto_kill", "^(?:ak|kk)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "autoKill.kill"
+ );
+
+ AddAlias("alias_auto_kill_set_skill", "^(?:ak|kk) (?.+?)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "autoKill.setSkill"
+ );
+
+ AddAlias("alias_auto_kill_toggle", "^toggle (?:ak|kk)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "autoKill.toggle"
+ );
+
+ autoKill.skill = var.akSkill or "kill";
+ autoKill.isTargetting = var.isTargetting == "true" and true or false;
+end
+
+function autoKill.kill()
+ local target = utility.targetKeyword;
+ local skill = autoKill.skill;
+
+ if (autoKill.isTargetting and target) then
+ skill = skill:gsub("[^;]+", "%1 '" .. target .. "'");
+ end
+
+ Execute(skill);
+end
+
+function autoKill.setSkill(name, line, wildcards)
+ autoKill.skill = wildcards.skill;
+ var.akSkill = wildcards.skill;
+ utility.print("Autokill skill/spell: " .. wildcards.skill);
+end
+
+function autoKill.toggle()
+ if (autoKill.isTargetting) then
+ utility.print("Target Mode: OFF");
+ else
+ utility.print("Target Mode: ON");
+ end
+
+ autoKill.isTargetting = not autoKill.isTargetting;
+ var.isTargetting = autoKill.isTargetting;
+end
+
+----------------------------------
+-- Noexp
+----------------------------------
+
+noexp = {};
+
+function noexp.initialize()
+ AddAlias("alias_noexp_toggle", "^mobber noexp(?: (?\\d+?))?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "noexp.toggle"
+ );
+
+ AddTriggerEx("trigger_noexp_1",
+ "^(?:You may take a campaign at this level\\.|You raise a level! You are now level \\d+?\\.)$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "noexp.enableCampaignAvailableAndCheckTnl", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_noexp_2",
+ "^(?:You will have to level before you can go on another campaign\\.|.+?'Good luck in your campaign!')$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "noexp.disableCampaignAvailableAndTurnOffNoExp", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_noexp_3",
+ "^You receive \\d+? experience points?\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "noexp.checkCampaignTnl", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_noexp_4",
+ "^You will no longer receive experience\\. Happy questing!$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "noexp.enableIsNoExp", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_noexp_5",
+ "^You will now receive experience\\. Happy leveling!$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "noexp.disableIsNoExp", sendto.script, 100
+ );
+
+ for i = 1, 5 do
+ SetTriggerOption("trigger_noexp_" .. i, "group", "trigger_group_noexp");
+ end
+
+ noexp.isNoExpMode = false;
+ noexp.isNoExp = false;
+ noexp.isCampaignAvailable = false;
+ noexp.threshold = tonumber(var.threshold) or 1000;
+end
+
+function noexp.showVars()
+ print("noexp.isNoExpMode", noexp.isNoExpMode);
+ print("noexp.isNoExp", noexp.isNoExp);
+ print("noexp.isCampaignAvailable", noexp.isCampaignAvailable);
+ print("noexp.threshold", noexp.threshold);
+end
+
+function noexp.toggle(name, line, wildcards)
+ noexp.threshold = tonumber(wildcards.threshold) or noexp.threshold;
+ var.threshold = noexp.threshold;
+
+ if (noexp.isNoExpMode) then
+ EnableTriggerGroup("trigger_group_noexp", false);
+ utility.print("NOEXP AUTOMODE OFF", "red");
+ else
+ EnableTriggerGroup("trigger_group_noexp", true);
+ utility.print("NOEXP AUTOMODE ON (THRESHOLD = " .. noexp.threshold .. "xp)", "lime");
+ Execute("cp check");
+ end
+
+ noexp.setNoExp(false);
+
+ noexp.isNoExpMode = not noexp.isNoExpMode;
+end
+
+function noexp.setNoExp(state)
+ noexp.isNoExp = state;
+
+ state = state and "on" or "off";
+
+ Send_GMCP_Packet("config noexp " .. state);
+ utility.print("Setting noexp " .. state, "orange");
+end
+
+function noexp.checkTnl()
+ local tnl = utility.gmcp.getPlayerTnl();
+
+ if (noexp.isNoExp and tnl > noexp.threshold) then
+ noexp.setNoExp(false);
+ elseif (not noexp.isNoExp and tnl < noexp.threshold) then
+ noexp.setNoExp(true);
+ end
+end
+
+function noexp.turnOffNoExp()
+ if (noexp.isNoExp) then
+ noexp.setNoExp(false);
+ end
+end
+
+function noexp.enableCampaignAvailableAndCheckTnl()
+ noexp.isCampaignAvailable = true;
+ DoAfterSpecial(0.2, "noexp.checkTnl()", 12);
+end
+
+function noexp.disableCampaignAvailableAndTurnOffNoExp()
+ noexp.isCampaignAvailable = false;
+ noexp.turnOffNoExp();
+end
+
+function noexp.checkCampaignTnl()
+ if (noexp.isCampaignAvailable) then
+ DoAfterSpecial(0.2, "noexp.checkTnl()", 12);
+ else
+ noexp.turnOffNoExp();
+ end
+end
+
+function noexp.enableIsNoExp()
+ noexp.isNoExp = true;
+end
+
+function noexp.disableIsNoExp()
+ noexp.isNoExp = false;
+end
+
+----------------------------------
+-- Quest
+----------------------------------
+
+quest = {};
+
+function quest.initialize()
+ AddAlias("alias_quest_mute", "^mobber (?mute|unmute) quest$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "quest.toggleMute"
+ );
+
+ AddAlias("alias_quest_request", "^mobber quest$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "quest.request"
+ );
+
+ quest.commPrefix = "@r(@WQuest@r)@w";
+ quest.timer = nil;
+ quest.isMuted = false;
+end
+
+function quest.start(newQuest)
+ if (not quest.isMuted) then
+ local commMsg = quest.commPrefix .. " @Y" .. newQuest.targ .. " @win @R" .. newQuest.room .. " @r(@w" .. newQuest.area .. "@r)@w";
+ CallPlugin("b555825a4a5700c35fa80780", "storeFromOutside", commMsg);
+
+ AddTimer("questReminder", 0, 1, 0, "", timer_flag.Enabled + timer_flag.Temporary, "quest.playSound");
+ quest.timer = os.time();
+ end
+
+ quest.process(newQuest);
+end
+
+function quest.showRequest(newQuest)
+ local mobName = newQuest.targ;
+ local roomName = stripColors(newQuest.room or "");
+ local areaName = newQuest.area;
+
+ if (mobName == "missing") then
+ ColourNote("maroon", "", "(", "white", "", "Quest", "maroon", "", ") ", "red", "", "Quest mob is missing.");
+ elseif (mobName == "killed") then
+ ColourNote("maroon", "", "(", "white", "", "Quest", "maroon", "", ") ", "red", "", "Quest mob is killed.");
+ elseif (mobName and roomName and areaName) then
+ ColourNote("maroon", "", "(", "white", "", "Quest", "maroon", "", ") ", "red", "", "Mob: " .. mobName);
+ ColourNote("maroon", "", "(", "white", "", "Quest", "maroon", "", ") ", "red", "", "Room: " .. roomName);
+ ColourNote("maroon", "", "(", "white", "", "Quest", "maroon", "", ") ", "red", "", "Area: " .. areaName);
+ quest.process(newQuest);
+ else
+ ColourNote("maroon", "", "(", "white", "", "Quest", "maroon", "", ") ", "red", "", "Quest mob not found.");
+ end
+end
+
+function quest.request()
+ Send_GMCP_Packet("request quest");
+end
+
+function quest.fail()
+ if (not quest.isMuted) then
+ local commMsg = quest.commPrefix .. " @WFailed!@w";
+ CallPlugin("b555825a4a5700c35fa80780", "storeFromOutside", commMsg);
+
+ quest.timer = nil;
+ DeleteTimer("questReminder");
+ ColourNote(
+ "maroon", "", "(",
+ "white", "", "Quest",
+ "maroon", "", ") ",
+ "red", "", "You failed the quest!\r\n"
+ );
+ end
+end
+
+function quest.complete(newQuest)
+ if (not quest.isMuted) then
+ local commMsg = quest.commPrefix .. " @WComplete!@w";
+ CallPlugin("b555825a4a5700c35fa80780", "storeFromOutside", commMsg);
+
+ local duration = 0;
+
+ if (quest.timer) then
+ duration = os.difftime(os.time(), quest.timer);
+ duration = formatSeconds(duration);
+ quest.timer = nil;
+ end
+
+ local numQuestPoints = tonumber(newQuest.totqp);
+ local numTriviaPoints = tonumber(newQuest.tp);
+
+ ColourTell(
+ "maroon", "", "(",
+ "white", "", "Quest",
+ "maroon", "", ") ",
+ "cyan", "", numQuestPoints,
+ "red", "", " quest points. Completed in: ",
+ "cyan", "", duration
+ );
+
+ if (numTriviaPoints > 0) then
+ ColourTell(
+ "red", "", " [",
+ "lime", "", numTriviaPoints .. " tp",
+ "red", "", "]\r\n\r\n"
+ );
+ else
+ ColourTell("red", "", "\r\n\r\n");
+ end
+
+ DeleteTimer("questReminder");
+ end
+end
+
+function quest.process(newQuest)
+ local mobName = newQuest.targ;
+ local roomName = stripColors(newQuest.room);
+ local areaName = newQuest.area;
+
+ local mobKeyword = database.lookupMobKeyword(mobName) or utility.getMobKeyword(mobName);
+
+ utility.setTarget(mobName, mobKeyword);
+
+ local sql = [[
+ SELECT mob.room_name, mob.room_id, mob.zone_name
+ FROM mob
+ INNER JOIN area ON mob.zone_name = area.zone_name
+ WHERE mob.mob_name = %s
+ AND mob.room_name = %s
+ AND area.area_name = %s;
+ ]];
+
+ sql = sql:format(fixsql(mobName), fixsql(roomName), fixsql(areaName));
+
+ local rooms = database.mobber:gettable(sql);
+
+ if (not next(rooms)) then
+ sql = [[
+ SELECT room.room_name, room.room_id, room.zone_name
+ FROM room
+ INNER JOIN area ON room.zone_name = area.zone_name
+ WHERE room.room_name = %s
+ AND area.area_name = %s;
+ ]];
+
+ sql = sql:format(fixsql(roomName), fixsql(areaName));
+
+ rooms = database.mobber:gettable(sql);
+ end
+
+ roomHandler.rooms = rooms;
+
+ display.printRooms(rooms);
+end
+
+function quest.playSound()
+ Sound(GetInfo(66) .. "\\sounds\\quest_warning.wav");
+end
+
+function quest.toggleMute(name, line, wildcards)
+ if (wildcards.toggle == "unmute") then
+ quest.isMuted = false;
+ utility.print("Quest messages are now on.", "yellowgreen");
+ else
+ quest.isMuted = true;
+ utility.print("Quest messages are now off.", "indianred");
+ end
+
+ var.isQuestMuted = quest.isMuted;
+end
+
+----------------------------------
+-- Database
+----------------------------------
+
+database = {};
+
+function database.initialize()
+ AddAlias("alias_database_developer", "^mobber dev(?:eloper)?(?: mode)?$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.toggleDeveloperMode"
+ );
+
+ AddAlias("alias_database_backup", "^mobber backup$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.manualBackup"
+ );
+
+ AddAlias("alias_database_vacuum", "^mobber vacuum$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.vacuum"
+ );
+
+ AddAlias("alias_database_consider", "^mobber consider$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.toggleConsiderMobLogging"
+ );
+
+ AddAlias("alias_database_add_area", "^mobber add(?:area|zone) (?\\d+) (?\\d+)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.startAddArea"
+ );
+
+ AddAlias("alias_database_remove_area", "^mobber (?:remove|delete) (?:area|zone)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.removeArea"
+ );
+
+ AddAlias("alias_database_show_areas", "^mobber show (?:areas|zones)(?: sort (?mobs?|rooms?|level))?$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.showAreas"
+ );
+
+ AddAlias("alias_database_show_area", "^mobber show (?:area|zone)(?: (?.+?))?$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.showArea"
+ );
+
+ AddAlias("alias_database_update_rooms", "^mobber update rooms$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.updateRooms"
+ );
+
+ AddAlias("alias_database_update_areas", "^mobber update (?:areas|zones)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.updateAreas"
+ );
+
+ AddAlias("alias_database_update_area_id", "^(?:mobber xset|xset mark)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.updateAreaId"
+ );
+
+ AddAlias("alias_database_update_area_level_range", "^mobber update (?:level|lvl)range (?\\d+) (?\\d+)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.updateAreaLevelRange"
+ );
+
+ AddAlias("alias_database_add_mob", "^mobber addmob (?.+?)(?: priority (?\\d+))?$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.addMob"
+ );
+
+ AddAlias("alias_database_remove_mob_room", "^mobber (?:remove|delete)mob (?:room|here)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.removeMobRoom"
+ );
+
+ AddAlias("alias_database_remove_mob_zone", "^mobber (?:remove|delete)mob (?:zone|area)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.removeMobZone"
+ );
+
+ AddAlias("alias_database_remove_mob_single", "^mobber (?:remove|delete)mob single (?.+?)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.removeMobSingle"
+ );
+
+ AddAlias("alias_database_update_mob_keyword", "^mobber keyword (?.+?) mob (?.+?)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.updateMobKeyword"
+ );
+
+ AddAlias("alias_database_update_mob_keywords", "^mobber update keywords?$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.updateMobKeywords"
+ );
+
+ AddAlias("alias_database_clean_keywords", "^mobber clean keywords?$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.cleanKeywords"
+ );
+
+ AddAlias("alias_database_set_mob_priority", "^mobber priority (?-?\\d+) mob (?.+?)$", "",
+ alias_flag.Enabled + alias_flag.RegularExpression + alias_flag.Temporary,
+ "database.setMobPriority"
+ );
+
+ -- Consider Triggers
+
+ AddTriggerEx("trigger_database_developer_mob_consider1",
+ "^You get .+? gold coins? from the .*?corpse of (?.+?)\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "database.addMob", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_database_developer_mob_consider2",
+ "^(?:\\([A-Za-z ]+\\)\\s?)*You would be completely annihilated by (?.+?)$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "database.addMob", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_database_developer_mob_consider3",
+ "^(?:\\([A-Za-z ]+\\)\\s?)*You would stomp (?.+?) into the ground\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "database.addMob", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_database_developer_mob_consider4",
+ "^(?:\\([A-Za-z ]+\\)\\s?)*No Problem! (?.+?) is weak compared to you\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "database.addMob", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_database_developer_mob_consider5",
+ "^(?:\\([A-Za-z ]+\\)\\s?)*Best run away from (?.+?) while you can!$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "database.addMob", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_database_developer_mob_consider6",
+ "^(?:\\([A-Za-z ]+\\)\\s?)*Challenging (?.+?) would be either very brave or very stupid\\.$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "database.addMob", sendto.script, 100
+ );
+
+ AddTriggerEx("trigger_database_developer_mob_consider7",
+ "^(?:\\([A-Za-z ]+\\)\\s?)*(?.+?) " ..
+ "(?:" ..
+ "would be easy, but is it even worth the work out\\?" ..
+ "|" ..
+ "looks a little worried about the idea\\." ..
+ "|" ..
+ "should be a fair fight!" ..
+ "|" ..
+ "snickers nervously\\." ..
+ "|" ..
+ "chuckles at the thought of you fighting (?:him|her|it)\\." ..
+ "|" ..
+ "would crush you like a bug!" ..
+ "|" ..
+ "would dance on your grave!" ..
+ "|" ..
+ "says 'BEGONE FROM MY SIGHT unworthy!'" ..
+ "|" ..
+ "has divine protection\\." ..
+ ")$",
+ "", trigger_flag.RegularExpression + trigger_flag.Temporary, custom_colour.NoChange, 0, "",
+ "database.addMob", sendto.script, 100
+ );
+
+ for i = 1, 7 do
+ local triggerName = "trigger_database_developer_mob_consider" .. i;
+ SetTriggerOption(triggerName, "group", "trigger_group_add_mob_consider");
+ end
+
+ database.isDeveloperMode = false;
+ database.isConsiderMobLogging = false;
+end
+
+function database.openDatabase()
+ if (not database.mobber) then
+ database.mobber = sqlitedb:new{name = "mobber.db"};
+ end
+ database.mobber:open();
+ database.checkCreateTables();
+ database.checkBackup();
+end
+
+function database.closeDatabase()
+ database.mobber:close();
+ database.mobber = nil;
+end
+
+function database.checkBackup()
+ if (var.dbBackupTimer) then
+ local NUM_SECONDS_DAY = 86400;
+ local duration = math.abs(GetInfo(232) - var.dbBackupTimer);
+
+ if (duration > NUM_SECONDS_DAY) then
+ database.mobber:backup();
+ var.dbBackupTimer = GetInfo(232);
+ else
+ local timeRemaining = formatSeconds(NUM_SECONDS_DAY - duration);
+ utility.print("Next database backup will occur in: " .. timeRemaining, "darkgray");
+ end
+ else
+ var.dbBackupTimer = GetInfo(232);
+ end
+end
+
+function database.manualBackup()
+ if (database.isDeveloperModeEnabled()) then
+ database.mobber:backup();
+ end
+end
+
+function database.vacuum()
+ if (database.isDeveloperModeEnabled()) then
+ database.mobber:vacuum();
+ end
+end
+
+function database.toggleDeveloperMode()
+ if (database.isDeveloperMode) then
+ utility.print("Developer mode disabled.");
+ else
+ utility.print("Developer mode enabled.");
+ end
+
+ database.isDeveloperMode = not database.isDeveloperMode;
+end
+
+function database.isDeveloperModeEnabled()
+ if (not database.isDeveloperMode) then
+ utility.print("Developer mode is disabled.", "red");
+ end
+
+ return database.isDeveloperMode;
+end
+
+function database.toggleConsiderMobLogging()
+ if (database.isConsiderMobLogging) then
+ utility.print("Developer consider mob logging disabled.");
+ EnableTriggerGroup("trigger_group_add_mob_consider", false);
+ else
+ utility.print("Developer consider mob logging enabled.");
+ EnableTriggerGroup("trigger_group_add_mob_consider", true);
+ end
+
+ database.isConsiderMobLogging = not database.isConsiderMobLogging;
+end
+
+function database.lookupMobKeyword(mobName)
+ local mobKeyword;
+
+ local sql = [[
+ SELECT mob_keyword
+ FROM keyword
+ WHERE mob_name = %s
+ LIMIT 1;
+ ]];
+
+ sql = sql:format(fixsql(mobName));
+
+ local results = database.mobber:gettable(sql);
+
+ mobKeyword = next(results) and results[1].mob_keyword or nil;
+
+ return mobKeyword;
+end
+
+function database.lookupMobRoomOrAreaByLvl(location, mobName)
+ local playerLevel = utility.gmcp.getPlayerLevel();
+
+ local sql = [[
+ SELECT mob.room_name, mob.room_id, mob.zone_name, mob.mob_priority
+ FROM mob
+ INNER JOIN area ON mob.zone_name = area.zone_name
+ WHERE mob.mob_name = %s
+ AND (mob.room_name = %s OR area.area_name = %s)
+ AND area.min_lvl - 10 <= %d
+ AND area.max_lvl + 15 >= %d;
+ ]];
+
+ sql = sql:format(fixsql(mobName), fixsql(location), fixsql(location), playerLevel, playerLevel);
+
+ local results = database.mobber:gettable(sql);
+
+ local lookupType = "mobMatchInLvlRange";
+
+ return results, lookupType;
+end
+
+function database.lookupMobRoomOrArea(location, mobName)
+ local playerLevel = utility.gmcp.getPlayerLevel();
+
+ local sql = [[
+ SELECT mob.room_name, mob.room_id, mob.zone_name, mob.mob_priority
+ FROM mob
+ INNER JOIN area ON mob.zone_name = area.zone_name
+ WHERE mob.mob_name = %s
+ AND (mob.room_name = %s OR area.area_name = %s);
+ ]];
+
+ sql = sql:format(fixsql(mobName), fixsql(location), fixsql(location), playerLevel, playerLevel);
+
+ local results = database.mobber:gettable(sql);
+
+ local lookupType = "mobMatchOutOfLvlRange";
+
+ return results, lookupType;
+end
+
+function database.lookupArea(location)
+ local sql = [[
+ SELECT zone_name, room_id
+ FROM area
+ WHERE area_name = %s
+ LIMIT 1;
+ ]];
+
+ sql = sql:format(fixsql(location));
+
+ local results = database.mobber:gettable(sql);
+
+ local lookupType = "areaMatch";
+
+ return results, lookupType;
+end
+
+function database.lookupRoomAreaByLvl(location)
+ local playerLevel = utility.gmcp.getPlayerLevel();
+
+ local sql = [[
+ SELECT room.room_name, room.room_id, room.zone_name
+ FROM room
+ INNER JOIN area ON room.zone_name = area.zone_name
+ WHERE room.room_name = %s
+ AND area.min_lvl - 10 <= %d
+ AND area.max_lvl + 15 >= %d;
+ ]];
+
+ sql = sql:format(fixsql(location), playerLevel, playerLevel);
+
+ local results = database.mobber:gettable(sql);
+
+ local lookupType = "roomMatchInLvlRange";
+
+ return results, lookupType;
+end
+
+function database.lookupRoomArea(location)
+ local sql = [[
+ SELECT room.room_name, room.room_id, room.zone_name
+ FROM room
+ INNER JOIN area ON room.zone_name = area.zone_name
+ WHERE room.room_name = %s;
+ ]];
+
+ sql = sql:format(fixsql(location));
+
+ local results = database.mobber:gettable(sql);
+
+ local lookupType = "roomMatchOutOfLvlRange";
+
+ return results, lookupType;
+end
+
+function database.setMobPriority(name, line, wildcards)
+ if (database.isDeveloperModeEnabled()) then
+ local mobName = wildcards.mob_name;
+ local priority = tonumber(wildcards.priority);
+ local zoneName = utility.gmcp.getPlayerZone();
+
+ if (zoneName ~= "") then
+ local sql = [[
+ UPDATE mob
+ SET mob_priority = %d
+ WHERE zone_name = %s
+ AND mob_name = %s;
+ ]];
+
+ sql = sql:format(priority, fixsql(zoneName), fixsql(mobName));
+
+ database.mobber:exec(sql);
+
+ local numChanges = database.mobber:changes();
+
+ if (numChanges > 0) then
+ utility.print(numChanges .. " instance(s) of " .. mobName .. " had their GQ priority set to: " .. priority, "lime");
+ elseif (numChanges == 0) then
+ utility.print("Mob " .. mobName .. " was not found in " .. zoneName .. ".", "yellow");
+ end
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+ end
+end
+
+function database.updateMobKeyword(name, line, wildcards)
+ if (database.isDeveloperModeEnabled()) then
+ local mobName = wildcards.mob_name;
+ local mobKeyword = wildcards.mobKeyword;
+
+ local sql = [[
+ UPDATE keyword
+ SET mob_keyword = %s
+ WHERE mob_name = %s;
+ ]];
+
+ sql = sql:format(fixsql(mobKeyword), fixsql(mobName));
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ if (numChanges ~= 0) then
+ utility.print("The mob " .. mobName .. " was updated with the keyword " .. mobKeyword .. ".", "lime");
+ else
+ utility.print("The mob " .. mobName .. " was not found and therefore its keyword could not be updated.", "yellow");
+ ColourNote(
+ "yellow", "",
+ "Possible reasons:\r\n\r\n" ..
+ "mob name mispelled\r\n" ..
+ "mob not previously added via the 'mob update keywords' command\r\n" ..
+ "mob not logged to the database"
+ );
+ end
+ end
+end
+
+function database.updateMobKeywords()
+ if (database.isDeveloperModeEnabled()) then
+ local start = GetInfo(232);
+
+ local sql = [[
+ SELECT mob_name FROM mob WHERE mob_name NOT IN (SELECT mob_name FROM keyword);
+ ]];
+
+ local mobNames = database.mobber:gettable(sql);
+
+ if (next(mobNames)) then
+ local sql = [[
+ INSERT OR IGNORE INTO keyword(mob_name, mob_keyword) VALUES(%s, %s);
+ ]];
+
+ database.mobber:exec("BEGIN TRANSACTION;", true);
+
+ local numChanges = 0;
+
+ for i = 1, #mobNames do
+ local mobKeyword = utility.getMobKeyword(mobNames[i].mob_name);
+ local stmt = sql:format(fixsql(mobNames[i].mob_name), fixsql(mobKeyword));
+
+ database.mobber:exec(stmt, true);
+
+ numChanges = numChanges + database.mobber:changes();
+ end
+
+ database.mobber:exec("END TRANSACTION;", true);
+
+ local dif = GetInfo(232) - start;
+
+ utility.print(numChanges .. " mob keyword(s) created.\r\nKeyword update completed in: " .. formatSeconds(dif), "lime");
+ else
+ utility.print("All mob keywords are up-to-date.", "greenyellow");
+ end
+ end
+end
+
+function database.cleanKeywords()
+ if (database.isDeveloperModeEnabled()) then
+ local sql = [[
+ DELETE FROM keyword WHERE mob_name NOT IN (SELECT mob_name FROM mob);
+ ]];
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ if (numChanges ~= 0) then
+ utility.print(numChanges .. " unmatched keyword(s) deleted from the mobber database.", "lime");
+ else
+ utility.print("There are no unmatched keywords in the mobber database!", "greenyellow");
+ end
+ end
+end
+
+function database.removeMobRoom()
+ if (database.isDeveloperModeEnabled()) then
+ local roomId = utility.gmcp.getPlayerRoomId();
+
+ if (roomId) then
+ local sql = [[
+ DELETE FROM mob WHERE room_id = %d;
+ ]];
+
+ sql = sql:format(roomId);
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ utility.print(numChanges .. " mob(s) removed from roomid: " .. roomId, "lime");
+ else
+ utility.print("GMCP error retrieving room id.");
+ end
+ end
+end
+
+function database.removeMobZone()
+ if (database.isDeveloperModeEnabled()) then
+ local zoneName = utility.gmcp.getPlayerZone();
+
+ if (zoneName ~= "") then
+ local sql = [[
+ DELETE FROM mob WHERE zone_name = %s;
+ ]];
+
+ sql = sql:format(fixsql(zoneName));
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ utility.print(numChanges .. " mob(s) removed from zone: " .. zoneName, "lime");
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+ end
+end
+
+function database.removeMobSingle(name, line, wildcards)
+ if (database.isDeveloperModeEnabled()) then
+ local mobName = wildcards.mob_name;
+ local zoneName = utility.gmcp.getPlayerZone();
+
+ if (zoneName ~= "") then
+ local sql = [[
+ DELETE FROM mob WHERE mob_name = %s AND zone_name = %s;
+ ]];
+
+ sql = sql:format(fixsql(mobName), fixsql(zoneName));
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ utility.print(numChanges .. " instance(s) of " .. mobName .. " removed from zone: " .. zoneName, "lime");
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+ end
+end
+
+function database.addMob(name, line, wildcards)
+ if (name ~= "alias_database_add_mob" or database.isDeveloperModeEnabled()) then
+ local mobName = wildcards.mob_name;
+ local mobPriority = tonumber(wildcards.mob_priority) or 0;
+
+ local roomName = utility.gmcp.getPlayerRoomName();
+ local zoneName = utility.gmcp.getPlayerZone();
+ local roomId = utility.gmcp.getPlayerRoomId();
+
+ if (roomName ~= "" and zoneName ~= "" and roomId) then
+ roomName = stripColors(roomName);
+
+ local sql = [[
+ INSERT OR IGNORE INTO mob VALUES(%s, %s, %d, %s, %d);
+ ]];
+
+ sql = sql:format(fixsql(mobName), fixsql(roomName), roomId, fixsql(zoneName), mobPriority);
+
+ local code = database.mobber:exec(sql, false);
+
+ if (code == sqlite3.OK) then
+ local numChanges = database.mobber:changes();
+
+ if (numChanges == 1) then
+ if (name == "alias_database_add_mob") then
+ utility.print(
+ numChanges .. " mob was added to the room.\r\n\r\n" ..
+ "Mob: " .. mobName .. "\r\n" ..
+ "Room: " .. roomName .. "\r\n" ..
+ "RoomId: " .. roomId .. "\r\n" ..
+ "Zone: " .. zoneName .. "\r\n" ..
+ "Priority: " .. mobPriority,
+ "lime"
+ );
+ else
+ utility.print(mobName .. " was logged to the room." ,"lime");
+ end
+ else
+ utility.print(mobName .. " is already logged to room id: " .. roomId, "yellow");
+ end
+ elseif (code == sqlite3.CONSTRAINT) then
+ utility.print("Error: This room and/or zone is missing from the mobber database.", "red");
+ else
+ local err = self.db:errmsg();
+ database.mobber.db:execute("ROLLBACK;");
+ error(err);
+ end
+ else
+ utility.print("GMCP error adding mob.");
+ end
+ end
+end
+
+function database.startAddArea(name, line, wildcards)
+ if (database.isDeveloperModeEnabled()) then
+ if (tonumber(wildcards.min_lvl) > tonumber(wildcards.max_lvl)) then
+ return utility.print("Max level must be greater than or equal to min level.", "yellow");
+ end
+
+ database.area = {};
+ database.area.minLvl = tonumber(wildcards.min_lvl);
+ database.area.maxLvl = tonumber(wildcards.max_lvl);
+ database.area.roomId = utility.gmcp.getPlayerRoomId();
+ database.area.isAddingArea = true;
+
+ utility.gmcp.requestPlayerArea();
+ end
+end
+
+function database.addArea(zoneName, areaName)
+ if (database.area and zoneName ~= "" and areaName ~= "") then
+ local sql = [[
+ INSERT OR IGNORE INTO area VALUES(%s, %s, %d, %d, %d);
+ ]];
+
+ sql = sql:format(fixsql(zoneName), fixsql(areaName), database.area.minLvl, database.area.maxLvl, database.area.roomId);
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ if (numChanges == 1) then
+ utility.print("Added to database: " .. areaName .. " (" .. zoneName .. ") " .. database.area.minLvl .. " to " .. database.area.maxLvl, "lime");
+ elseif (numChanges == 0) then
+ utility.print("This area already exists in the mobber database.", "yellow");
+ end
+ else
+ utility.print("GMCP error adding area.");
+ end
+
+ database.area = nil;
+end
+
+function database.removeArea()
+ if (database.isDeveloperModeEnabled()) then
+ local zoneName = utility.gmcp.getPlayerZone();
+
+ if (zoneName ~= "") then
+ local start = GetInfo(232);
+
+ local sql = [[
+ DELETE FROM area WHERE zone_name = %s;
+ ]];
+
+ sql = sql:format(fixsql(zoneName));
+
+ database.mobber:exec(sql, true);
+
+ local dif = GetInfo(232) - start;
+
+ local numChanges = database.mobber:changes();
+
+ if (numChanges == 1) then
+ utility.print(zoneName .. " was removed from the mobber database.\r\nDelete operation completed in: " .. formatSeconds(dif), "lime");
+ elseif (numChanges == 0) then
+ utility.print(zoneName .. " does not exist in the mobber database!", "yellow");
+ end
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+ end
+end
+
+function database.showAreas(name, line, wildcards)
+ local order = wildcards.order;
+
+ if (order == "mob" or order == "mobs") then
+ order = "mob_count";
+ elseif (order == "room" or order == "rooms") then
+ order = "room_count";
+ elseif (order == "level") then
+ order = "min_lvl ASC, max_lvl";
+ else
+ order = "zone_name";
+ end
+
+ local sql = [[
+ SELECT * FROM
+ (
+ SELECT area.zone_name, area.area_name, area.min_lvl, area.max_lvl, area.room_id, COUNT(room.room_id) AS room_count
+ FROM area
+ LEFT JOIN room USING(zone_name)
+ GROUP BY zone_name
+ )
+ JOIN
+ (
+ SELECT area.zone_name, COUNT(DISTINCT mob.mob_name) AS mob_count
+ FROM area
+ LEFT JOIN mob USING(zone_name)
+ GROUP BY zone_name
+ )
+ USING (zone_name)
+ ORDER BY %s ASC;
+ ]];
+
+ sql = sql:format(order);
+
+ local areas = database.mobber:gettable(sql);
+
+ if (next(areas)) then
+ display.printAreas(areas);
+ end
+
+ utility.print("Found " .. #areas .. " area(s) in the mobber database.", "lime");
+end
+
+function database.showArea(name, line, wildcards)
+ local zoneName = wildcards.zone_name ~= "" and wildcards.zone_name or utility.gmcp.getPlayerZone();
+
+ if (zoneName ~= "") then
+ local sql = [[
+ SELECT * FROM
+ (
+ SELECT area.zone_name, area.area_name, area.min_lvl, area.max_lvl, area.room_id, COUNT(room.room_id) AS room_count
+ FROM area
+ LEFT JOIN room USING(zone_name)
+ WHERE zone_name = %s
+ GROUP BY zone_name
+ )
+ JOIN
+ (
+ SELECT area.zone_name, COUNT(DISTINCT mob.mob_name) AS mob_count
+ FROM area
+ LEFT JOIN mob USING(zone_name)
+ WHERE zone_name = %s
+ GROUP BY zone_name
+ )
+ USING (zone_name);
+ ]];
+
+ sql = sql:format(fixsql(zoneName), fixsql(zoneName));
+
+ local areas = database.mobber:gettable(sql);
+
+ if (next(areas)) then
+ display.printAreas(areas);
+ end
+
+ utility.print("Found " .. #areas .. " area(s) in the mobber database with the name: " .. zoneName, "lime");
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+end
+
+function database.updateAreaId()
+ if (database.isDeveloperModeEnabled()) then
+ local roomId = utility.gmcp.getPlayerRoomId();
+ local zoneName = utility.gmcp.getPlayerZone();
+
+ if (roomId and zoneName ~= "") then
+ local sql = [[
+ UPDATE area
+ SET room_id = %d
+ WHERE zone_name = %s;
+ ]];
+
+ sql = sql:format(roomId, fixsql(zoneName));
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ if (numChanges == 1) then
+ utility.print(zoneName .. " roomid set to: " .. roomId, "lime");
+ elseif (numChanges == 0) then
+ utility.print(
+ zoneName .. " was not found in the mobber database.\r\n" ..
+ "Possible solutions:\r\n" ..
+ "Add area to the database with the 'mobber addarea ' command.",
+ "red"
+ );
+ end
+ else
+ utility.print("GMCP error updating zone id.");
+ end
+ end
+end
+
+function database.updateAreaLevelRange(name, line, wildcards)
+ if (database.isDeveloperModeEnabled()) then
+ local minLvl = tonumber(wildcards.min_lvl);
+ local maxLvl = tonumber(wildcards.max_lvl);
+
+ if (minLvl > maxLvl) then
+ return utility.print("Max level must be greater than or equal to min level.", "yellow");
+ end
+
+ local zoneName = utility.gmcp.getPlayerZone();
+
+ if (zoneName ~= "") then
+ local sql = [[
+ UPDATE area
+ SET min_lvl = %d, max_lvl = %d
+ WHERE zone_name = %s;
+ ]];
+
+ sql = sql:format(minLvl, maxLvl, fixsql(zoneName));
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ if (numChanges == 1) then
+ utility.print(zoneName .. " level range set to: " .. minLvl .. " - " .. maxLvl .. ".", "lime");
+ elseif (numChanges == 0) then
+ utility.print("The zone " .. zoneName .. " was not found in the mobber database.", "yellow");
+ end
+ else
+ utility.print("GMCP error retrieving zone.");
+ end
+ end
+end
+
+function database.updateRooms()
+ if (database.isDeveloperModeEnabled()) then
+ local start = GetInfo(232);
+
+ local sql = [[
+ ATTACH DATABASE %s AS 'aardwolf';
+ ]];
+
+ sql = sql:format(fixsql(GetInfo(68) .. "Aardwolf.db"));
+
+ database.mobber:exec(sql, true);
+
+ sql = [[
+ INSERT OR IGNORE INTO room('room_name', 'room_id', 'zone_name')
+ SELECT name, CAST(uid AS INTEGER), area
+ FROM aardwolf.rooms
+ WHERE EXISTS (SELECT * FROM area WHERE aardwolf.rooms.area = area.zone_name);
+ ]];
+
+ database.mobber:exec(sql, true);
+
+ local numChanges = database.mobber:changes();
+
+ sql = [[
+ DETACH DATABASE 'aardwolf';
+ ]];
+
+ database.mobber:exec(sql, true);
+
+ local dif = GetInfo(232) - start;
+
+ utility.print(numChanges .. " room(s) imported from Aardwolf.db\r\nRoom update completed in: " .. formatSeconds(dif), "lime");
+ end
+end
+
+function database.checkCreateTables()
+ local sql = [[
+ CREATE TABLE IF NOT EXISTS `area` (
+ `zone_name` TEXT NOT NULL UNIQUE,
+ `area_name` TEXT NOT NULL,
+ `min_lvl` INTEGER NOT NULL,
+ `max_lvl` INTEGER NOT NULL,
+ `room_id` INTEGER NOT NULL UNIQUE,
+ PRIMARY KEY(`zone_name`)
+ );
+
+ CREATE TABLE IF NOT EXISTS `room` (
+ `room_name` TEXT NOT NULL,
+ `room_id` INTEGER NOT NULL UNIQUE,
+ `zone_name` TEXT NOT NULL,
+ FOREIGN KEY(`zone_name`) REFERENCES `area`(`zone_name`) ON DELETE CASCADE,
+ PRIMARY KEY(`room_id`)
+ );
+
+ CREATE TABLE IF NOT EXISTS `mob` (
+ `mob_name` TEXT NOT NULL COLLATE NOCASE,
+ `room_name` TEXT NOT NULL,
+ `room_id` INTEGER NOT NULL,
+ `zone_name` TEXT NOT NULL,
+ `mob_priority` INTEGER NOT NULL,
+ FOREIGN KEY(`room_id`) REFERENCES `room`(`room_id`) ON DELETE CASCADE,
+ FOREIGN KEY(`zone_name`) REFERENCES `area`(`zone_name`) ON DELETE CASCADE,
+ PRIMARY KEY(`mob_name`,`room_id`)
+ );
+
+ CREATE TABLE IF NOT EXISTS `keyword` (
+ `mob_name` TEXT NOT NULL UNIQUE COLLATE NOCASE,
+ `mob_keyword` TEXT NOT NULL,
+ PRIMARY KEY(`mob_name`)
+ );
+
+ CREATE INDEX IF NOT EXISTS `idx_area_an_zn_rid` ON `area` (
+ `area_name`,
+ `zone_name`,
+ `room_id`
+ );
+
+ CREATE INDEX IF NOT EXISTS `idx_room_rn_zn_rid` ON `room` (
+ `room_name`,
+ `zone_name`,
+ `room_id`
+ );
+ ]];
+
+ database.mobber:exec(sql);
+end
+
+function database.updateAreas()
+ if (database.isDeveloperModeEnabled()) then
+ local start = GetInfo(232);
+
+ local zones = {
+ ["aardington"] = { area_name = "Aardington Estate", room_id = 47509, min_lvl = 70, max_lvl = 90 },
+ ["abend"] = { area_name = "The Dark Continent, Abend", room_id = 24909, min_lvl = 1, max_lvl = 201 },
+ ["academy"] = { area_name = "The Aylorian Academy", room_id = 35233, min_lvl = 1, max_lvl = 15 },
+ ["adaldar"] = { area_name = "Battlefields of Adaldar", room_id = 34400, min_lvl = 150, max_lvl = 175 },
+ ["afterglow"] = { area_name = "Afterglow", room_id = 38134, min_lvl = 190, max_lvl = 201 },
+ ["agroth"] = { area_name = "The Marshlands of Agroth", room_id = 11027, min_lvl = 105, max_lvl = 125 },
+ ["ahner"] = { area_name = "Kingdom of Ahner", room_id = 30129, min_lvl = 20, max_lvl = 50 },
+ ["alagh"] = { area_name = "Alagh, the Blood Lands", room_id = 3224, min_lvl = 1, max_lvl = 201 },
+ ["alehouse"] = { area_name = "Wayward Alehouse", room_id = 885, min_lvl = 75, max_lvl = 90 },
+ ["amazon"] = { area_name = "The Amazon Nation", room_id = 1409, min_lvl = 120, max_lvl = 201 },
+ ["amazonclan"] = { area_name = "The Ivory City", room_id = 34212, min_lvl = 210, max_lvl = 210 },
+ ["amusement"] = { area_name = "The Amusement Park", room_id = 29282, min_lvl = 1, max_lvl = 20 },
+ ["andarin"] = { area_name = "The Blighted Tundra of Andarin", room_id = 2399, min_lvl = 35, max_lvl = 60 },
+ ["annwn"] = { area_name = "Annwn", room_id = 28963, min_lvl = 160, max_lvl = 186 },
+ ["anthrox"] = { area_name = "Anthrox", room_id = 3993, min_lvl = 80, max_lvl = 105 },
+ ["arboretum"] = { area_name = "Arboretum", room_id = 39100, min_lvl = 110, max_lvl = 130 },
+ ["arena"] = { area_name = "The Gladiator's Arena", room_id = 25768, min_lvl = 90, max_lvl = 105 },
+ ["arisian"] = { area_name = "Arisian Realm", room_id = 28144, min_lvl = 150, max_lvl = 170 },
+ ["ascent"] = { area_name = "The First Ascent", room_id = 43161, min_lvl = 1, max_lvl = 15 },
+ ["astral"] = { area_name = "The Astral Travels", room_id = 27882, min_lvl = 180, max_lvl = 201 },
+ ["atlantis"] = { area_name = "Atlantis", room_id = 10573, min_lvl = 20, max_lvl = 40 },
+ ["autumn"] = { area_name = "Eternal Autumn", room_id = 13839, min_lvl = 170, max_lvl = 201 },
+ ["avian"] = { area_name = "Avian Kingdom", room_id = 4334, min_lvl = 170, max_lvl = 186 },
+ ["aylor"] = { area_name = "The Grand City of Aylor", room_id = 32418, min_lvl = 1, max_lvl = 201 },
+ ["badtrip"] = { area_name = "A Bad Trip", room_id = 32877, min_lvl = 210, max_lvl = 210 },
+ ["bard"] = { area_name = "The Bard Clan", room_id = 30538, min_lvl = 1, max_lvl = 201 },
+ ["bazaar"] = { area_name = "Onyx Bazaar", room_id = 34454, min_lvl = 30, max_lvl = 85 },
+ ["beer"] = { area_name = "The Land of the Beer Goblins", room_id = 20062, min_lvl = 1, max_lvl = 20 },
+ ["believer"] = { area_name = "The Path of the Believer", room_id = 25940, min_lvl = 1, max_lvl = 10 },
+ ["birthday"] = { area_name = "Aardwolf Birthday Area", room_id = 10920, min_lvl = 210, max_lvl = 210 },
+ ["blackrose"] = { area_name = "Black Rose", room_id = 1817, min_lvl = 175, max_lvl = 201 },
+ ["bliss"] = { area_name = "Wedded Bliss", room_id = 29988, min_lvl = 80, max_lvl = 100 },
+ ["bonds"] = { area_name = "Unearthly Bonds", room_id = 23411, min_lvl = 140, max_lvl = 170 },
+ ["bootcamp"] = { area_name = "The Boot Camp", room_id = 49256, min_lvl = 1, max_lvl = 201 },
+ ["cabal"] = { area_name = "Cathedral of the Elements", room_id = 15704, min_lvl = 1, max_lvl = 201 },
+ ["caldera"] = { area_name = "The Icy Caldera of Mauldoon", room_id = 26341, min_lvl = 190, max_lvl = 201 },
+ ["callhero"] = { area_name = "The Call of Heroes", room_id = 33031, min_lvl = 1, max_lvl = 15 },
+ ["camps"] = { area_name = "Tournament Camps", room_id = 4714, min_lvl = 1, max_lvl = 15 },
+ ["canyon"] = { area_name = "Canyon Memorial Hospital", room_id = 25551, min_lvl = 1, max_lvl = 30 },
+ ["caravan"] = { area_name = "Gypsy Caravan", room_id = 16071, min_lvl = 190, max_lvl = 201 },
+ ["cards"] = { area_name = "House of Cards", room_id = 6255, min_lvl = 120, max_lvl = 160 },
+ ["carnivale"] = { area_name = "Olde Worlde Carnivale", room_id = 28635, min_lvl = 1, max_lvl = 35 },
+ ["cataclysm"] = { area_name = "The Cataclysm", room_id = 19976, min_lvl = 145, max_lvl = 201 },
+ ["cathedral"] = { area_name = "The Old Cathedral", room_id = 27497, min_lvl = 50, max_lvl = 80 },
+ ["cats"] = { area_name = "Sheila's Cat Sanctuary", room_id = 40900, min_lvl = 1, max_lvl = 35 },
+ ["chaos"] = { area_name = "The Realm of Chaos", room_id = 28909, min_lvl = 1, max_lvl = 201 },
+ ["chasm"] = { area_name = "The Chasm and The Catacombs", room_id = 29446, min_lvl = 1, max_lvl = 25 },
+ ["chessboard"] = { area_name = "The Chessboard", room_id = 25513, min_lvl = 1, max_lvl = 20 },
+ ["childsplay"] = { area_name = "Child's Play", room_id = 678, min_lvl = 1, max_lvl = 25 },
+ ["cineko"] = { area_name = "Aerial City of Cineko", room_id = 1507, min_lvl = 1, max_lvl = 30 },
+ ["citadel"] = { area_name = "The Flying Citadel", room_id = 14963, min_lvl = 40, max_lvl = 65 },
+ ["conflict"] = { area_name = "Thandeld's Conflict", room_id = 27711, min_lvl = 1, max_lvl = 50 },
+ ["coral"] = { area_name = "The Coral Kingdom", room_id = 4565, min_lvl = 1, max_lvl = 50 },
+ ["cougarian"] = { area_name = "The Cougarian Queendom", room_id = 14311, min_lvl = 140, max_lvl = 170 },
+ ["cove"] = { area_name = "Kiksaadi Cove", room_id = 49941, min_lvl = 190, max_lvl = 201 },
+ ["cradle"] = { area_name = "Cradlebrook", room_id = 11267, min_lvl = 30, max_lvl = 50 },
+ ["crimson"] = { area_name = "The Crimson Horde Clan Hall", room_id = 27989, min_lvl = 1, max_lvl = 201 },
+ ["crusaders"] = { area_name = "The Crusader Clan", room_id = 31122, min_lvl = 1, max_lvl = 201 },
+ ["crynn"] = { area_name = "Crynn's Church", room_id = 43800, min_lvl = 190, max_lvl = 201 },
+ ["damned"] = { area_name = "Halls of the Damned", room_id = 10469, min_lvl = 95, max_lvl = 115 },
+ ["daoine"] = { area_name = "The Underground Hall", room_id = 30949, min_lvl = 1, max_lvl = 201 },
+ ["darklight"] = { area_name = "The DarkLight", room_id = 19642, min_lvl = 60, max_lvl = 120 },
+ ["darkside"] = { area_name = "The Darkside of the Fractured Lands", room_id = 15060, min_lvl = 35, max_lvl = 60 },
+ ["ddoom"] = { area_name = "Desert Doom", room_id = 4193, min_lvl = 135, max_lvl = 150 },
+ ["deadlights"] = { area_name = "The Deadlights", room_id = 16856, min_lvl = 175, max_lvl = 201 },
+ ["deathtrap"] = { area_name = "Deathtrap Dungeon", room_id = 1767, min_lvl = 85, max_lvl = 120 },
+ ["deneria"] = { area_name = "Realm of Deneria", room_id = 35006, min_lvl = 60, max_lvl = 80 },
+ ["desert"] = { area_name = "The Desert Prison", room_id = 20186, min_lvl = 130, max_lvl = 201 },
+ ["desolation"] = { area_name = "The Mountains of Desolation", room_id = 19532, min_lvl = 130, max_lvl = 180 },
+ ["dhalgora"] = { area_name = "Dhal'Gora Outlands", room_id = 16755, min_lvl = 1, max_lvl = 50 },
+ ["diatz"] = { area_name = "The Three Pillars of Diatz", room_id = 1254, min_lvl = 60, max_lvl = 80 },
+ ["diner"] = { area_name = "Tumari's Diner", room_id = 36700, min_lvl = 130, max_lvl = 140 },
+ ["doh"] = { area_name = "Disciples of Hassan Clan Hall", room_id = 16803, min_lvl = 1, max_lvl = 201 },
+ ["dominion"] = { area_name = "Dominion Clan Area", room_id = 5863, min_lvl = 1, max_lvl = 201 },
+ ["dortmund"] = { area_name = "Dortmund", room_id = 16577, min_lvl = 1, max_lvl = 25 },
+ ["drageran"] = { area_name = "The Drageran Empire", room_id = 25894, min_lvl = 125, max_lvl = 150 },
+ ["dragon"] = { area_name = "The White Dragon Clan", room_id = 642, min_lvl = 1, max_lvl = 201 },
+ ["dread"] = { area_name = "Dread Tower", room_id = 26075, min_lvl = 160, max_lvl = 201 },
+ ["druid"] = { area_name = "Isle of Anglesey", room_id = 29582, min_lvl = 1, max_lvl = 201 },
+ ["dsr"] = { area_name = "Diamond Soul Revelation", room_id = 30030, min_lvl = 35, max_lvl = 90 },
+ ["dundoom"] = { area_name = "The Dungeon of Doom", room_id = 25661, min_lvl = 190, max_lvl = 201 },
+ ["dunoir"] = { area_name = "Mount duNoir", room_id = 14222, min_lvl = 175, max_lvl = 201 },
+ ["duskvalley"] = { area_name = "Dusk Valley", room_id = 37301, min_lvl = 100, max_lvl = 120 },
+ ["dynasty"] = { area_name = "The Eighteenth Dynasty", room_id = 30799, min_lvl = 120, max_lvl = 140 },
+ ["earthlords"] = { area_name = "The Earth Lords", room_id = 42000, min_lvl = 190, max_lvl = 201 },
+ ["earthplane"] = { area_name = "Earth Plane 4", room_id = 1354, min_lvl = 50, max_lvl = 80 },
+ ["elemental"] = { area_name = "Elemental Chaos", room_id = 41624, min_lvl = 90, max_lvl = 150 },
+ ["emerald"] = { area_name = "The Emerald Clan HQ", room_id = 831, min_lvl = 1, max_lvl = 201 },
+ ["empire"] = { area_name = "The Empire of Aiighialla", room_id = 32203, min_lvl = 150, max_lvl = 186 },
+ ["empyrean"] = { area_name = "Empyrean, Streets of Downfall", room_id = 14042, min_lvl = 170, max_lvl = 201 },
+ ["entropy"] = { area_name = "The Archipelago of Entropy", room_id = 29773, min_lvl = 120, max_lvl = 145 },
+ ["fantasy"] = { area_name = "Fantasy Fields", room_id = 15205, min_lvl = 1, max_lvl = 30 },
+ ["farm"] = { area_name = "Kimr's Farm", room_id = 10676, min_lvl = 1, max_lvl = 10 },
+ ["fayke"] = { area_name = "All in a Fayke Day", room_id = 30418, min_lvl = 1, max_lvl = 30 },
+ ["fens"] = { area_name = "The Curse of the Midnight Fens", room_id = 16528, min_lvl = 190, max_lvl = 201 },
+ ["fields"] = { area_name = "The Killing Fields", room_id = 29232, min_lvl = 60, max_lvl = 80 },
+ ["firebird"] = { area_name = "Realm of the Firebird", room_id = 32885, min_lvl = 80, max_lvl = 110 },
+ ["firenation"] = { area_name = "Realm of the Sacred Flame", room_id = 41879, min_lvl = 190, max_lvl = 201 },
+ ["fireswamp"] = { area_name = "The Fire Swamp", room_id = 34755, min_lvl = 1, max_lvl = 15 },
+ ["fortress"] = { area_name = "The Goblin Fortress", room_id = 31835, min_lvl = 60, max_lvl = 80 },
+ ["fortune"] = { area_name = "Crossroads of Fortune", room_id = 38561, min_lvl = 201, max_lvl = 201 },
+ ["fractured"] = { area_name = "The Fractured Lands", room_id = 17033, min_lvl = 20, max_lvl = 40 },
+ ["ft1"] = { area_name = "Faerie Tales", room_id = 1205, min_lvl = 100, max_lvl = 120 },
+ ["ftii"] = { area_name = "Faerie Tales II", room_id = 26673, min_lvl = 120, max_lvl = 140 },
+ ["gaardian"] = { area_name = "Midgaardian Publishing House", room_id = 20026, min_lvl = 1, max_lvl = 201 },
+ ["gallows"] = { area_name = "Gallows Hill", room_id = 4344, min_lvl = 1, max_lvl = 20 },
+ ["gathering"] = { area_name = "The Gathering Horde", room_id = 36451, min_lvl = 140, max_lvl = 170 },
+ ["gauntlet"] = { area_name = "The Gauntlet", room_id = 31652, min_lvl = 1, max_lvl = 30 },
+ ["gelidus"] = { area_name = "Gelidus", room_id = 18780, min_lvl = 1, max_lvl = 201 },
+ ["geniewish"] = { area_name = "A Genie's Last Wish", room_id = 38464, min_lvl = 201, max_lvl = 201 },
+ ["gilda"] = { area_name = "Gilda And The Dragon", room_id = 4243, min_lvl = 120, max_lvl = 140 },
+ ["glamdursil"] = { area_name = "The Glamdursil", room_id = 35055, min_lvl = 170, max_lvl = 201 },
+ ["glimmerdim"] = { area_name = "Brightsea and Glimmerdim", room_id = 26252, min_lvl = 1, max_lvl = 40 },
+ ["gnomalin"] = { area_name = "Cloud City of Gnomalin", room_id = 34397, min_lvl = 1, max_lvl = 35 },
+ ["goldrush"] = { area_name = "Gold Rush", room_id = 15014, min_lvl = 30, max_lvl = 70 },
+ ["graveyard"] = { area_name = "The Graveyard", room_id = 28918, min_lvl = 1, max_lvl = 15 },
+ ["greece"] = { area_name = "Ancient Greece", room_id = 2089, min_lvl = 20, max_lvl = 55 },
+ ["gwillim"] = { area_name = "The Trouble with Gwillimberry", room_id = 25974, min_lvl = 185, max_lvl = 201 },
+ ["hades"] = { area_name = "Entrance to Hades", room_id = 29161, min_lvl = 180, max_lvl = 201 },
+ ["hatchling"] = { area_name = "Hatchling Aerie", room_id = 34670, min_lvl = 1, max_lvl = 55 },
+ ["hawklord"] = { area_name = "The Realm of the Hawklords", room_id = 40550, min_lvl = 80, max_lvl = 100 },
+ ["hedge"] = { area_name = "Hedgehogs' Paradise", room_id = 15146, min_lvl = 60, max_lvl = 80 },
+ ["helegear"] = { area_name = "Helegear Sea", room_id = 30699, min_lvl = 160, max_lvl = 180 },
+ ["hell"] = { area_name = "Descent to Hell", room_id = 30984, min_lvl = 30, max_lvl = 85 },
+ ["hoard"] = { area_name = "Swordbreaker's Hoard", room_id = 1675, min_lvl = 1, max_lvl = 60 },
+ ["hodgepodge"] = { area_name = "A Magical Hodgepodge", room_id = 30469, min_lvl = 1, max_lvl = 35 },
+ ["horath"] = { area_name = "The Broken Halls of Horath", room_id = 91, min_lvl = 120, max_lvl = 175 },
+ ["horizon"] = { area_name = "Nebulous Horizon", room_id = 31959, min_lvl = 190, max_lvl = 201 },
+ ["icefall"] = { area_name = "Icefall", room_id = 38701, min_lvl = 201, max_lvl = 201 },
+ ["illoria"] = { area_name = "The Tournament of Illoria", room_id = 10420, min_lvl = 70, max_lvl = 90 },
+ ["imagi"] = { area_name = "Imagi's Nation", room_id = 36800, min_lvl = 140, max_lvl = 160 },
+ ["immhomes"] = { area_name = "The Aardwolf Plaza Hotel", room_id = 26151, min_lvl = 0, max_lvl = 220 },
+ ["imperial"] = { area_name = "Imperial Nation", room_id = 16966, min_lvl = 40, max_lvl = 201 },
+ ["imperium"] = { area_name = "The Stronghold of the Imperium", room_id = 30415, min_lvl = 1, max_lvl = 201 },
+ ["infamy"] = { area_name = "The Realm of Infamy", room_id = 26641, min_lvl = 190, max_lvl = 201 },
+ ["inferno"] = { area_name = "Journey to the Inferno", room_id = 37213, min_lvl = 200, max_lvl = 201 },
+ ["infest"] = { area_name = "The Infestation", room_id = 16165, min_lvl = 1, max_lvl = 35 },
+ ["insan"] = { area_name = "Insanitaria", room_id = 6850, min_lvl = 95, max_lvl = 115 },
+ ["jenny"] = { area_name = "Jenny's Tavern", room_id = 29637, min_lvl = 55, max_lvl = 100 },
+ ["jotun"] = { area_name = "Jotunheim", room_id = 31508, min_lvl = 1, max_lvl = 40 },
+ ["kearvek"] = { area_name = "The Keep of Kearvek", room_id = 29722, min_lvl = 180, max_lvl = 201 },
+ ["kerofk"] = { area_name = "Kerofk", room_id = 16405, min_lvl = 1, max_lvl = 30 },
+ ["ketu"] = { area_name = "Ketu Uplands", room_id = 35114, min_lvl = 190, max_lvl = 201 },
+ ["kingsholm"] = { area_name = "Kingsholm", room_id = 27522, min_lvl = 1, max_lvl = 70 },
+ ["knossos"] = { area_name = "The Great City of Knossos", room_id = 28193, min_lvl = 60, max_lvl = 80 },
+ ["kobaloi"] = { area_name = "Keep of the Kobaloi", room_id = 10691, min_lvl = 1, max_lvl = 201 },
+ ["kultiras"] = { area_name = "Kul Tiras", room_id = 31161, min_lvl = 1, max_lvl = 30 },
+ ["lab"] = { area_name = "Chaprenula's Laboratory", room_id = 28684, min_lvl = 1, max_lvl = 15 },
+ ["labyrinth"] = { area_name = "The Labyrinth", room_id = 31405, min_lvl = 30, max_lvl = 60 },
+ ["lagoon"] = { area_name = "Black Lagoon", room_id = 30549, min_lvl = 155, max_lvl = 195 },
+ ["landofoz"] = { area_name = "The Land of Oz", room_id = 510, min_lvl = 40, max_lvl = 75 },
+ ["laym"] = { area_name = "Tai'rha Laym", room_id = 6005, min_lvl = 50, max_lvl = 120 },
+ ["legend"] = { area_name = "Land of Legend", room_id = 16224, min_lvl = 1, max_lvl = 20 },
+ ["lemdagor"] = { area_name = "Storm Ships of Lem-Dagor", room_id = 1966, min_lvl = 40, max_lvl = 100 },
+ ["lidnesh"] = { area_name = "The Forest of Li'Dnesh", room_id = 27995, min_lvl = 1, max_lvl = 10 },
+ ["light"] = { area_name = "The Order of Light", room_id = 2339, min_lvl = 1, max_lvl = 201 },
+ ["livingmine"] = { area_name = "Living Mines of Dak'Tai", room_id = 37008, min_lvl = 110, max_lvl = 140 },
+ ["longnight"] = { area_name = "Into the Long Night", room_id = 26367, min_lvl = 100, max_lvl = 201 },
+ ["loqui"] = { area_name = "Loqui Clan Area", room_id = 28580, min_lvl = 1, max_lvl = 201 },
+ ["losttime"] = { area_name = "Island of Lost Time", room_id = 28584, min_lvl = 80, max_lvl = 110 },
+ ["lowlands"] = { area_name = "Lowlands Paradise '96", room_id = 28044, min_lvl = 1, max_lvl = 10 },
+ ["lplanes"] = { area_name = "The Lower Planes", room_id = 29364, min_lvl = 70, max_lvl = 100 },
+ ["maelstrom"] = { area_name = "The Maelstrom", room_id = 38058, min_lvl = 20, max_lvl = 45 },
+ ["manor"] = { area_name = "Death's Manor", room_id = 10621, min_lvl = 20, max_lvl = 40 },
+ ["manor1"] = { area_name = "The Aardwolf Real Estates", room_id = 14460, min_lvl = 1, max_lvl = 201 },
+ ["manor3"] = { area_name = "Aardwolf Estates 2000", room_id = 20836, min_lvl = 1, max_lvl = 201 },
+ ["manorisle"] = { area_name = "The Aardwolf Isle Estates", room_id = 6366, min_lvl = 1, max_lvl = 201 },
+ ["manormount"] = { area_name = "Mountain View Estates", room_id = 39449, min_lvl = 1, max_lvl = 201 },
+ ["manorsea"] = { area_name = "Seaside Height Estates", room_id = 35003, min_lvl = 1, max_lvl = 201 },
+ ["manorville"] = { area_name = "Prairie Village Estates", room_id = 35004, min_lvl = 1, max_lvl = 201 },
+ ["manorwoods"] = { area_name = "Shady Acres Estates", room_id = 35002, min_lvl = 1, max_lvl = 201 },
+ ["masaki"] = { area_name = "Masaki Clan Area", room_id = 15852, min_lvl = 1, max_lvl = 201 },
+ ["masq"] = { area_name = "Masquerade Island", room_id = 29840, min_lvl = 100, max_lvl = 130 },
+ ["mayhem"] = { area_name = "Artificer's Mayhem", room_id = 1866, min_lvl = 180, max_lvl = 201 },
+ ["melody"] = { area_name = "Art of Melody", room_id = 14172, min_lvl = 1, max_lvl = 15 },
+ ["mesolar"] = { area_name = "The Continent of Mesolar", room_id = 12664, min_lvl = 1, max_lvl = 201 },
+ ["minos"] = { area_name = "The Shadows of Minos", room_id = 20472, min_lvl = 1, max_lvl = 35 },
+ ["mistridge"] = { area_name = "The Covenant of Mistridge", room_id = 4491, min_lvl = 160, max_lvl = 186 },
+ ["monastery"] = { area_name = "The Monastery", room_id = 15756, min_lvl = 85, max_lvl = 115 },
+ ["mudwog"] = { area_name = "Mudwog's Swamp", room_id = 2347, min_lvl = 30, max_lvl = 45 },
+ ["nanjiki"] = { area_name = "Nanjiki Ruins", room_id = 11203, min_lvl = 140, max_lvl = 160 },
+ ["necro"] = { area_name = "Necromancers' Guild", room_id = 29922, min_lvl = 1, max_lvl = 35 },
+ ["nenukon"] = { area_name = "Nenukon and the Far Country", room_id = 31784, min_lvl = 70, max_lvl = 110 },
+ ["newthalos"] = { area_name = "New Thalos", room_id = 23853, min_lvl = 1, max_lvl = 35 },
+ ["ninehells"] = { area_name = "The Nine Hells", room_id = 4613, min_lvl = 190, max_lvl = 201 },
+ ["northstar"] = { area_name = "Northstar", room_id = 11127, min_lvl = 70, max_lvl = 150 },
+ ["nottingham"] = { area_name = "Nottingham", room_id = 11077, min_lvl = 190, max_lvl = 201 },
+ ["nulan"] = { area_name = "Plains of Nulan'Boar", room_id = 37900, min_lvl = 60, max_lvl = 80 },
+ ["nursing"] = { area_name = "Ascension Bluff Nursing Home", room_id = 31977, min_lvl = 100, max_lvl = 130 },
+ ["nynewoods"] = { area_name = "The Nyne Woods", room_id = 23562, min_lvl = 190, max_lvl = 201 },
+ ["oceanpark"] = { area_name = "Andolor's Ocean Adventure Park", room_id = 39600, min_lvl = 190, max_lvl = 201 },
+ ["omentor"] = { area_name = "The Witches of Omen Tor", room_id = 15579, min_lvl = 160, max_lvl = 201 },
+ ["ooku"] = { area_name = "Ookushka Garrison", room_id = 39000, min_lvl = 201, max_lvl = 201 },
+ ["oradrin"] = { area_name = "Oradrin's Chosen", room_id = 25436, min_lvl = 201, max_lvl = 201 },
+ ["origins"] = { area_name = "Tribal Origins", room_id = 35900, min_lvl = 175, max_lvl = 186 },
+ ["orlando"] = { area_name = "Hotel Orlando", room_id = 30331, min_lvl = 1, max_lvl = 20 },
+ ["paradise"] = { area_name = "Paradise Lost", room_id = 29624, min_lvl = 50, max_lvl = 70 },
+ ["partroxis"] = { area_name = "The Partroxis", room_id = 5814, min_lvl = 180, max_lvl = 201 },
+ ["peninsula"] = { area_name = "Tairayden Peninsula", room_id = 35701, min_lvl = 115, max_lvl = 125 },
+ ["perdition"] = { area_name = "Perdition Clan Area", room_id = 19968, min_lvl = 1, max_lvl = 201 },
+ ["petstore"] = { area_name = "Giant's Pet Store", room_id = 995, min_lvl = 1, max_lvl = 20 },
+ ["pompeii"] = { area_name = "Pompeii", room_id = 57, min_lvl = 90, max_lvl = 110 },
+ ["promises"] = { area_name = "Foolish Promises", room_id = 25819, min_lvl = 130, max_lvl = 140 },
+ ["prosper"] = { area_name = "Prosper's Island", room_id = 28268, min_lvl = 100, max_lvl = 201 },
+ ["pyre"] = { area_name = "Twilight Hall", room_id = 15141, min_lvl = 1, max_lvl = 201 },
+ ["qong"] = { area_name = "Qong", room_id = 16115, min_lvl = 190, max_lvl = 201 },
+ ["quarry"] = { area_name = "Gnoll's Quarry", room_id = 23510, min_lvl = 105, max_lvl = 125 },
+ ["radiance"] = { area_name = "Radiance Woods", room_id = 19805, min_lvl = 190, max_lvl = 201 },
+ ["raga"] = { area_name = "Raganatittu", room_id = 19861, min_lvl = 40, max_lvl = 60 },
+ ["raukora"] = { area_name = "The Blood Opal of Rauko'ra", room_id = 6040, min_lvl = 130, max_lvl = 201 },
+ ["rebellion"] = { area_name = "Rebellion of the Nix", room_id = 10305, min_lvl = 150, max_lvl = 201 },
+ ["remcon"] = { area_name = "The Reman Conspiracy", room_id = 25837, min_lvl = 130, max_lvl = 201 },
+ ["reme"] = { area_name = "The Imperial City of Reme", room_id = 32703, min_lvl = 20, max_lvl = 150 },
+ ["romani"] = { area_name = "A Clearing in the Woods", room_id = 24180, min_lvl = 1, max_lvl = 201 },
+ ["rosewood"] = { area_name = "Rosewood Castle", room_id = 6901, min_lvl = 65, max_lvl = 150 },
+ ["ruins"] = { area_name = "The Ruins of Diamond Reach", room_id = 16805, min_lvl = 60, max_lvl = 125 },
+ ["sagewood"] = { area_name = "Sagewood Grove", room_id = 28754, min_lvl = 145, max_lvl = 195 },
+ ["sahuagin"] = { area_name = "The Abyssal Caverns of Sahuagin", room_id = 34592, min_lvl = 140, max_lvl = 160 },
+ ["salt"] = { area_name = "The Great Salt Flats", room_id = 4538, min_lvl = 40, max_lvl = 75 },
+ ["sanctity"] = { area_name = "Sanctity of Eternal Damnation", room_id = 10518, min_lvl = 100, max_lvl = 201 },
+ ["sanctum"] = { area_name = "The Blood Sanctum", room_id = 15307, min_lvl = 180, max_lvl = 201 },
+ ["sandcastle"] = { area_name = "Sho'aram, Castle in the Sand", room_id = 37701, min_lvl = 1, max_lvl = 30 },
+ ["sanguine"] = { area_name = "The Sanguine Tavern", room_id = 15436, min_lvl = 120, max_lvl = 150 },
+ ["scarred"] = { area_name = "The Scarred Lands", room_id = 34036, min_lvl = 90, max_lvl = 110 },
+ ["seaking"] = { area_name = "Sea King's Dominion", room_id = 145, min_lvl = 210, max_lvl = 210 },
+ ["seekers"] = { area_name = "The Fortress of Knowledge", room_id = 14165, min_lvl = 1, max_lvl = 201 },
+ ["sendhian"] = { area_name = "Adventures in Sendhia", room_id = 20288, min_lvl = 1, max_lvl = 60 },
+ ["sennarre"] = { area_name = "Sen'narre Lake", room_id = 15491, min_lvl = 1, max_lvl = 20 },
+ ["shadokil"] = { area_name = "The Shadokil Guildhouse", room_id = 32407, min_lvl = 1, max_lvl = 201 },
+ ["shouggoth"] = { area_name = "The Temple of Shouggoth", room_id = 34087, min_lvl = 1, max_lvl = 65 },
+ ["siege"] = { area_name = "Kobold Siege Camp", room_id = 43265, min_lvl = 80, max_lvl = 100 },
+ ["sirens"] = { area_name = "Siren's Oasis Resort", room_id = 16298, min_lvl = 1, max_lvl = 15 },
+ ["slaughter"] = { area_name = "The Slaughter House", room_id = 1601, min_lvl = 120, max_lvl = 145 },
+ ["snuckles"] = { area_name = "Snuckles Village", room_id = 182, min_lvl = 80, max_lvl = 100 },
+ ["soh"] = { area_name = "The School of Horror", room_id = 25611, min_lvl = 125, max_lvl = 201 },
+ ["sohtwo"] = { area_name = "The School of Horror", room_id = 30752, min_lvl = 165, max_lvl = 201 },
+ ["solan"] = { area_name = "The Town of Solan", room_id = 23713, min_lvl = 1, max_lvl = 35 },
+ ["songpalace"] = { area_name = "The Palace of Song", room_id = 47013, min_lvl = 60, max_lvl = 80 },
+ ["southern"] = { area_name = "The Southern Ocean", room_id = 5192, min_lvl = 1, max_lvl = 201 },
+ ["spyreknow"] = { area_name = "Guardian's Spyre of Knowledge", room_id = 34800, min_lvl = 1, max_lvl = 30 },
+ ["stone"] = { area_name = "The Fabled City of Stone", room_id = 11386, min_lvl = 70, max_lvl = 135 },
+ ["storm"] = { area_name = "Storm Mountain", room_id = 6304, min_lvl = 1, max_lvl = 40 },
+ ["stormhaven"] = { area_name = "The Ruins of Stormhaven", room_id = 20649, min_lvl = 150, max_lvl = 201 },
+ ["stronghold"] = { area_name = "Dark Elf Stronghold", room_id = 20572, min_lvl = 70, max_lvl = 125 },
+ ["stuff"] = { area_name = "The Stuff of Shadows", room_id = 40400, min_lvl = 110, max_lvl = 130 },
+ ["takeda"] = { area_name = "Takeda's Warcamp", room_id = 15952, min_lvl = 140, max_lvl = 201 },
+ ["talsa"] = { area_name = "The Empire of Talsa", room_id = 26917, min_lvl = 65, max_lvl = 140 },
+ ["tanelorn"] = { area_name = "The Legendary City of Tanelorn", room_id = 31561, min_lvl = 1, max_lvl = 201 },
+ ["tanra"] = { area_name = "Tanra'vea", room_id = 46913, min_lvl = 180, max_lvl = 201 },
+ ["tao"] = { area_name = "The Collective Mind of Tao", room_id = 29210, min_lvl = 1, max_lvl = 201 },
+ ["temple"] = { area_name = "The Temple of Shal'indrael", room_id = 31597, min_lvl = 180, max_lvl = 201 },
+ ["terra"] = { area_name = "The Cracks of Terra", room_id = 19679, min_lvl = 190, max_lvl = 201 },
+ ["terramire"] = { area_name = "Fort Terramire", room_id = 4493, min_lvl = 1, max_lvl = 35 },
+ ["thieves"] = { area_name = "Den of Thieves", room_id = 7, min_lvl = 1, max_lvl = 20 },
+ ["times"] = { area_name = "Intrigues of Times Past", room_id = 28463, min_lvl = 180, max_lvl = 201 },
+ ["tirna"] = { area_name = "Tir na nOg", room_id = 20136, min_lvl = 130, max_lvl = 150 },
+ ["titan"] = { area_name = "The Titans' Keep", room_id = 38234, min_lvl = 190, max_lvl = 201 },
+ ["tol"] = { area_name = "The Tree of Life", room_id = 16325, min_lvl = 175, max_lvl = 201 },
+ ["tombs"] = { area_name = "The Relinquished Tombs", room_id = 15385, min_lvl = 45, max_lvl = 100 },
+ ["touchstone"] = { area_name = "Touchstone Cavern", room_id = 28346, min_lvl = 1, max_lvl = 201 },
+ ["twinlobe"] = { area_name = "The Twinlobe Clan HQ", room_id = 15575, min_lvl = 1, max_lvl = 201 },
+ ["umari"] = { area_name = "Umari's Castle", room_id = 36601, min_lvl = 190, max_lvl = 201 },
+ ["uncharted"] = { area_name = "The Uncharted Oceans", room_id = 7701, min_lvl = 1, max_lvl = 201 },
+ ["underdark"] = { area_name = "The UnderDark", room_id = 27341, min_lvl = 1, max_lvl = 50 },
+ ["uplanes"] = { area_name = "The Upper Planes", room_id = 29365, min_lvl = 60, max_lvl = 85 },
+ ["uprising"] = { area_name = "The Uprising", room_id = 15382, min_lvl = 120, max_lvl = 160 },
+ ["vale"] = { area_name = "Sundered Vale", room_id = 1036, min_lvl = 1, max_lvl = 30 },
+ ["vanir"] = { area_name = "The Halls of Vanir", room_id = 878, min_lvl = 1, max_lvl = 201 },
+ ["verdure"] = { area_name = "Verdure Estate", room_id = 24090, min_lvl = 120, max_lvl = 140 },
+ ["verume"] = { area_name = "Jungles of Verume", room_id = 30607, min_lvl = 1, max_lvl = 40 },
+ ["vidblain"] = { area_name = "Vidblain, the Ever Dark", room_id = 33570, min_lvl = 1, max_lvl = 201 },
+ ["village"] = { area_name = "A Peaceful Giant Village", room_id = 30850, min_lvl = 135, max_lvl = 170 },
+ ["vlad"] = { area_name = "Castle Vlad-Shamir", room_id = 15970, min_lvl = 50, max_lvl = 100 },
+ ["volcano"] = { area_name = "The Silver Volcano", room_id = 6091, min_lvl = 30, max_lvl = 100 },
+ ["watchmen"] = { area_name = "The World of the Watchmen", room_id = 32342, min_lvl = 1, max_lvl = 201 },
+ ["weather"] = { area_name = "Weather Observatory", room_id = 40499, min_lvl = 20, max_lvl = 40 },
+ ["werewood"] = { area_name = "The Were Wood", room_id = 30956, min_lvl = 190, max_lvl = 201 },
+ ["wildwood"] = { area_name = "Wildwood", room_id = 322, min_lvl = 1, max_lvl = 40 },
+ ["winds"] = { area_name = "Winds of Fate", room_id = 39900, min_lvl = 201, max_lvl = 201 },
+ ["winter"] = { area_name = "Winterlands", room_id = 1306, min_lvl = 150, max_lvl = 170 },
+ ["wizards"] = { area_name = "War of the Wizards", room_id = 31316, min_lvl = 1, max_lvl = 35 },
+ ["wonders"] = { area_name = "Seven Wonders", room_id = 32981, min_lvl = 100, max_lvl = 120 },
+ ["wooble"] = { area_name = "The Wobbly Woes of Woobleville", room_id = 11335, min_lvl = 40, max_lvl = 60 },
+ ["woodelves"] = { area_name = "The Wood Elves of Nalondir", room_id = 32199, min_lvl = 1, max_lvl = 30 },
+ ["wtc"] = { area_name = "Warrior's Training Camp", room_id = 37895, min_lvl = 1, max_lvl = 15 },
+ ["wyrm"] = { area_name = "The Council of the Wyrm", room_id = 28847, min_lvl = 190, max_lvl = 201 },
+ ["xmas"] = { area_name = "Christmas Vacation", room_id = 6212, min_lvl = 110, max_lvl = 150 },
+ ["xylmos"] = { area_name = "Xyl's Mosaic", room_id = 472, min_lvl = 100, max_lvl = 120 },
+ ["yarr"] = { area_name = "The Misty Shores of Yarr", room_id = 30281, min_lvl = 115, max_lvl = 135 },
+ ["ygg"] = { area_name = "Yggdrasil: The World Tree", room_id = 24186, min_lvl = 180, max_lvl = 201 },
+ ["yurgach"] = { area_name = "The Yurgach Domain", room_id = 29450, min_lvl = 35, max_lvl = 110 },
+ ["zangar"] = { area_name = "Zangar's Demonic Grotto", room_id = 6164, min_lvl = 40, max_lvl = 60 },
+ ["zodiac"] = { area_name = "Realm of the Zodiac", room_id = 15857, min_lvl = 1, max_lvl = 45 },
+ ["zoo"] = { area_name = "Aardwolf Zoological Park", room_id = 5920, min_lvl = 1, max_lvl = 35 },
+ ["zyian"] = { area_name = "The Dark Temple of Zyian", room_id = 729, min_lvl = 120, max_lvl = 150 },
+ };
+
+ local sql = [[
+ INSERT OR IGNORE INTO area VALUES(%s, %s, %d, %d, %d);
+ ]];
+
+ database.mobber:exec("BEGIN TRANSACTION;", true);
+
+ local numChanges = 0;
+
+ for k,v in pairs(zones) do
+ local stmt = sql:format(fixsql(k), fixsql(v.area_name), v.min_lvl, v.max_lvl, v.room_id);
+ database.mobber:exec(stmt);
+ numChanges = numChanges + database.mobber:changes();
+ end
+
+ database.mobber:exec("END TRANSACTION;", true);
+
+ local dif = GetInfo(232) - start;
+
+ utility.print(numChanges .. " area(s) added to the mobber database.\r\nAreas update completed in: " .. formatSeconds(dif), "lime");
+ end
+end
+
+----------------------------------
+-- Utility
+----------------------------------
+
+utility = {};
+
+function utility.initialize()
+ AddAlias("alias_utility_help_main", "^(?:mobber help|help mobber)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "utility.help.main"
+ );
+
+ AddAlias("alias_utility_help_quests", "^mobber help quests?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "utility.help.quests"
+ );
+
+ AddAlias("alias_utility_help_searching", "^mobber help search(?:ing)?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "utility.help.searching"
+ );
+
+ AddAlias("alias_utility_help_utils", "^mobber help (?:utils?|utility)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "utility.help.utils"
+ );
+
+ AddAlias("alias_utility_help_developer", "^mobber help dev(?:eloper)?$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "utility.help.developer"
+ );
+
+ local initializers = {
+ database.initialize,
+ campaign.initialize,
+ globalQuest.initialize,
+ roomHandler.initialize,
+ roomSearch.initialize,
+ mobSearch.initialize,
+ quickScan.initialize,
+ quickWhere.initialize,
+ autoHunt.initialize,
+ huntTrick.initialize,
+ autoKill.initialize,
+ noexp.initialize,
+ quest.initialize,
+ colorPalette.initialize
+ };
+
+ for k,v in ipairs(initializers) do
+ v();
+ end
+
+ if (var.isQuestMuted == "false") then
+ quest.isMuted = false;
+ else
+ quest.isMuted = true;
+ end
+
+ database.openDatabase();
+end
+
+function utility.deinitialize()
+ local aliases = GetAliasList();
+
+ if (aliases) then
+ for i = 1, #aliases do
+ EnableAlias(aliases[i], false);
+ DeleteAlias(aliases[i]);
+ end
+ end
+
+ local triggers = GetTriggerList();
+
+ if (triggers) then
+ for i = 1, #triggers do
+ EnableTrigger(triggers[i], false);
+ DeleteTrigger(triggers[i]);
+ end
+ end
+
+ database.closeDatabase();
+end
+
+function utility.runToRoomId(roomId)
+ local STATE_ACTIVE = 3;
+ local canRun = true;
+
+ if (utility.gmcp.getPlayerRoomId() ~= roomId) then
+ if (utility.gmcp.getPlayerState() == STATE_ACTIVE) then
+ Execute("mapper goto " .. roomId);
+ else
+ canRun = false;
+ Execute("mapper where " .. roomId);
+ end
+ else
+ utility.print("You are already in that room.");
+ end
+
+ return canRun;
+end
+
+function utility.setTarget(target, keyword)
+ utility.targetName = target;
+ utility.targetKeyword = keyword;
+
+ var.target = target;
+end
+
+function utility.updateEnemy()
+ local player_enemy = utility.gmcp.getPlayerEnemy():lower();
+
+ if (player_enemy ~= "" and utility.playerEnemy ~= player_enemy) then
+ utility.playerEnemy = player_enemy;
+ end
+end
+
+function utility.getMobKeyword(mobName)
+ mobName = mobName:lower();
+
+ local mobKeyword;
+
+ local splitMobName = {};
+
+ for token in mobName:gmatch("[^ ]+") do
+ token = token:gsub("[^%a]", "");
+ table.insert(splitMobName, token);
+ end -- for each non-space word strip any non-letter/"-"
+
+ local keywords = {};
+
+ for i = 1, #splitMobName do
+ local token = splitMobName[i];
+
+ if (#splitMobName == 2 and (token:find("^evil$") == 1 or token:find("^good$") == 1)) then
+ return splitMobName[1];
+ end -- sohtwo mobs
+
+ local prefixPatterns = {
+ "^a$",
+ "^an$",
+ "^the$",
+ "^of$",
+ "^some$",
+ "^whelp$",
+ "^dragon$",
+ "^lizardman$",
+ "^sea$"
+ };
+
+ for k,v in ipairs(prefixPatterns) do
+ token = token:gsub(v, "");
+ end -- strip certain non-keyword prefixes
+
+ if (token ~= "") then
+ table.insert(keywords, token);
+ end -- if token still exists put into keywords table
+ end
+
+ if (not next(keywords)) then
+ mobKeyword = mobName;
+ else
+ mobKeyword = #keywords == 1 and keywords[1] or (keywords[1]:sub(1, 4) .. " " .. keywords[#keywords]:sub(1, 4));
+ end
+
+ return mobKeyword;
+end
+
+function utility.print(message, color)
+ color = color or "white";
+
+ ColourNote(
+ pluginPalette.mobberParentheses, "", "\r\n(",
+ pluginPalette.mobber, "", "Mobber",
+ pluginPalette.mobberParentheses, "", ") ",
+ color, "", message .. "\r\n\r\n"
+ );
+end
+
+----------------------------------
+-- GMCP
+----------------------------------
+
+utility.gmcp = {};
+
+function utility.gmcp.getPlayerName()
+ return gmcp("char.base.name");
+end
+
+function utility.gmcp.getPlayerLevel()
+ return tonumber(gmcp("char.status.level"));
+end
+
+function utility.gmcp.getPlayerTnl()
+ return tonumber(gmcp("char.status.tnl"));
+end
+
+function utility.gmcp.getPlayerRoomId()
+ return tonumber(gmcp("room.info.num"));
+end
+
+function utility.gmcp.getPlayerRoomName()
+ return gmcp("room.info.name");
+end
+
+function utility.gmcp.getPlayerZone()
+ return gmcp("room.info.zone");
+end
+
+function utility.gmcp.getPlayerState()
+ return tonumber(gmcp("char.status.state"));
+end
+
+function utility.gmcp.getPlayerEnemy()
+ return gmcp("char.status.enemy"):lower();
+end
+
+function utility.gmcp.requestPlayerArea()
+ Send_GMCP_Packet("request area");
+end
+
+----------------------------------
+-- Help
+----------------------------------
+
+utility.help = {};
+
+function utility.help.main()
+ print();
+ ColourNote(pluginPalette.border, "", string.format('%-15s : ', 'Name'), pluginPalette.helpDescription, "", "Mobber");
+ ColourNote(pluginPalette.border, "", string.format('%-15s : ', 'Author'), pluginPalette.helpDescription, "", GetPluginInfo(GetPluginID(), 2));
+ ColourNote(pluginPalette.border, "", string.format('%-15s : ', 'Version'), pluginPalette.helpDescription, "", string.format("%.1f", GetPluginInfo(GetPluginID(), 19)));
+ ColourNote(pluginPalette.border, "", string.format('%-15s : ', 'Purpose'), pluginPalette.helpDescription, "", GetPluginInfo(GetPluginID(), 8));
+ ColourNote(pluginPalette.border, "", string.format('%-15s : ', 'Mem Usage'), "yellow", "", string.format('%0d KB', collectgarbage('count')));
+ print("\r\n\r\n");
+
+ ColourNote(pluginPalette.border, "", string.rep("-", 7), pluginPalette.helpTitles, "", " Mobber Topics ", pluginPalette.border, "", string.rep("-", 7));
+ print();
+
+ utility.help.command("mobber help quests", "Campaigns and global quests.");
+ utility.help.command("mobber help searching", "Searching for mobs and rooms.");
+ utility.help.command("mobber help utils", "Utility commands.");
+ utility.help.command("mobber help developer", "Adding areas, rooms and mobs to the database.");
+ print("\r\n\r\n");
+
+ collectgarbage("collect");
+end
+
+function utility.help.quests()
+ print();
+ ColourNote(pluginPalette.border, "", string.rep("-", 7), pluginPalette.helpTitles, "", " Mobber Quests ", pluginPalette.border, "", string.rep("-", 7));
+ print();
+
+ utility.help.command("cp c", "Send 'campaign check' to process cp mobs.");
+ utility.help.command("xcp <#>", "Select a mob from the campaign list.");
+ print();
+
+ utility.help.command("gq c", "Send 'gquest check' to process gq mobs.");
+ utility.help.command("xgq <#>", "Select a mob from the global quest list.");
+ print();
+
+ utility.help.command("xgo <#>", "Go to a room in the current room list.");
+ utility.help.command("xnext", "Go to the next room in the room list.");
+ utility.help.command("xprev", "Go to the previous room in the room list.");
+ print();
+
+ utility.help.command("mobber quest", "Show quest info for current quest mob.");
+ utility.help.command("mobber mute|unmute quest", "Mute or unmute quest messages/sounds.");
+ print("\r\n\r\n");
+end
+
+function utility.help.searching()
+ print();
+ ColourNote(pluginPalette.border, "", string.rep("-", 7), pluginPalette.helpTitles, "", " Mobber Searching ", pluginPalette.border, "", string.rep("-", 7));
+ print();
+
+ utility.help.command("mf zone ", "Search for a mob in the current zone.");
+ print();
+
+ utility.help.command("mfa lvl ", "Search for a mob in all zones.");
+ print();
+
+ utility.help.command("rf zone ", "Search for a room in the current zone.");
+ print();
+ utility.help.command("rfa lvl ", "Search for a room in all zones.");
+ print();
+
+ utility.help.command("mobber show zone ", "Show information for a single zone.");
+ utility.help.command("mobber show zones sort ", "Show all of the zones in the database.");
+ utility.help.command("", " can be 'mobs', 'rooms' or 'level'.");
+ print("\r\n\r\n");
+
+ ColourNote("silver", "", "Note: Most arguments/parameters are optional.");
+ print("\r\n\r\n");
+end
+
+function utility.help.utils()
+ print();
+
+ ColourNote(pluginPalette.border, "", string.rep("-", 7), pluginPalette.helpTitles, "", " Mobber Utilities ", pluginPalette.border, "", string.rep("-", 7));
+ print();
+
+ utility.help.command("xrt ", "Run to the marked room of a zone.");
+ utility.help.command("xset mark", "Mark the landing room of a zone.");
+ print();
+
+ utility.help.command("qs ", "Quick scan for current target.");
+ utility.help.command("", " is optional. Default is target.");
+ print();
+
+ utility.help.command("qw ", "Quick where a mob to get its location.");
+ utility.help.command("", " is optional. Default is target.");
+ print();
+
+ utility.help.command("ak", "Send the set autokill command.");
+ utility.help.command("ak ", "Set the command to use for autokill.");
+ utility.help.command("toggle ak", "Toggle appending the current target.");
+ print();
+
+ utility.help.command("ah ", "Auto-hunt a mob. No arg will abort hunt.");
+ utility.help.command("ht ", "Hunt-trick a mob. No arg will abort hunt.");
+ print();
+
+ utility.help.command("mobber noexp ", "Auto noexp. Default is 1000 exp threshold.");
+ print();
+
+ utility.help.command("mobber color ", "Options: red, blue, green, yellow, purple.");
+ print();
+
+ utility.help.command("mobber btncolor window", "Change text color of the window buttons.");
+ utility.help.command("mobber hide|show window", "Show or hide the window.");
+ utility.help.command("mobber reset window", "Reset the window if it goes off-screen.");
+ print("\r\n\r\n");
+
+ ColourNote("silver", "", "Note: Window commands require the miniwindow plugin to be installed.");
+ print("\r\n\r\n");
+end
+
+function utility.help.developer()
+ print();
+
+ ColourNote(pluginPalette.border, "", string.rep("-", 7), pluginPalette.helpTitles, "", " Mobber Developer ", pluginPalette.border, "", string.rep("-", 7));
+ print();
+
+ utility.help.command("mobber developer", "Enable or disable developer mode.");
+ utility.help.command("mobber backup", "Manually force a backup of the database.");
+ utility.help.command("mobber vacuum", "Defragment and reduce size of the db.");
+ print();
+
+ utility.help.command("mobber addzone ", "Add the current zone to the database.");
+ utility.help.command("mobber update lvlrange <#> <#>", "Update a zone's level range.");
+ print();
+
+ utility.help.command("mobber update zones", "Update zones with default values.");
+ utility.help.command("mobber update rooms", "Update rooms from Aardwolf.db to the database.");
+ print();
+
+ utility.help.command("mobber addmob ", "Add a mob to the current room.");
+ utility.help.command("mobber consider", "Toggle logging mobs with 'consider' command.");
+ print();
+
+ utility.help.command("mobber removemob single ", "Remove matching mobs from the current zone.");
+ utility.help.command("mobber removemob room", "Remove all mobs from the current room.");
+ utility.help.command("mobber removemob zone", "Remove all mobs from the current zone.");
+ utility.help.command("mobber remove zone", "Remove the current zone from the database.");
+ print();
+
+ utility.help.command("mobber update keywords", "Update keywords for all mobs in the database.");
+ utility.help.command("mobber keyword mob ", "Update keyword for a single mob.");
+ utility.help.command("mobber clean keywords", "Remove unused keywords from the database.");
+ utility.help.command("mobber priority <#> mob ", "Update priority of a mob in the current zone.");
+ print("\r\n\r\n");
+
+
+ ColourNote("silver", "", "Note: See the README.txt before using developer commands.");
+ print("\r\n\r\n");
+end
+
+function utility.help.command(cmd, desc)
+ ColourNote(pluginPalette.helpCommand, "", string.format('%-31s', cmd), pluginPalette.border, "", " : ", pluginPalette.helpDescription, "", desc);
+end
+
+----------------------------------
+-- Mob Target Prototype
+----------------------------------
+
+mobTarget = {
+ name = "",
+ keyword = "",
+ location = "",
+ isDead = false,
+ lookupType = "",
+ amount = 1,
+ rooms = {}
+};
+
+function mobTarget:new(mob)
+ mob = mob or {};
+ setmetatable(mob, self);
+ self.__index = self;
+ return mob;
+end
+
+function mobTarget:initialize()
+ self.keyword = database.lookupMobKeyword(self.name) or utility.getMobKeyword(self.name);
+
+ local lookups = {
+ database.lookupMobRoomOrAreaByLvl,
+ database.lookupMobRoomOrArea,
+ database.lookupArea,
+ database.lookupRoomAreaByLvl,
+ database.lookupRoomArea
+ };
+
+ for k,v in ipairs(lookups) do
+ self.rooms, self.lookupType = v(self.location, self.name);
+
+ if (next(self.rooms)) then
+ break;
+ end
+ end
+
+ if (not next(self.rooms)) then
+ self.lookupType = "none";
+ table.insert(self.rooms, {zone_name = self.location});
+ end
+end
+
+----------------------------------
+-- Color Palette
+----------------------------------
+
+colorPalette = {};
+
+function colorPalette.initialize()
+ AddAlias("alias_color_palette_set_color", "^mobber color (?.+?)$", "",
+ alias_flag.Enabled + alias_flag.IgnoreAliasCase + alias_flag.RegularExpression + alias_flag.Temporary,
+ "colorPalette.setColor"
+ );
+
+ if (var.pluginPalette) then
+ pluginPalette = loadstring("return " .. var.pluginPalette)();
+ else
+ pluginPalette = colorPalette.getDefault();
+ var.pluginPalette = serialize.save_simple(pluginPalette);
+ end
+end
+
+function colorPalette.getDefault()
+ return {
+ border = "maroon",
+ header = "white",
+ indexNumbering = "white",
+ deadMob = "red",
+ foundMobRoom = "paleturquoise",
+ foundArea = "deepskyblue",
+ roomOrMobAll = "darkgray",
+ missingAreaRoom = "yellow",
+ rooms = "paleturquoise",
+ highlightTarget = "red",
+ highlightTargetParentheses = "#4D4D4D",
+ mobber = "white",
+ mobberParentheses = "maroon",
+ helpDescription = "white",
+ helpTitles = "darkgray",
+ helpCommand = "firebrick"
+ };
+end
+
+function colorPalette.setColor(name, line, wildcards)
+ local color = wildcards.color;
+
+ if (color == "green") then
+ pluginPalette.border = "forestgreen";
+ pluginPalette.highlightTarget = "lime";
+ pluginPalette.mobberParentheses = "forestgreen";
+ pluginPalette.helpCommand = "chartreuse";
+ elseif (color == "blue") then
+ pluginPalette.border = "steelblue";
+ pluginPalette.highlightTarget = "lightskyblue";
+ pluginPalette.mobberParentheses = "blue";
+ pluginPalette.helpCommand = "mediumturquoise";
+ elseif (color == "purple") then
+ pluginPalette.border = "slateblue";
+ pluginPalette.highlightTarget = "blueviolet";
+ pluginPalette.mobberParentheses = "darkorchid";
+ pluginPalette.helpCommand = "mediumorchid";
+ elseif (color == "yellow") then
+ pluginPalette.border = "#B1B55F";
+ pluginPalette.highlightTarget = "yellow";
+ pluginPalette.mobberParentheses = "khaki";
+ pluginPalette.helpCommand = "#F3FF00";
+ else
+ color = "default";
+ pluginPalette = colorPalette.getDefault();
+ end
+
+ var.pluginPalette = serialize.save_simple(pluginPalette);
+
+ utility.print("Color scheme set to: " .. color);
+end
+
+----------------------------------
+-- Mushclient Plugin Callbacks
+----------------------------------
+
+function OnPluginInstall()
+ utility.initialize();
+end
+
+function OnPluginConnect()
+ Send_GMCP_Packet("request char");
+ Send_GMCP_Packet("request room");
+ Send_GMCP_Packet("request quest");
+end
+
+function OnPluginClose()
+ utility.deinitialize();
+end
+
+function OnPluginEnable()
+ OnPluginInstall();
+ OnPluginConnect();
+end
+
+function OnPluginDisable()
+ OnPluginClose();
+end
+
+function OnPluginBroadcast(msg, id, name, text)
+ if (id == "3e7dedbe37e44942dd46d264") then
+ if (text == "char.status") then
+ utility.updateEnemy();
+ elseif (text == "room.area" and database.area and database.area.isAddingArea) then
+ local zoneName = gmcp("room.area.id");
+ local areaName = gmcp("room.area.name");
+
+ database.addArea(zoneName, areaName);
+ elseif (text == "comm.quest") then
+ local newQuest = gmcp("comm.quest");
+
+ if (newQuest.action == "ready") then
+ if (not quest.isMuted) then
+ local commMsg = quest.commPrefix .. " @WAvailable!@w";
+ CallPlugin("b555825a4a5700c35fa80780", "storeFromOutside", commMsg);
+ end
+ elseif (newQuest.action == "start") then
+ quest.start(newQuest);
+ elseif (newQuest.action == "status") then
+ quest.showRequest(newQuest);
+ elseif (newQuest.action == "fail") then
+ quest.fail(newQuest);
+ elseif (newQuest.action == "comp") then
+ quest.complete(newQuest);
+ end
+ elseif (text == "config") then
+ noexp.isNoExp = gmcp("config.noexp") == "YES" and true or false;
+ end
+ end
+end
\ No newline at end of file
diff --git a/lua/mw_theme_base.lua b/lua/mw_theme_base.lua
new file mode 100644
index 0000000..342600e
--- /dev/null
+++ b/lua/mw_theme_base.lua
@@ -0,0 +1,500 @@
+--[[
+This file contains code that that allows many different miniwindow plugins
+to maintain the same UI theme, as long as they follow certain guidelines.
+We can use this to get a base set of colors, standard display elements,
+and title fonts that get used everywhere in order to unify the visual style
+and provide a way to customize that style easily.
+
+Steps for use: (also see https://github.com/fiendish/aardwolfclientpackage/wiki/Miniwindow-Color-Themes )
+1) Inside your plugins, require this file at the very beginning of your script section.
+2) Use variable names as spelled out in lua/mw_themes/Charcoal.lua for colorization within the "Theme." namespace, e.g. Theme.BODY_TEXT etc.
+3) Use the shared graphics functions defined below for drawing various elements with the current color theme.
+ Some of the functions are not for drawing things. You can ignore them.
+4) Four things make a miniwindow fit the theme: The border, the titlebar, the resize widget, and the general colors being used.
+ The border, titlebar, resize widget, and 3D boxes all have special draw functions included below. The colors are defined in the theme files.
+4) Optional: Make your own themes (clone one of the files in lua/mw_themes and customize your colors).
+5) Optional: If your plugin wants to preserve state between theme changes (changing theme reloads the plugin),
+ you can detect whether the plugin is closing because of a theme change with GetVariable(Theme.reloading_variable).
+--]]
+require "checkplugin"
+require "movewindow"
+
+dofile(GetInfo(60) .. "aardwolf_colors.lua");
+
+module ("Theme", package.seeall)
+
+function b9315e040989d3f81f4328d6()
+ -- used for theme system detection
+ return true
+end
+
+theme_dir = GetInfo(66).."lua\\mw_themes\\"
+theme_file = "Charcoal.lua"
+
+function get_theme()
+ return theme_file
+end
+
+reloading_variable = "aard_theme_just_reloading"
+
+function just_reloading()
+ SetVariable(reloading_variable, 1)
+end
+
+local default_theme = {
+ LOGO_OPACITY = 0.02,
+
+ PRIMARY_BODY = 0x0c0c0c,
+ SECONDARY_BODY = 0x777777,
+ BODY_TEXT = 0xe8e8e8,
+
+ CLICKABLE = 0x666666,
+ CLICKABLE_HOVER = 0x444444,
+ CLICKABLE_HOT = 0x40406b,
+ CLICKABLE_TEXT = 0xc8c8c8,
+ CLICKABLE_HOVER_TEXT = 0xdddddd,
+ CLICKABLE_HOT_TEXT = 0xcfc5df,
+
+ TITLE_PADDING = 2,
+
+ THREE_D_HIGHLIGHT = 0xe8e8e8,
+
+ THREE_D_GRADIENT = miniwin.gradient_vertical,
+ THREE_D_GRADIENT_FIRST = 0xcdced1,
+ THREE_D_GRADIENT_SECOND = 0x8c8c8c,
+ THREE_D_GRADIENT_ONLY_IN_TITLE = false,
+
+ THREE_D_SOFTSHADOW = 0x606060,
+ THREE_D_HARDSHADOW = 0x303030,
+ THREE_D_SURFACE_DETAIL = 0x050505,
+
+ SCROLL_TRACK_COLOR1 = 0x444444,
+ SCROLL_TRACK_COLOR2 = 0x888888,
+ VERTICAL_TRACK_BRUSH = miniwin.brush_hatch_forwards_diagonal,
+
+ DYNAMIC_BUTTON_PADDING = 20,
+ RESIZER_SIZE = 16
+}
+
+function load_theme(file)
+ local file_loaded, data_from_file = pcall(dofile, theme_dir..file)
+
+ -- init defaults
+ for k,v in pairs(default_theme) do
+ Theme[k] = v
+ end
+
+ if file_loaded then
+ if type(data_from_file) == "table" then
+ for k,v in pairs(data_from_file) do
+ Theme[k] = v
+ end
+ theme_file = file
+ else
+ print("Error loading theme file: ", theme_dir..file)
+ print("This theme file is invalid. Please delete it and select a different theme.")
+ print()
+ end
+ else
+ print("Error loading theme file: ", theme_dir..file)
+ print(data_from_file) -- error message
+ end
+end
+
+-- junk files that should not have existed in the first place
+local bad_shas = {
+ ["default.lua"] = {
+ ["d189be39efb49730537c8ada65fbd5382d3e44ad"] = true,
+ ["39d9b7fc4d571ac62d1005b52f8da43c4b7827e1"] = true,
+ ["774bdd2f098f1c4fe81d9a6b8eb5bfad6bd79c51"] = true,
+ ["b7fd919be678f0c7b1c52bea644f183a7b9397f1"] = true,
+ ["acc1d615901050b2faa50e1b0497ee30b72f5118"] = true,
+ ["ac993b349691eb51e472ca5b1b0ae18a03943bf2"] = true,
+ ["0cd5bcca0723a57eabedd08a9cfa6f4ec93ea469"] = true
+ },
+ ["dark_pony.lua"] = {
+ ["d189be39efb49730537c8ada65fbd5382d3e44ad"] = true
+ },
+ ["Joker.lua"] = {
+ ["17fa722746df781326b100744dd790c66721b749"] = true,
+ ["b2b83804ce87eb889127658cb80c9d23d76082b4"] = true
+ }
+}
+
+require "gitsha"
+function theme_has_bad_sha(filename)
+ return bad_shas[filename] and bad_shas[filename][gitsha(theme_dir..filename)]
+end
+
+local theme_controller_ID = "b9315e040989d3f81f4328d6"
+local theme_controller_name = "aard_Theme_Controller"
+if (GetPluginID() ~= theme_controller_ID) then
+ if not IsPluginInstalled(theme_controller_ID) then
+ local inner_action = [[DoAfterSpecial(0.1, 'require \'checkplugin\';do_plugin_check_now(\']]..theme_controller_ID..[[\', \']]..theme_controller_name..[[\')', sendto.script)]]
+
+ -- execute_in_global_space(inner_action)
+ local prefix = GetAlphaOption("script_prefix")
+ local action = [[
+ SetAlphaOption("script_prefix", "/")
+ Execute("/]] .. inner_action:gsub("\\", "\\\\") .. [[")
+ SetAlphaOption("script_prefix", "]] .. prefix:gsub("\\", "\\\\") .. [[")
+ ]]
+ DoAfterSpecial(0.1, action, sendto.script)
+ end
+
+ local maybe_theme_file = GetPluginVariable(theme_controller_ID, "theme_file") or theme_file
+
+ if not theme_has_bad_sha(maybe_theme_file) then
+ theme_file = maybe_theme_file
+ end
+
+ load_theme(theme_file)
+end
+
+-- Replacement for WindowRectOp action 5, which allows for a 3D look while maintaining color theme.
+function Draw3DRect (win, left, top, right, bottom, depressed)
+ local gradient = (not THREE_D_GRADIENT_ONLY_IN_TITLE or __theme_istitle) and THREE_D_GRADIENT or false
+ __theme_istitle = false
+
+ if right > 0 then
+ right = right + 1
+ end
+ if bottom > 0 then
+ bottom = bottom + 1
+ end
+
+ if gradient and (THREE_D_GRADIENT_FIRST == THREE_D_GRADIENT_SECOND) then
+ gradient = false
+ end
+
+ if (gradient == 1) or (gradient == 2) or (gradient == 3) then
+ WindowGradient(win, left, top, right, bottom,
+ THREE_D_GRADIENT_FIRST,
+ THREE_D_GRADIENT_SECOND,
+ gradient)
+ else
+ WindowRectOp(win, 2, left, top, right, bottom, THREE_D_GRADIENT_FIRST)
+ end
+
+ if not depressed then
+ WindowLine(win, left+1, top+1, right, top+1, THREE_D_HIGHLIGHT, 0x0200, 1)
+ WindowLine(win, left+1, top+1, left+1, bottom, THREE_D_HIGHLIGHT, 0x0200, 1)
+
+ WindowLine(win, left, bottom-2, right, bottom-2, THREE_D_SOFTSHADOW, 0x0200, 1)
+ WindowLine(win, right-2, top, right-2, bottom-2, THREE_D_SOFTSHADOW, 0x0200, 1)
+
+ WindowLine(win, left, bottom-1, right, bottom-1, THREE_D_HARDSHADOW, 0x0200, 1)
+ WindowLine(win, right-1, top, right-1, bottom-1, THREE_D_HARDSHADOW, 0x0200, 1)
+ else
+ WindowLine(win, left, top+1, right, top+1, THREE_D_HARDSHADOW, 0x0200, 1)
+ WindowLine(win, left+1, top, left+1, bottom, THREE_D_HARDSHADOW, 0x0200, 1)
+
+ WindowLine(win, left, top, right, top, THREE_D_HARDSHADOW, 0x0200, 1)
+ WindowLine(win, left, top, left, bottom, THREE_D_HARDSHADOW, 0x0200, 1)
+ end
+end
+
+function Draw3DTextBox(win, font, left, top, text, utf8)
+ local right = left + WindowTextWidth(win, font, text)
+ local bottom = top + TextHeight(win, font)
+ Draw3DRect(win, left, top, right+3, bottom+3)
+ WindowText(win, font, text, left+2, top+2, right+1, bottom+1, THREE_D_SURFACE_DETAIL, utf8)
+ return right-left
+end
+
+function DrawTextBox(win, font, left, top, text, utf8, outlined, bgcolor, textcolor)
+ if nil == bgcolor then
+ bgcolor = CLICKABLE
+ end
+ if nil == textcolor then
+ textcolor = CLICKABLE_TEXT
+ end
+ local right = left + WindowTextWidth(win, font, text) + 4
+ local bottom = top + TextHeight(win, font)
+ WindowRectOp(win, 2, left, top+1, right, bottom+2, bgcolor)
+ if outlined then
+ WindowRectOp(win, 1, left-1, top, right+1, bottom+3, textcolor)
+ end
+ WindowText(win, font, text, left+2, top+1, right, bottom+1, textcolor, utf8)
+ return right-left
+end
+
+function AddResizeTag(win, type, x1, y1, mousedown_callback, dragmove_callback, dragrelease_callback)
+ local x1, y1 = DrawResizeTag(win, type, x1, y1)
+
+ -- Add handler hotspots
+ if WindowMoveHotspot(win, "resize", x1, y1, 0, 0) ~= 0 then
+ WindowAddHotspot(win, "resize", x1, y1, 0, 0, nil, nil, mousedown_callback, nil, nil, "", 6, 0)
+ WindowDragHandler(win, "resize", dragmove_callback, dragrelease_callback, 0)
+ end
+
+ return x1, y1
+end
+
+function DrawResizeTag(win, type, x1, y1)
+ local x2, y2
+ if not (x1 and y1) then
+ x2 = WindowInfo(win, 3) - 3
+ y2 = WindowInfo(win, 4) - 3
+ x1 = x2 - RESIZER_SIZE + 1
+ y1 = y2 - RESIZER_SIZE + 1
+ else
+ x2 = x1 + RESIZER_SIZE
+ y2 = y1 + RESIZER_SIZE
+ end
+
+ local m = 2
+ local n = 2
+ if (type == 2) or (type == "full") then
+ Draw3DRect(win, x1, y1, x2, y2, false)
+ while (x1+m < x2 and y1+n+1 < y2) do
+ WindowLine(win, x1+m-1, y2-2, x2-1, y1+n-2, THREE_D_HIGHLIGHT, 0, 1)
+ WindowLine(win, x1+m, y2-2, x2-1, y1+n-1, THREE_D_HARDSHADOW, 0, 1)
+ WindowLine(win, x1+m+1, y2-2, x2-1, y1+n, THREE_D_GRADIENT_FIRST, 0, 1)
+ m = m+3
+ n = n+3
+ end
+ WindowSetPixel(win, x2-2, y2-2, THREE_D_HIGHLIGHT)
+ else
+ while (x1+m < x2 and y1+n+1 < y2) do
+ WindowLine(win, x1+m, y2-1, x2, y1+n-1, THREE_D_HIGHLIGHT, 0, 1)
+ WindowLine(win, x1+m+1, y2-1, x2, y1+n, THREE_D_HARDSHADOW, 0, 1)
+ WindowLine(win, x1+m+2, y2-1, x2, y1+n+1, THREE_D_GRADIENT_FIRST, 0, 1)
+ m = m+3
+ n = n+3
+ end
+ WindowLine(win, x1, y2, x2, y2, THREE_D_HARDSHADOW, 0, 1)
+ WindowLine(win, x2, y1, x2, y2+1, THREE_D_HARDSHADOW, 0, 1)
+ WindowSetPixel(win, x2-1, y2-1, THREE_D_HIGHLIGHT)
+ end
+ return x1, y1
+end
+
+function TextHeight(win, font)
+ return WindowFontInfo(win, font, 1)
+end
+
+-- title_alignment can be "left", "right", or "center" (the default)
+function DressWindow(win, font, title, title_alignment)
+ local l, t, r, b = DrawBorder(win)
+
+ if title and (title ~= "") then
+ t = DrawTitleBar(win, font, title, title_alignment)
+ if t > 1 then
+ movewindow.add_drag_handler(win, 0, 0, 0, t)
+ end
+ end
+
+ return l, t, r, b
+end
+
+function DrawTitleBar(win, font, title, text_alignment)
+ local title_lines
+ if type(title) == "string" then
+ title_lines = utils.split(title, "\n")
+ else
+ title_lines = title
+ end
+
+ local line_height = 0
+ local title_height = 0
+ if font and title_lines then
+ line_height = TextHeight(win, font)
+ if line_height == nil then
+ return (2*TITLE_PADDING)
+ end
+ title_height = (2*TITLE_PADDING) + (line_height * #title_lines)
+ end
+
+ __theme_istitle = true
+ Draw3DRect(
+ win,
+ -1,
+ -1,
+ WindowInfo(win, 3)-1,
+ title_height,
+ false
+ )
+
+ local first_color = nil
+ local txt = nil
+ for i,v in ipairs(title_lines) do
+ if type(v) == "table" then
+ txt = strip_colours_from_styles(v)
+ else
+ txt = v
+ end
+ local width = WindowTextWidth(win, font, txt)
+
+ local text_left = (WindowInfo(win, 3) - width) / 2 -- default text align center
+ if text_alignment == "left" then
+ text_left = TITLE_PADDING + 2
+ elseif text_alignment == "right" then
+ text_left = WindowInfo(win, 3) - width - TITLE_PADDING
+ end
+ local text_right = math.min(text_left + width, WindowInfo(win, 3) - TITLE_PADDING - 2)
+
+ local text_top = (line_height * (i-1)) + TITLE_PADDING
+ if type(v) == "string" then
+ WindowText(win, font, v, text_left, text_top, text_right, title_height, THREE_D_SURFACE_DETAIL)
+ else
+ -- The colors of all styles matching the first style color get stripped out and replaced with the default title color
+ for i,w in ipairs(v) do
+ first_color = first_color or w.textcolour
+ if w.textcolour == first_color then
+ w.textcolour = THREE_D_SURFACE_DETAIL
+ end
+ end
+ WindowTextFromStyles(win, font, v, text_left, text_top, text_right, title_height, true)
+ end
+ end
+ return title_height
+end
+
+function DrawBorder(win)
+ local r = WindowInfo(win, 3)-3
+ local b = WindowInfo(win, 4)-3
+ WindowRectOp(win, 1, 0, 0, 0, 0, THREE_D_HIGHLIGHT)
+ WindowRectOp(win, 1, 1, 1, -1, -1, THREE_D_SOFTSHADOW)
+ return 2, 2, r, b
+end
+
+function OutlinedText(win, font, text, startx, starty, endx, endy, color, outline_color, utf8, thickness)
+ if thickness == nil then
+ thickness = 1
+ end
+ if outline_color == nil then
+ outline_color = THREE_D_HARDSHADOW
+ end
+ local right = nil
+ for xi = -thickness,thickness do
+ for yi = -thickness,thickness do
+ right = WindowText(win, font, text, startx+xi, starty+yi, endx+1, endy+1, outline_color, utf8)
+ end
+ end
+ local right = WindowText(win, font, text, startx+1, starty+1, endx, endy, outline_color, utf8)
+ WindowText(win, font, text, startx, starty, endx, endy, color, utf8)
+ return right
+end
+
+function WindowTextFromStyles(win, font, styles, left, top, right, bottom, utf8)
+ for i,v in ipairs(styles) do
+ left = left + WindowText(win, font, v.text, left, top, right, bottom, v.textcolour or BODY_TEXT, utf8)
+ end
+ return left
+end
+
+-- text with a black outline
+function OutlinedTextFromStyles(win, font, styles, startx, starty, endx, endy, outline_color, utf8, thickness)
+ if thickness == nil then
+ thickness = 1
+ end
+ if outline_color == nil then
+ outline_color = THREE_D_HARDSHADOW
+ end
+ local text = strip_colours_from_styles(styles)
+ local right = nil
+ for xi = -thickness,thickness do
+ for yi = -thickness,thickness do
+ right = WindowText(win, font, text, startx+xi, starty+yi, endx+1, endy+1, outline_color, utf8)
+ end
+ end
+ WindowTextFromStyles(win, font, styles, startx, starty, endx, endy, utf8)
+ return right
+end
+
+-- Based on mw.lua's popup function, but with theme colors
+function Popup(win, -- window name to use
+ font_id, -- font to use for each body line
+ info, -- table of lines to show (plain text or styles)
+ left, top, -- preferred location
+ stay_left_of, -- guidance for keeping the popup visible
+ stay_right_of) -- guidance for keeping the popup visible
+
+ local BORDER_WIDTH = 2
+
+ assert(WindowInfo (win, 1), "Window " .. win .. " must already exist")
+ assert(WindowFontInfo (win, font_id, 1), "No font " .. font_id .. " in " .. win)
+
+ local font_height = WindowFontInfo (win, font_id, 1)
+ local font_leading = WindowFontInfo (win, font_id, 4) + WindowFontInfo (win, font_id, 5)
+
+ -- find text width - minus colour codes
+ local infowidth = 0
+ local infoheight = 0
+
+ -- calculate remaining width and height
+ for _, v in ipairs (info) do
+ if type(v) == "table" then
+ txt = strip_colours_from_styles(v)
+ else
+ txt = strip_colours(v)
+ end
+ infowidth = math.max (infowidth, WindowTextWidth (win, font_id, txt))
+ infoheight = infoheight + font_height
+ end -- for
+
+ infowidth = infowidth + (2 * BORDER_WIDTH) + -- leave room for border
+ WindowFontInfo (win, font_id, 6) -- one character width extra
+
+ infoheight = infoheight + (2 * BORDER_WIDTH) + -- leave room for border
+ font_leading + -- plus leading below bottom line,
+ 10 -- and 5 pixels top and bottom
+
+ -- if align_right then
+ -- left = left - infowidth
+ -- end -- if align_right
+
+ -- if align_bottom then
+ -- top = top - infoheight
+ -- end -- if align_bottom
+
+ top = math.min(top, GetInfo(280) - infoheight)
+ top = math.max(0, top)
+ if left < stay_left_of then
+ if left+infowidth > stay_left_of then
+ left = stay_left_of - infowidth
+ end
+ if left < 0 then
+ left = stay_right_of
+ end
+ else
+ if left < stay_right_of then
+ left = stay_right_of
+ end
+ if (left + infowidth) > GetInfo(281) then
+ left = stay_left_of - infowidth
+ end
+ end
+ WindowCreate(win,
+ left, top, -- where
+ infowidth, -- width (gap of 5 pixels per side)
+ infoheight, -- height
+ miniwin.pos_top_left, -- position mode: can't be 0 to 3
+ miniwin.create_absolute_location + miniwin.create_transparent,
+ SECONDARY_BODY)
+
+ WindowCircleOp(win, miniwin.circle_round_rectangle,
+ BORDER_WIDTH, BORDER_WIDTH, -BORDER_WIDTH, -BORDER_WIDTH, -- border inset
+ THREE_D_HIGHLIGHT, miniwin.pen_solid, BORDER_WIDTH, -- line
+ PRIMARY_BODY, miniwin.brush_solid, -- fill
+ 5, 5) -- diameter of ellipse
+
+ local x = BORDER_WIDTH + WindowFontInfo (win, font_id, 6) / 2 -- start 1/2 character in
+ local y = BORDER_WIDTH + 5 -- skip border, and leave 5 pixel gap
+
+ -- show each line
+ for _, line in ipairs(info) do
+ if type(line) == "string" then
+ WindowText(win, font_id, line, x, y, 0, 0, BODY_TEXT)
+ else
+ WindowTextFromStyles(win, font_id, line, x, y, 0, 0)
+ end
+
+ y = y + font_height
+ end -- for
+
+ -- display popup window
+ WindowShow(win, true)
+
+end -- popup
diff --git a/lua/sqlitedb.lua b/lua/sqlitedb.lua
new file mode 100644
index 0000000..2f0c387
--- /dev/null
+++ b/lua/sqlitedb.lua
@@ -0,0 +1,177 @@
+----------------------------------
+-- sqlite3 Helper Module
+----------------------------------
+
+sqlitedb = {
+ path = GetPluginInfo(GetPluginID(), 20),
+ name = "sqlite.db",
+ db = nil
+};
+
+function sqlitedb:new(db)
+ db = db or {};
+ setmetatable(db, self);
+ self.__index = self;
+ return db;
+end
+
+function sqlitedb:open()
+ if (self.db == nil or not self.db:isopen()) then
+ self.db = assert(sqlite3.open(self.path .. self.name));
+ self:pragma();
+ end
+end
+
+function sqlitedb:close()
+ if (self.db) then
+ if (self.db:isopen()) then
+ local code = self.db:close();
+ self:check(code);
+ end
+
+ self.db = nil;
+ end
+end
+
+function sqlitedb:check(code)
+ if (code ~= sqlite3.OK and
+ code ~= sqlite3.ROW and
+ code ~= sqlite3.DONE) then
+
+ local err = self.db:errmsg();
+ self.db:execute("ROLLBACK;");
+ error("REPORT THIS ERROR TO RAURU:\r\n" .. err, 2);
+ end
+end
+
+function sqlitedb:exec(sql, checkCode, callback)
+ assert(sqlite3.complete(sql), "Not an SQL statement: " .. sql);
+
+ local code = self.db:execute(sql, callback);
+
+ if (checkCode) then
+ self:check(code);
+ end
+
+ return code;
+end
+
+function sqlitedb:pragma()
+ self:exec("PRAGMA foreign_keys=ON;", true);
+end
+
+function sqlitedb:gettable(sql)
+ assert(sqlite3.complete(sql), "Not an SQL statement:\r\n" .. sql);
+
+ local results = {};
+
+ for row in self.db:nrows(sql) do
+ table.insert(results, row);
+ end
+
+ return results;
+end
+
+function sqlitedb:changes()
+ return self.db:changes();
+end
+
+function sqlitedb:backup()
+ self:open();
+
+ local isDatabaseBackedUp;
+
+ print("\r\n\r\nPERFORMING", self.name, "DATABASE BACKUP.");
+ print("CHECKING INTEGRITY...");
+
+ BroadcastPlugin(999, "repaint");
+
+ local integrityCheck = true;
+
+ self:exec("PRAGMA wal_checkpoint;", true);
+
+ for row in self.db:nrows("PRAGMA integrity_check;") do
+ if (row.integrity_check ~= "ok") then
+ integrityCheck = false;
+ end
+ end
+
+ self:close();
+
+ if (not integrityCheck) then
+ isDatabaseBackedUp = false;
+
+ print("INTEGRITY CHECK FAILED. CLOSE MUSHCLIENT AND RESTORE A KNOWN GOOD DATABASE.");
+ print("BACKUP ABORTED.\r\n\r\n");
+ else
+ print("INTEGRITY CHECK PASSED.");
+
+ BroadcastPlugin (999, "repaint");
+
+ local backupDir = self.path .. "db_backups\\";
+
+ local makeDirCmd = "mkdir " .. addQuotes(backupDir);
+
+ os.execute(makeDirCmd);
+
+ local copyCmd = "copy /Y " .. addQuotes(self.path .. self.name) .. " " .. addQuotes(backupDir .. self.name .. "." .. "backup");
+
+ os.execute(copyCmd);
+
+ print("FINISHED DATABASE BACKUP.\r\n\r\n");
+
+ self:open();
+
+ isDatabaseBackedUp = true;
+ end
+
+ return isDatabaseBackedUp;
+end
+
+function sqlitedb:vacuum()
+ local fileSizeBefore;
+ local fileSizeAfter;
+
+ self:open();
+
+ local file = io.open(self.path .. self.name);
+
+ fileSizeBefore = file and fsize(file) or nil;
+
+ print("BEGIN VACUUM ON", self.name);
+
+ local code = self:exec("VACUUM;");
+
+ if (code ~= sqlite3.OK) then
+ print("END VACUUM ON", self.name);
+ local err = self.db:errmsg();
+ self.db:execute("ROLLBACK;");
+ file:close();
+ error(err);
+ else
+ fileSizeAfter = file and fsize(file) or nil;
+ file:close();
+
+ if (fileSizeBefore and fileSizeAfter) then
+ fileSizeBefore = fileSizeBefore / 1024;
+ fileSizeAfter = fileSizeAfter / 1024;
+
+ local fileSizeDif = fileSizeBefore - fileSizeAfter;
+
+ print("DISK SPACE RECOVERED: " .. fileSizeDif .. " KB");
+ end
+
+ print("END VACUUM ON", self.name);
+ end
+end
+
+function fsize(file)
+ local current = file:seek(); -- get current position
+ local size = file:seek("end"); -- get file size
+ file:seek("set", current); -- restore position
+ return size;
+end
+
+function addQuotes(str)
+ return "\"" .. str .. "\"";
+end
\ No newline at end of file
diff --git a/lua/stringutils.lua b/lua/stringutils.lua
new file mode 100644
index 0000000..d5c21f4
--- /dev/null
+++ b/lua/stringutils.lua
@@ -0,0 +1,113 @@
+--[[
+String Functions
+
+functions in this module
+
+fixsql - usage: fixsql(sql, likeOperator)
+ change quotes to double single quotes
+
+stripColors - usage: stripColors(str)
+ remove color codes from string
+
+toPascalCase - usage toPascalCase(str)
+ captialize the beginning of each word
+
+formatSeconds - usage formatSeconds(seconds)
+ convert seconds to hrs mins secs
+--]]
+
+
+function fixsql(sql, likeOperator)
+ if (sql) then
+ if (likeOperator) then
+ if (likeOperator == "left") then
+ sql = "'%" .. string.gsub(sql, "'", "''") .. "'";
+ elseif (likeOperator == "right") then
+ sql = "'" .. string.gsub(sql, "'", "''") .. "%'";
+ else
+ sql = "'%" .. string.gsub(sql, "'", "''") .. "%'";
+ end
+ else
+ sql = "'" .. string.gsub(sql, "'", "''") .. "'";
+ end
+ else
+ sql = "NULL;"
+ end
+
+ return sql;
+end
+
+function stripColors(str)
+ str = str:gsub("@@", "\0"); -- change @@ to 0x00
+ str = str:gsub("@%-", "~"); -- fix tildes (historical)
+ str = str:gsub("@x%d?%d?%d?", ""); -- strip valid and invalid xterm color codes
+ str = str:gsub("@.([^@]*)", "%1"); -- strip normal color codes and hidden garbage
+ return (str:gsub("%z", "@")); -- put @ back (has parentheses on purpose)
+end
+
+function toPascalCase(str)
+ str = string.gsub(str, "(%a)([%w_']*)",
+ function (first, rest)
+ return first:upper()..rest:lower();
+ end
+ );
+ return str;
+end
+
+function wrap(line, length)
+ local lines = {};
+
+ length = length or 10;
+
+ while (#line > length) do
+ -- find a space not followed by a space, or a , closest to the end of the line
+ local col = string.find(line:sub(1, length), "[%s,][^%s,]*$");
+
+ if (col and col > 2) then
+ -- col = col - 1 -- use the space to indent
+ else
+ col = length -- just cut off at wrap_column
+ end -- if
+
+ table.insert(lines, line:sub(1, col));
+ line = line:sub(col + 1);
+ end
+
+ table.insert(lines, line);
+
+ return lines;
+end
+
+function formatSeconds(seconds)
+ if (not tonumber(seconds)) then
+ return seconds;
+ end
+
+ if (seconds < 1) then
+ return string.format("%.2fs", seconds);
+ end
+
+ local hours = math.floor(seconds / 3600);
+
+ seconds = seconds % 3600
+
+ local mins = math.floor(seconds / 60);
+
+ seconds = math.floor(seconds % 60);
+
+ local duration = "";
+
+ if (hours > 0) then
+ duration = hours .. "h ";
+ end
+
+ if (mins > 0) then
+ duration = duration .. mins .. "m ";
+ end
+
+ if (seconds > 0) then
+ duration = duration .. seconds .. "s";
+ end
+
+ return duration;
+end
\ No newline at end of file
diff --git a/resources/README.txt b/resources/README.txt
new file mode 100644
index 0000000..c95a178
--- /dev/null
+++ b/resources/README.txt
@@ -0,0 +1,5 @@
+Customize Background
+--------------------
+
+Replace the bg.png image with a png of your own, same file name.
+Image should be at least 350 x 270px.
\ No newline at end of file
diff --git a/resources/bg.png b/resources/bg.png
new file mode 100644
index 0000000..0368cd0
Binary files /dev/null and b/resources/bg.png differ