· 7 years ago · Jan 16, 2019, 05:30 AM
1<?xml version="1.0" encoding="iso-8859-1"?>
2<!DOCTYPE muclient [
3 <!ENTITY show_vnums "true" >
4 <!ENTITY show_timing "false" >
5 <!ENTITY show_completed "false" >
6 <!ENTITY show_database_mods "true" >
7 <!ENTITY show_other_areas "false" >
8 <!ENTITY show_area_exits "false" >
9 <!ENTITY show_up_down "true" >
10 <!ENTITY speedwalk_prefix "" >
11]>
12
13<muclient>
14<plugin
15 name="Materia_Magica_Mapper"
16 author="Nick Gammon"
17 id="1c17ac2c83b2b66c402c80c6"
18 language="Lua"
19 purpose="Mapper for Materia Magica"
20 save_state="y"
21 date_written="2010-10-12"
22 date_modified="2010-10-18 07:05"
23 requires="4.61"
24 version="1.5"
25 >
26
27 <description trim="y">
28<![CDATA[
29AUTOMATIC MAPPER ... by Nick Gammon
30
31The window can be dragged to a new location by dragging the room name.
32
33Your current room is always in the center with a bolder border.
34
35LH-click on a room to speed-walk to it. RH-click on a room for options.
36
37LH-click on the "*" button on the bottom-left corner to configure it.
38
39** WHY DOES THE MAP CHANGE? **
40
41The mapper draws from your room outwards - that is, it draws your room's exits
42first, then the rooms leading from those rooms, and so on.
43
44Eventually it finds an overlap, and draws a short "stub" line to indicate there
45is a room there which there isn't space to draw. If you get closer to that
46room the stub will disappear and the room(s) in question will be drawn.
47
48ACTIONS
49
50mapper help --> this help (or click the "?" button on the bottom right)
51mapper zoom out --> zoom out
52mapper zoom in --> zoom in
53mapper hide --> hide map
54mapper show --> show map
55
56FINDING THINGS
57
58mapper bookmarks --> show nearby rooms that you bookmarked
59mapper find <text> --> full-text search (eg. shop OR baker)
60mapper shop --> show nearby shops/banks etc.
61mapper train --> show nearby trainers
62mapper where <room> --> show directions to a room
63
64MOVING
65
66mapper goto <room> --> walk to a room by its room number (partial)
67mapper stop --> cancel any current speedwalk
68mapper resume --> resume last speedwalk or hyperlinked speedwalk
69
70]]>
71</description>
72
73
74</plugin>
75
76
77<!-- Triggers -->
78
79<triggers>
80
81 <trigger
82 back_colour="8"
83 bold="y"
84 enabled="y"
85 match="[Shop] *"
86 match_back_colour="y"
87 match_bold="y"
88 match_inverse="y"
89 match_italic="y"
90 match_text_colour="y"
91 name="Shop_Line"
92 script="Shop_Line"
93 sequence="200"
94 text_colour="11"
95
96 >
97 </trigger>
98
99<trigger
100 back_colour="8"
101 bold="y"
102 enabled="y"
103 match="[Train] *"
104 match_back_colour="y"
105 match_bold="y"
106 match_inverse="y"
107 match_italic="y"
108 match_text_colour="y"
109 name="Train_Line"
110 script="Train_Line"
111 sequence="200"
112 text_colour="11"
113
114 >
115 </trigger>
116
117 <trigger
118 back_colour="8"
119 bold="y"
120 enabled="y"
121 match="*"
122 match_back_colour="y"
123 match_bold="y"
124 match_inverse="y"
125 match_italic="y"
126 match_text_colour="y"
127 name="Name_Line"
128 script="Name_Line"
129 sequence="100"
130 text_colour="11"
131 keep_evaluating="y"
132
133 >
134 </trigger>
135
136
137
138
139 <trigger
140enabled="y"
141match="kxwt_rshort *"
142name="process_room_name"
143sequence="100"
144send_to="14"
145>
146<send>name="%1"</send>
147</trigger>
148<trigger
149enabled="y"
150match="kxwt_rvnum *"
151name="process_room_desc"
152sequence="100"
153send_to="14"
154>
155<send>uid="%1"</send>
156</trigger>
157<trigger
158enabled="y"
159match="kxwt_terrain *"
160name="process_terrain"
161sequence="100"
162send_to="14"
163>
164<send>terrain="%1"</send>
165</trigger>
166<trigger
167enabled="y"
168match="kxwt_area * *"
169name="process_area"
170sequence="100"
171send_to="14"
172>
173<send>area="%2"</send>
174</trigger>
175 <trigger
176 enabled="y"
177 match="[Exits: *]"
178 name="Exits_Line"
179 sequence="100"
180 send_to="14"
181 >
182<send>process_exits("%1")</send>
183 </trigger>
184
185 <trigger
186 enabled="y"
187 match="^The (door|gate) is closed\.$"
188 regexp="y"
189 name="Door_Closed"
190 script="Door_Closed"
191 sequence="100"
192 >
193 </trigger>
194
195 <!-- various messages that cancel speedwalks -->
196
197 <trigger
198 enabled="y"
199 match="You are too exhausted. Better rest for a bit."
200 regexp="y"
201 script="mapper.cancel_speedwalk"
202 sequence="100"
203 >
204 </trigger>
205
206</triggers>
207
208
209<aliases>
210
211 <!-- zooming aliases -->
212
213 <alias
214 match="mapper zoom out"
215 enabled="y"
216 sequence="100"
217 omit_from_command_history="y"
218 omit_from_output="y"
219 script="mapper.zoom_out"
220 >
221 </alias>
222
223<alias
224 match="mapper zoom in"
225 enabled="y"
226 sequence="100"
227 omit_from_command_history="y"
228 omit_from_output="y"
229 script="mapper.zoom_in"
230 >
231 </alias>
232
233
234 <alias
235 match="mapper goto *"
236 enabled="y"
237 sequence="100"
238 script="map_goto"
239 >
240
241 </alias>
242
243 <!-- finding aliases -->
244
245 <alias
246 match="^mapper find ([\w* %d/"]+)$"
247 enabled="y"
248 sequence="100"
249 script="map_find"
250 regexp="y"
251 >
252
253 </alias>
254
255 <alias
256 match="^mapper book\w*$"
257 regexp="y"
258 enabled="y"
259 sequence="100"
260 script="map_bookmarks"
261 >
262
263 </alias>
264
265 <alias
266 match="mapper where *"
267 enabled="y"
268 sequence="100"
269 script="map_where"
270 >
271
272 </alias>
273<alias
274 match="mapper backup"
275 enabled="y"
276 sequence="100"
277 omit_from_output="y"
278 script="manual_backup"
279></alias>
280
281 <alias
282 match="^mapper shops?$"
283 regexp="y"
284 enabled="y"
285 sequence="100"
286 script="map_shops"
287 >
288
289 </alias>
290
291 <alias
292 match="^mapper train\w*$"
293 regexp="y"
294 enabled="y"
295 sequence="100"
296 script="map_trainers"
297 >
298
299 </alias>
300
301 <alias
302 match="mapper resume"
303 enabled="y"
304 sequence="100"
305 script="map_resume"
306 >
307
308 </alias>
309
310 <alias
311 script="OnHelp"
312 match="mapper help"
313 enabled="y"
314 >
315 </alias>
316
317 <!-- cancel speedwalking -->
318
319 <alias
320 match="mapper stop"
321 enabled="y"
322 sequence="100"
323 script="mapper.cancel_speedwalk"
324 >
325 </alias>
326
327 <!-- show/hide mapper -->
328
329 <alias
330 match="mapper hide"
331 enabled="y"
332 sequence="100"
333 script="mapper.hide"
334 >
335 </alias>
336
337 <alias
338 match="mapper show"
339 enabled="y"
340 sequence="100"
341 script="mapper.show"
342 >
343 </alias>
344
345</aliases>
346
347<!-- Script -->
348
349
350<script>
351
352local show_vnums = &show_vnums;
353local show_timing = &show_timing;
354local show_completed = &show_completed;
355local show_database_mods = &show_database_mods;
356local show_other_areas = &show_other_areas;
357local show_up_down = &show_up_down;
358local show_area_exits = &show_area_exits;
359local speedwalk_prefix = "&speedwalk_prefix;"
360
361<![CDATA[
362
363mapper = require "altermapper"
364require "serialize"
365require "copytable"
366
367rooms = {}
368terrain = 0
369area = 0
370uid = 0
371current_room=0
372current_area = 0
373expected_exit=-666
374count = 0
375from_room=0
376last_direction_moved=0
377roomcount = 0
378
379valid_direction = {
380 n = "n",
381 s = "s",
382 e = "e",
383 w = "w",
384 u = "u",
385 d = "d",
386 ne = "ne",
387 sw = "sw",
388 nw = "nw",
389 se = "se",
390 north = "n",
391 south = "s",
392 east = "e",
393 west = "w",
394 up = "u",
395 down = "d",
396 northeast = "ne",
397 northwest = "nw",
398 southeast = "se",
399 southwest = "sw",
400 ['in'] = "in",
401 out = "out",
402 } -- end of valid_direction
403
404terrain_color = {}
405
406-- -----------------------------------------------------------------
407-- Here on white coloured line - this is a room name or room exits
408-- -----------------------------------------------------------------
409
410function Name_Or_Exits (name, line, wildcards)
411
412 exits = string.match (line, "^Exits: (.*)")
413
414 if exits then
415 process_exits (exits)
416 end -- if
417
418 roomname = line
419 roomdesc = nil
420
421end -- Name_Or_Exits
422
423-- -----------------------------------------------------------------
424-- Here on yellow line - part of room description
425-- -----------------------------------------------------------------
426
427function Name_Line (name, line, wildcards)
428 roomdesc = (roomdesc or "" ) .. line .. "\n"
429end -- Name_Or_Exits
430
431
432
433
434function process_exits (exits_str)
435
436 -- generate a "room ID" by hashing the room name, description and exits
437 -- break up exits into individual directions
438 exits = {}
439 for exit in string.gmatch (exits_str, "%w+") do
440 local ex = valid_direction [exit]
441 if ex then
442 exits [ex] = "-666" -- don't know where it goes yet
443 end -- if
444 end -- for
445 if not rooms [uid] then
446 rooms [uid] = { name = name, description = uid, exits = exits, area=area, fillcolour=terrain_color[terrain], fillbrush=0, bordercolour=0xffffff }
447 end -- if
448 -- save so we know current room later on
449 current_room = uid
450
451
452-- ColourNote ("rosybrown", "", roomdesc)
453-- ColourNote ("olive", "", uid)
454
455 local room = rooms [current_room]
456
457 -- not cached - see if in database
458 if not room then
459 -- print ("Loading room", current_room, "from database")
460 room = load_room_from_database (current_room)
461 end -- not in cache
462
463
464
465 if not room then
466 -- print ("Added room", uid) -- debugging
467 -- print ("Name", roomname)
468 -- ColourNote ("rosybrown", "", roomdesc)
469
470 db:exec ("BEGIN TRANSACTION;")
471
472 save_room_to_database (current_room, name, description)
473 save_exits_to_database (current_room, exits)
474
475 db:exec ("COMMIT;")
476
477 -- get it back now
478 room = load_room_from_database (current_room)
479
480 end -- if room not there
481
482 -- call mapper to draw this rom
483 mapper.draw (current_room) -- redraw room with name
484
485 -- try to work out where previous room's exit led
486 if expected_exit == "-666" and from_room then
487 fix_up_exit ()
488 end -- exit was wrong
489
490end -- process_exits
491
492function fix_up_exit ()
493
494 -- where we were before
495 local room = rooms [from_room]
496
497 -- leads to here
498 room.exits [last_direction_moved] = current_room
499
500 -- clear for next time
501 last_direction_moved = nil
502 from_room = nil
503
504end -- fix_up_exit
505
506
507function Shop_Line (name, line, wildcards)
508
509 -- location not known?
510 if not current_room then
511 return
512 end -- if
513
514 if rooms [current_room].shop then
515 return
516 end -- already a shop
517
518 -- mark as shop
519 rooms [current_room].shop = true
520
521 -- update database
522 dbcheck (db:execute (string.format (
523 "UPDATE rooms SET shop = %i WHERE uid = %s;",
524 fixbool (rooms [current_room].shop),
525 fixsql (current_room)
526 )))
527
528 -- note it
529 mapper.mapprint ("Room", current_room, "marked as a shop")
530
531 -- redraw
532 mapper.draw (current_room)
533
534end -- Shop_Line
535
536function Train_Line (name, line, wildcards)
537
538 -- location not known?
539 if not current_room then
540 return
541 end -- if
542
543 if rooms [current_room].train then
544 return
545 end -- already a train
546
547 -- mark as train
548 rooms [current_room].train = true
549
550 -- update database
551 dbcheck (db:execute (string.format (
552 "UPDATE rooms SET train = %i WHERE uid = %s;",
553 fixbool (rooms [current_room].train),
554 fixsql (current_room)
555 )))
556
557 -- note it
558 mapper.mapprint ("Room", current_room, "marked as a training room")
559
560 -- redraw
561 mapper.draw (uid)
562
563end -- Train_Line
564
565-- -----------------------------------------------------------------
566-- mapper 'get_room' callback - it wants to know about room uid
567-- -----------------------------------------------------------------
568
569function get_room (uid)
570
571 -- check we got room at all
572 if not uid then
573 -- return nil
574 end -- if
575
576 -- look it up
577 local ourroom = rooms [uid]
578
579 -- not cached - see if in database
580 if not ourroom then
581 ourroom = load_room_from_database (uid)
582 rooms [uid] = ourroom -- cache for later
583 end -- not in cache
584
585 if not ourroom then
586 return nil
587 end -- if
588
589 local room = copytable.deep (rooms [uid])
590
591 if uid == current_room then
592 current_area = room.area
593 end -- if
594
595 -- build hover message
596
597
598 local shop = ""
599 if room.shop then
600 shop = "\nRoom is shop"
601 end -- if shop
602
603 local train = ""
604 if room.train then
605 train = "\nRoom has trainer"
606 end -- if train
607
608 local notes = ""
609 if room.notes then
610 notes = "\nBookmark: " .. room.notes
611 end -- if notes
612
613 local texits = {}
614 for dir in pairs (room.exits) do
615 table.insert (texits, dir)
616 end -- for
617 table.sort (texits)
618
619 room.hovermessage = string.format (
620 "%s\tExits: %s\nRoom: %s%s%s%s",
621 room.name,
622 table.concat (texits, ", "),
623 uid,
624 shop,
625 train,
626 notes
627 -- depth,
628 -- table.concat (path, ",")
629 )
630
631
632 -- special room fill colours
633
634 if room.shop then
635 room.fillcolour = config.SHOP_FILL_COLOUR.colour
636 room.fillbrush = 8
637 end -- if
638
639 if room.train then
640 room.fillcolour = config.TRAINER_FILL_COLOUR.colour
641 room.fillbrush = 8
642 end -- if
643
644 if uid == current_room then
645 room.bordercolour = ColourNameToRGB "red"
646 room.borderpenwidth = 2
647 elseif room.area ~= current_area then
648 room.bordercolour = config.DIFFERENT_AREA_COLOUR.colour
649 end -- not in this area
650
651 return room
652
653end -- get_room
654
655
656
657-- -----------------------------------------------------------------
658-- We have changed rooms - work out where the previous room led to
659-- -----------------------------------------------------------------
660
661function fix_up_exit ()
662
663 -- where we were before
664 local room = rooms [from_room]
665
666 -- print ("Moved from", from_room, "to", current_room, "in direction", last_direction_moved)
667
668 -- leads to here
669 if from_room ~= current_room then
670
671 dbcheck (db:execute (string.format ([[
672 UPDATE exits SET touid = %s WHERE fromuid = %s AND dir = %s;
673 ]],
674 fixsql (current_room), -- destination room
675 fixsql (from_room), -- from previous room
676 fixsql (last_direction_moved) -- direction (eg. "n")
677 )))
678
679 if show_database_mods then
680 mapper.mapprint ("Fixed exit", last_direction_moved, "from room", from_room, "to be to", current_room)
681 end -- if
682
683 room.exits [last_direction_moved] = current_room
684 end -- if
685
686 -- clear for next time
687 last_direction_moved = nil
688 from_room = nil
689
690end -- fix_up_exit
691
692-- -----------------------------------------------------------------
693-- try to detect when we send a movement command
694-- -----------------------------------------------------------------
695
696function OnPluginSent (sText)
697 if valid_direction [sText] then
698 last_direction_moved = valid_direction [sText]
699 -- print ("Just moved", last_direction_moved)
700 if current_room and rooms [current_room] then
701 expected_exit = rooms [current_room].exits [last_direction_moved]
702 if expected_exit then
703 from_room = current_room
704 end -- if
705 -- print ("expected exit for this direction is to room", expected_exit)
706 end -- if
707 end -- if
708end -- function
709
710
711-- -----------------------------------------------------------------
712-- Plugin Install
713-- -----------------------------------------------------------------
714
715function OnPluginInstall ()
716
717 config = {} -- in case not found
718 setup_terrain_colors()
719
720 -- get saved configuration
721 assert (loadstring (GetVariable ("config") or "")) ()
722
723
724 -- initialize mapper
725
726 mapper.init { config = config,
727 get_room = get_room,
728 show_help = OnHelp, -- to show help
729 room_click = room_click, -- called on RH click on room square
730 timing = show_timing, -- want to see timing
731 show_completed = show_completed, -- want to see "Speedwalk completed." message
732 show_other_areas = show_other_areas, -- want to see areas other than the current one?
733 show_up_down = show_up_down, -- want to follow up/down exits?
734 show_area_exits = show_area_exits, -- want to see area exits?
735 speedwalk_prefix = speedwalk_prefix, -- how to speedwalk
736 }
737 mapper.mapprint (string.format ("MUSHclient mapper installed, version %0.1f", mapper.VERSION))
738
739 -- open databases on disk
740 db = assert (sqlite3.open(GetInfo (66) .. Trim (WorldAddress ()) .. "_" .. WorldPort () .. ".db"))
741
742 create_tables () -- create database structure if necessary
743
744end -- OnPluginInstall
745
746-- -----------------------------------------------------------------
747-- Plugin Save State
748-- -----------------------------------------------------------------
749
750function OnPluginSaveState ()
751 mapper.save_state ()
752 SetVariable ("config", "config = " .. serialize.save_simple (config))
753end -- OnPluginSaveState
754
755function map_resume (name, line, wildcards)
756
757 local wanted = mapper.last_hyperlink_uid or mapper.last_speedwalk_uid
758
759 if not wanted then
760 mapper.print "No outstanding speedwalks or hyperlinks."
761 return
762 end -- if nothing to do
763
764 -- find desired room
765 mapper.find (
766 function (uid)
767 return uid == wanted, uid == wanted
768 end, -- function
769 show_vnums, -- show vnum?
770 1, -- how many to expect
771 true -- just walk there
772 )
773
774end -- map_resume
775
776function map_goto (name, line, wildcards)
777
778 local wanted = wildcards [1]
779
780 -- check valid string
781 if string.match (wanted, "%X") then
782 mapper.maperror ("Room number must be hex string (0-9, A-F), you entered: " .. wanted)
783 return
784 end -- if
785
786 -- internally rooms are upper-case hex
787 wanted = wanted:upper ()
788
789 -- see if already there
790 if current_room and string.match (current_room, "^" .. wanted) then
791 mapper.mapprint ("You are already in that room.")
792 return
793 end -- if
794
795 -- find desired room
796 mapper.find (
797 function (uid)
798 local found = string.match (uid, "^" .. wanted)
799 return found, found
800 end, -- function
801 show_vnums, -- show vnum?
802 1, -- how many to expect
803 true -- just walk there
804 )
805
806end -- map_goto
807
808function map_where (name, line, wildcards)
809
810 if not mapper.check_we_can_find () then
811 return
812 end -- if
813
814 local wanted = wildcards [1]
815
816 if current_room and wanted == current_room then
817 mapper.mapprint ("You are already in that room.")
818 return
819 end -- if
820
821 local paths = mapper.find_paths (current_room,
822 function (uid)
823 return uid == wanted, -- wanted room?
824 uid == wanted -- stop searching?
825 end)
826
827 local uid, item = next (paths, nil) -- extract first (only) path
828
829 -- nothing? room not found
830 if not item then
831 mapper.mapprint (string.format ("Room %s not found", wanted))
832 return
833 end -- if
834
835 -- turn into speedwalk
836 local path = mapper.build_speedwalk (item.path)
837
838 -- display it
839 mapper.mapprint (string.format ("Path to %s is: %s", wanted, path))
840
841end -- map_where
842
843function OnHelp ()
844 mapper.mapprint (string.format ("[MUSHclient mapper, version %0.1f]", mapper.VERSION))
845 mapper.mapprint (world.GetPluginInfo (world.GetPluginID (), 3))
846end
847
848
849
850room_not_in_database = {}
851room_in_database = {}
852
853function dbcheck (code)
854
855 if code ~= sqlite3.OK and -- no error
856 code ~= sqlite3.ROW and -- completed OK with another row of data
857 code ~= sqlite3.DONE then -- completed OK, no more rows
858 local err = db:errmsg () -- the rollback will change the error message
859 db:exec ("ROLLBACK") -- rollback any transaction to unlock the database
860 error (err, 2) -- show error in caller's context
861 end -- if
862
863end -- dbcheck
864
865function fixsql (s)
866
867 if s then
868 return "'" .. (string.gsub (s, "'", "''")) .. "'" -- replace single quotes with two lots of single quotes
869 else
870 return "NULL"
871 end -- if
872end -- fixsql
873
874function fixbool (b)
875 if b then
876 return 1
877 else
878 return 0
879 end -- if
880end -- fixbool
881
882function load_room_from_database (uid)
883
884 local room
885
886 assert (uid, "No UID supplied to load_room_from_database")
887
888 -- if not in database, don't look again
889 if room_not_in_database [uid] then
890 return nil
891 end -- no point looking
892
893 for row in db:nrows(string.format ("SELECT * FROM rooms WHERE uid = %s", fixsql (uid))) do
894 room = {
895 name = row.name,
896 area = row.area,
897 description = row.description,
898 shop = row.shop,
899 train = row.train,
900 notes = row.notes,
901
902 exits = {} }
903
904 for exitrow in db:nrows(string.format ("SELECT * FROM exits WHERE fromuid = %s", fixsql (uid))) do
905 room.exits [exitrow.dir] = tostring (exitrow.touid)
906 end -- for each exit
907
908 end -- finding room
909
910 if room then
911 rooms [uid] = room
912 return room
913 end -- if found
914
915 room_not_in_database [uid] = true
916 return nil
917
918end -- load_room_from_database
919
920
921function save_room_to_database (uid, title, description)
922
923 assert (uid, "No UID supplied to save_room_to_database")
924
925 dbcheck (db:execute (string.format (
926 "INSERT INTO rooms (uid, name, description, date_added) VALUES (%s, %s, %s, DATETIME('NOW'));",
927 fixsql (uid),
928 fixsql (title),
929 fixsql (description)
930 )))
931
932 dbcheck (db:execute (string.format ([[
933 INSERT INTO rooms_lookup (uid, name, description) VALUES (%s, %s, %s);
934 ]], fixsql (uid),
935 fixsql (title),
936 fixsql (description)
937 )))
938
939 room_not_in_database [uid] = false
940
941 if show_database_mods then
942 mapper.mapprint ("Added room", uid, "to database. Name:", title)
943 end -- if
944
945end -- function save_room_to_database
946
947function save_exits_to_database (uid, exits)
948
949 for dir in string.gmatch (exits, "%a+") do
950
951 -- fix up in and out
952 dir = ({ ['i'] = "in", o = "out", }) [dir] or dir
953
954 dbcheck (db:execute (string.format ([[
955 INSERT INTO exits (dir, fromuid, touid, date_added)
956 VALUES (%s, %s, %s, DATETIME('NOW'));
957 ]], fixsql (dir), -- direction (eg. "n")
958 fixsql (uid), -- from current room
959 fixsql (-666) -- destination room (not known)
960 )))
961 if show_database_mods then
962 -- mapper.mapprint ("Added unknown exit", dir, "from room", uid, "to database.")
963 end -- if
964
965 end -- for each exit
966
967end -- function save_exits_to_database
968
969
970function create_tables ()
971 -- create rooms table
972 dbcheck (db:execute[[
973
974 PRAGMA foreign_keys = ON;
975 PRAGMA journal_mode = WAL;
976
977 CREATE TABLE IF NOT EXISTS rooms (
978 roomid INTEGER PRIMARY KEY AUTOINCREMENT,
979 uid TEXT NOT NULL, -- vnum or how the MUD identifies the room
980 name TEXT, -- name of room
981 description TEXT, -- description
982 terrain TEXT, -- which building it is in
983 shop INTEGER, -- 1 = shop here
984 train INTEGER, -- 1 = trainer here
985 notes TEXT, -- player notes
986 date_added DATE, -- date added to database
987 UNIQUE (uid)
988 );
989 CREATE INDEX IF NOT EXISTS shop_index ON rooms (shop);
990 CREATE INDEX IF NOT EXISTS train_index ON rooms (train);
991
992 CREATE TABLE IF NOT EXISTS exits (
993 exitid INTEGER PRIMARY KEY AUTOINCREMENT,
994 dir TEXT NOT NULL, -- direction, eg. "n", "s"
995 fromuid TEXT NOT NULL, -- exit from which room (in rooms table)
996 touid TEXT NOT NULL, -- exit to which room (in rooms table)
997 date_added DATE, -- date added to database
998 FOREIGN KEY(fromuid) REFERENCES rooms(uid)
999 );
1000 CREATE INDEX IF NOT EXISTS fromuid_index ON exits (fromuid);
1001 CREATE INDEX IF NOT EXISTS touid_index ON exits (touid);
1002
1003 ]])
1004
1005 -- check if rooms_lookup table exists
1006 local table_exists
1007 for a in db:nrows "SELECT * FROM sqlite_master WHERE name = 'rooms_lookup' AND type = 'table'" do
1008 table_exists = true
1009 end -- for
1010
1011 if not table_exists then
1012 dbcheck (db:execute "CREATE VIRTUAL TABLE rooms_lookup USING FTS3(uid, name, description);")
1013 -- in case we only deleted the rooms_lookup table to save space in the download
1014 dbcheck (db:execute "INSERT INTO rooms_lookup (uid, name, description) SELECT uid, name, description FROM rooms;")
1015 end -- if
1016
1017
1018end -- function create_tables
1019
1020function room_edit_bookmark (room, uid)
1021
1022 local notes = room.notes or ""
1023
1024 if notes ~= "" then
1025 newnotes = utils.inputbox ("Modify room comment (clear it to delete from database)", room.name, notes)
1026 else
1027 newnotes = utils.inputbox ("Enter room comment (creates a bookmark for this room)", room.name, notes)
1028 end -- if
1029
1030 if not newnotes then
1031 return
1032 end -- if cancelled
1033
1034 if newnotes == "" then
1035 if notes == "" then
1036 mapper.mapprint ("No comment entered, bookmark not saved.")
1037 return
1038 else
1039 dbcheck (db:execute (string.format (
1040 "UPDATE rooms SET notes = NULL WHERE uid = %s;",
1041 fixsql (uid)
1042 )))
1043 mapper.mapprint ("Bookmark for room", uid, "deleted. Was previously:", notes)
1044 rooms [uid].notes = nil
1045 return
1046 end -- if
1047 end -- if
1048
1049 if notes == newnotes then
1050 return -- no change made
1051 end -- if
1052
1053 dbcheck (db:execute (string.format (
1054 "UPDATE rooms SET notes = %s WHERE uid = %s;",
1055 fixsql (newnotes),
1056 fixsql (uid)
1057 )))
1058
1059 if notes ~= "" then
1060 mapper.mapprint ("Bookmark for room", uid, "changed to:", newnotes)
1061 else
1062 mapper.mapprint ("Bookmark added to room", uid, ":", newnotes)
1063 end -- if
1064
1065 rooms [uid].notes = newnotes
1066
1067end -- room_edit_bookmark
1068
1069function room_toggle_shop (room, uid)
1070
1071 rooms [uid].shop = not rooms [uid].shop
1072
1073 dbcheck (db:execute (string.format (
1074 "UPDATE rooms SET shop = %i WHERE uid = %s;",
1075 fixbool (rooms [uid].shop),
1076 fixsql (uid)
1077 )))
1078
1079 if rooms [uid].shop then
1080 mapper.mapprint ("Room", uid, "marked as a shop")
1081 else
1082 mapper.mapprint ("Room", uid, "not a shop any more")
1083 end
1084
1085 mapper.draw (current_room)
1086
1087end -- room_toggle_shop
1088
1089function room_toggle_train (room, uid)
1090
1091 rooms [uid].train = not rooms [uid].train
1092
1093 dbcheck (db:execute (string.format (
1094 "UPDATE rooms SET train = %i WHERE uid = %s;",
1095 fixbool (rooms [uid].train),
1096 fixsql (uid)
1097 )))
1098
1099 if rooms [uid].train then
1100 mapper.mapprint ("Room", uid, "marked as a training room")
1101 else
1102 mapper.mapprint ("Room", uid, "not a training room any more")
1103 end
1104
1105 mapper.draw (current_room)
1106
1107end -- room_toggle_train
1108
1109function setup_terrain_colors()
1110terrain_color["0"]=tonumber("0x000000")--NOTSET
1111terrain_color["1"]=tonumber("0x606060")--Building
1112terrain_color["2"]=tonumber("0x805a22")--Town
1113terrain_color["3"]=tonumber("0x00ff00")--FIELD
1114terrain_color["4"]=tonumber("0x00c000")--LFOREST
1115terrain_color["5"]=tonumber("0x008000")--TFOREST
1116terrain_color["6"]=tonumber("0x004000")--DFOREST
1117terrain_color["7"]=tonumber("0x406080")--SWAMP
1118terrain_color["8"]=tonumber("0x60a060")--PLATEAU
1119terrain_color["9"]=tonumber("0x00ffff")--SANDY
1120terrain_color["10"]=tonumber("0xc0c0c0")--MOUNTAIN
1121terrain_color["11"]=tonumber("0x808080")--ROCK
1122terrain_color["12"]=tonumber("0x00ffff")--DESERT
1123terrain_color["13"]=tonumber("0x805080")--TUNDRA
1124terrain_color["14"]=ColourNameToRGB("lightyellow")--BEACH
1125terrain_color["15"]=tonumber("0x409040")--HILL
1126terrain_color["16"]=tonumber("0xf0f080")--DUNES
1127terrain_color["17"]=tonumber("0x20c040")--JUNGLE
1128terrain_color["18"]=ColourNameToRGB("darkblue")--OCEAN
1129terrain_color["19"]=tonumber("0x00f0f0")--STREAM
1130terrain_color["20"]=ColourNameToRGB("blue")--RIVER
1131terrain_color["21"]=tonumber("0x021e6c")--UNDERWATER
1132terrain_color["22"]=tonumber("0x303030")--UNDERGROUND
1133terrain_color["23"]=tonumber("0x000000")--AIR
1134terrain_color["24"]=tonumber("0x82f8e6")--ICE
1135terrain_color["25"]=ColourNameToRGB("red")--LAVA
1136terrain_color["26"]=tonumber("0x806060")--RUINS
1137terrain_color["27"]=tonumber("0x404040")--CAVE
1138terrain_color["28"]=tonumber("0x907040")--CITY
1139terrain_color["29"]=tonumber("0x20a060")--MARSH
1140terrain_color["30"]=tonumber("0xf0f0a0")--WASTELAND
1141terrain_color["31"]=tonumber("0xffffff")--CLOUD
1142terrain_color["32"]=ColourNameToRGB("blue")--WATER
1143terrain_color["33"]=tonumber("0x808080")--METAL
1144terrain_color["34"]=tonumber("0x006000")--TAIGA
1145terrain_color["35"]=tonumber("0x404040")--SEWER
1146terrain_color["36"]=ColourNameToRGB("indigo")--SHADOW
1147 -- end of terrain_color
1148end
1149
1150
1151function room_add_exit (room, uid)
1152
1153local available = {
1154 n = "North",
1155 s = "South",
1156 e = "East",
1157 w = "West",
1158 u = "Up",
1159 d = "Down",
1160 ne = "Northeast",
1161 sw = "Southwest",
1162 nw = "Northwest",
1163 se = "Southeast",
1164 ['in'] = "In",
1165 out = "Out",
1166 } -- end of available
1167
1168 -- remove existing exits
1169 for k in pairs (room.exits) do
1170 available [k] = nil
1171 end -- for
1172
1173 if next (available) == nil then
1174 utils.msgbox ("All exits already used.", "No free exits!", "ok", "!", 1)
1175 return
1176 end -- not known
1177
1178 local chosen_exit = utils.listbox ("Choose exit to add", "Exits ...", available )
1179 if not chosen_exit then
1180 return
1181 end
1182
1183 exit_destination = utils.inputbox ("Enter destination room identifier (number) for " .. available [chosen_exit], room.name, "")
1184
1185 if not exit_destination then
1186 return
1187 end -- cancelled
1188
1189 -- look it up
1190 local dest_room = rooms [exit_destination]
1191
1192 -- not cached - see if in database
1193 if not dest_room then
1194 dest_room = load_room_from_database (exit_destination)
1195 rooms [exit_destination] = dest_room -- cache for later
1196 end -- not in cache
1197
1198 if not dest_room then
1199 utils.msgbox ("Room " .. exit_destination .. " does not exist.", "Room does not exist!", "ok", "!", 1)
1200 return
1201 end -- if still not there
1202
1203 dbcheck (db:execute (string.format ([[
1204 INSERT INTO exits (dir, fromuid, touid, date_added)
1205 VALUES (%s, %s, %s, DATETIME('NOW'));
1206 ]], fixsql (chosen_exit), -- direction (eg. "n")
1207 fixsql (uid), -- from current room
1208 fixsql (exit_destination) -- destination room
1209 )))
1210 if show_database_mods then
1211 mapper.mapprint ("Added exit", available [chosen_exit], "from room", uid, "to room", exit_destination, "to database.")
1212 end -- if
1213
1214 -- update in-memory table
1215 rooms [uid].exits [chosen_exit] = exit_destination
1216
1217 mapper.draw (current_room)
1218
1219end -- room_add_exit
1220
1221
1222function room_delete_exit (room, uid)
1223
1224local available = {
1225 n = "North",
1226 s = "South",
1227 e = "East",
1228 w = "West",
1229 u = "Up",
1230 d = "Down",
1231 ne = "Northeast",
1232 sw = "Southwest",
1233 nw = "Northwest",
1234 se = "Southeast",
1235 ['in'] = "In",
1236 out = "Out",
1237 } -- end of available
1238
1239 -- remove non-existent exits
1240 for k in pairs (available) do
1241 if room.exits [k] then
1242 available [k] = available [k] .. " --> " .. room.exits [k]
1243 else
1244 available [k] = nil
1245 end -- if not a room exit
1246 end -- for
1247
1248 if next (available) == nil then
1249 utils.msgbox ("There are no exits from this room.", "No exits!", "ok", "!", 1)
1250 return
1251 end -- not known
1252
1253 local chosen_exit = utils.listbox ("Choose exit to delete", "Exits ...", available )
1254 if not chosen_exit then
1255 return
1256 end
1257
1258 dbcheck (db:execute (string.format ([[
1259 DELETE FROM exits WHERE dir = %s AND fromuid = %s;
1260 ]], fixsql (chosen_exit), -- direction (eg. "n")
1261 fixsql (uid) -- from current room
1262 )))
1263 if show_database_mods then
1264 mapper.mapprint ("Deleted exit", available [chosen_exit], "from room", uid, "from database.")
1265 end -- if
1266
1267 -- update in-memory table
1268 rooms [uid].exits [chosen_exit] = nil
1269
1270 mapper.draw (current_room)
1271
1272end -- room_delete_exit
1273
1274
1275function room_change_exit (room, uid)
1276
1277local available = {
1278 n = "North",
1279 s = "South",
1280 e = "East",
1281 w = "West",
1282 u = "Up",
1283 d = "Down",
1284 ne = "Northeast",
1285 sw = "Southwest",
1286 nw = "Northwest",
1287 se = "Southeast",
1288 ['in'] = "In",
1289 out = "Out",
1290 } -- end of available
1291
1292 -- remove non-existent exits
1293 for k in pairs (available) do
1294 if room.exits [k] then
1295 available [k] = available [k] .. " --> " .. room.exits [k]
1296 else
1297 available [k] = nil
1298 end -- if not a room exit
1299 end -- for
1300
1301 if next (available) == nil then
1302 utils.msgbox ("There are no exits from this room.", "No exits!", "ok", "!", 1)
1303 return
1304 end -- not known
1305
1306 local chosen_exit = utils.listbox ("Choose exit to change destination of:", "Exits ...", available )
1307 if not chosen_exit then
1308 return
1309 end
1310
1311 exit_destination = utils.inputbox ("Enter destination room identifier (number) for " .. available [chosen_exit], room.name, "")
1312
1313 if not exit_destination then
1314 return
1315 end -- cancelled
1316
1317 -- look it up
1318 local dest_room = rooms [exit_destination]
1319
1320 -- not cached - see if in database
1321 if not dest_room then
1322 dest_room = load_room_from_database (exit_destination)
1323 rooms [exit_destination] = dest_room -- cache for later
1324 end -- not in cache
1325
1326 if not dest_room then
1327 utils.msgbox ("Room " .. exit_destination .. " does not exist.", "Room does not exist!", "ok", "!", 1)
1328 return
1329 end -- if still not there
1330
1331 dbcheck (db:execute (string.format ([[
1332 UPDATE exits SET touid = %s WHERE dir = %s AND fromuid = %s;
1333 ]], fixsql (exit_destination),
1334 fixsql (chosen_exit), -- direction (eg. "n")
1335 fixsql (uid) -- from current room
1336 )))
1337
1338 if show_database_mods then
1339 mapper.mapprint ("Modified exit", available [chosen_exit], "from room", uid, "to be to room", exit_destination, "in database.")
1340 end -- if
1341
1342 -- update in-memory table
1343 rooms [uid].exits [chosen_exit] = exit_destination
1344 mapper.draw (current_room)
1345
1346end -- room_change_exit
1347
1348function room_click (uid, flags)
1349
1350 -- check we got room at all
1351 if not uid then
1352 return nil
1353 end -- if
1354
1355 -- look it up
1356 local room = rooms [uid]
1357
1358 -- not cached - see if in database
1359 if not room then
1360 room = load_room_from_database (uid)
1361 rooms [uid] = room -- cache for later
1362 end -- not in cache
1363
1364 if not room then
1365 return
1366 end -- if still not there
1367
1368 local handlers = {
1369 { name = "Edit bookmark", func = room_edit_bookmark} ,
1370 { name = "-", } ,
1371 { name = "Add Exit", func = room_add_exit} ,
1372 { name = "Change Exit", func = room_change_exit} ,
1373 { name = "Delete Exit", func = room_delete_exit} ,
1374 { name = "-", } ,
1375 { name = "Toggle Shop", func = room_toggle_shop } ,
1376 { name = "Toggle Trainer", func = room_toggle_train } ,
1377 } -- handlers
1378
1379 local t, tf = {}, {}
1380 for _, v in pairs (handlers) do
1381 table.insert (t, v.name)
1382 tf [v.name] = v.func
1383 end -- for
1384
1385 local choice = WindowMenu (mapper.win,
1386 WindowInfo (mapper.win, 14),
1387 WindowInfo (mapper.win, 15),
1388 table.concat (t, "|"))
1389
1390 local f = tf [choice]
1391
1392 if f then
1393 f (room, uid)
1394 end -- if handler found
1395
1396end -- room_click
1397
1398
1399function map_find (name, line, wildcards)
1400
1401 local rooms = {}
1402 local count = 0
1403 local snippets = {}
1404 local reset = ANSI (0)
1405 local bold = ANSI (1)
1406 local unbold = ANSI (22)
1407
1408 function show_snippet (uid)
1409 AnsiNote (reset .. snippets [uid])
1410 end -- show_snippet
1411
1412
1413 -- find matching rooms using FTS3
1414 for row in db:nrows(string.format (
1415 [[
1416 SELECT uid, name, snippet(rooms_lookup, '%s', '%s', ' ... ', -1, -10) AS snippet
1417 FROM rooms_lookup
1418 WHERE rooms_lookup MATCH %s]],
1419 bold, unbold,
1420 fixsql (wildcards [1]))) do
1421 rooms [row.uid] = true
1422 snippets [row.uid] = row.snippet
1423 count = count + 1
1424 end -- finding room
1425
1426 -- see if nearby
1427 mapper.find (
1428 function (uid)
1429 local room = rooms [uid]
1430 if room then
1431 rooms [uid] = nil
1432 end -- if
1433 return room, next (rooms) == nil
1434 end, -- function
1435 show_vnums, -- show vnum?
1436 count, -- how many to expect
1437 false, -- don't auto-walk
1438 show_snippet -- show find snippet
1439 )
1440
1441end -- map_find
1442
1443function map_bookmarks (name, line, wildcards)
1444
1445 local rooms = {}
1446 local count = 0
1447
1448 -- build table of special places (with info in them)
1449 for row in db:nrows(string.format ("SELECT uid, notes FROM rooms WHERE notes IS NOT NULL")) do
1450 rooms [row.uid] = capitalize (row.notes)
1451 count = count + 1
1452 end -- finding room
1453
1454 -- find such places
1455 mapper.find (
1456 function (uid)
1457 local room = rooms [uid]
1458 if room then
1459 rooms [uid] = nil
1460 end -- if
1461 return room, next (rooms) == nil -- room will be type of info (eg. shop)
1462 end, -- function
1463 show_vnums, -- show vnum?
1464 count, -- how many to expect
1465 false -- don't auto-walk
1466 )
1467
1468end -- map_bookmarks
1469
1470function map_find_special (which)
1471
1472 local rooms = {}
1473 local count = 0
1474
1475 -- build table of special places (with info in them)
1476 for row in db:nrows(string.format ("SELECT uid, name FROM rooms WHERE %s = 1", which)) do
1477 rooms [row.uid] = true
1478 count = count + 1
1479 end -- finding room
1480
1481 -- find such places
1482 mapper.find (
1483 function (uid)
1484 local room = rooms [uid]
1485 if room then
1486 rooms [uid] = nil
1487 end -- if
1488 return room, next (rooms) == nil -- room will be type of info (eg. shop)
1489 end, -- function
1490 show_vnums, -- show vnum?
1491 count, -- how many to expect
1492 false -- don't auto-walk
1493 )
1494
1495end -- map_find_special
1496
1497function map_shops (name, line, wildcards)
1498 map_find_special ("shop")
1499end -- map_shops
1500
1501function map_trainers (name, line, wildcards)
1502 map_find_special ("train")
1503end -- map_trainers
1504
1505function manual_backup()
1506 backup_databases(true)
1507end
1508
1509function backup_databases(manual)
1510 wait.make(function()
1511
1512 performing_maintenance = true
1513 local success = false
1514 maybe_Note("PERFORMING "..((manual and "MANUAL") or "AUTOMATIC").." DATABASE BACKUP. DON'T TOUCH ANYTHING!", manual)
1515 if not checkDatabaseIntegrity(true) then
1516 maybe_Note("ABORTING CURRENT BACKUP", manual)
1517 if not compact_mode then Note("") end
1518 Repaint()
1519 db = assert (sqlite3.open(worldPath..".db")) -- try to re-open the database anyway
1520 performing_maintenance = false
1521 satisfy_broadcast_queue()
1522 return
1523 end
1524 maybe_Note("BACKING UP DATABASE", manual)
1525 Repaint()
1526
1527 backupPath = GetInfo(66).."db_backups\\"..sanitize_filename(WorldName())
1528 firstBackup = backupPath..".db.Backup"
1529 local ffi
1530 if type(jit) == 'table' then
1531 ffi = require("ffi")
1532 ffi.cdef[[
1533 bool CreateDirectoryA(const char *lpPathName, void *lpSecurityAttributes);
1534 bool CopyFileA(const char* lpExistingFileName, const char * lpNewFileName, bool bFailIfExists);
1535 unsigned long GetLastError(void);
1536 ]]
1537
1538 succ = ffi.C.CreateDirectoryA(GetInfo(66).."db_backups\\", nil)
1539 err_no = ffi.C.GetLastError()
1540 else
1541 succ, err_no = utils.shellexecute("cmd", "/C mkdir db_backups", GetInfo(66), "open", 0)
1542 end
1543 if succ == false and err_no ~= 183 and err_no ~= 127 then
1544 -- error
1545 ColourNote("yellow","red", "ERROR ("..err_no..") trying to CreateDirectory: "..GetInfo(66).."db_backups\\\n")
1546 else
1547 -- successfully created the backup directory
1548
1549 -- record when the backup occurs
1550 dbCheckExecute(string.format("INSERT OR REPLACE INTO storage (name, data) VALUES (%s,%s);", fixsql("backup_date"), fixsql(os.date("%b %d, %Y - %X"))))
1551
1552 if db:isopen() then
1553 db:close() -- always close the database before copying
1554 end
1555
1556 -- make new backup
1557 if use_compression == 1 then
1558
1559 local tmp = GetInfo(66).."aard_package_temp_file.txt" -- temp file for recording output
1560 -- It's not so simple to catch errors from this, so record the output and look at it later.
1561 -- Use pushd/popd because cd can't @!#($$# access UNC paths.
1562 local execute_string = "pushd "..quote(GetInfo(66)).." & zip -j -v -T -lf "..quote(tmp).." "..quote(firstBackup..".zip").." "..quote(worldPath..".db").." & popd"
1563
1564 function file_exists(name)
1565 local f=io.open(name,"r")
1566 if f~=nil then io.close(f) return true else return false end
1567 end
1568 function file_is_writable(name)
1569 local f=io.open(name,"w")
1570 if f~=nil then io.close(f) return true else return false end
1571 end
1572
1573 if file_exists(tmp) and not file_is_writable(tmp) then
1574 ColourNote("yellow","red", "ERROR executing system command: "..execute_string.."\n")
1575 ColourNote("yellow", "red", "Log file "..quote(tmp).." is not writeable. Do you have write permission?") -- flaming output
1576 -- failure
1577 else
1578 utils.shellexecute("cmd", "/C "..execute_string, "", "open", 0)
1579
1580 local zip_done = false
1581 local zip_error = false
1582 local zipstarttime = utils.timer()
1583 local zip_lines
1584 while not zip_done do
1585 zip_lines = {}
1586 if file_exists(tmp) then
1587 for line in io.lines(tmp) do
1588 table.insert(zip_lines, line)
1589 end
1590 os.remove(tmp) -- remove temp file
1591 end
1592
1593 for _,line in ipairs(zip_lines) do
1594 if line:find("zip warning") or line:find("zip error") or line:find("zip I/O error") then
1595 zip_done = true
1596 zip_error = true
1597 end
1598 if line:find("zip OK") then
1599 zip_done = true
1600 end
1601 end
1602
1603 if zip_done then break end
1604 if utils.timer() - zipstarttime > 30 then
1605 -- we have to give up EVENTUALLY
1606 break
1607 end
1608 wait.time(0.1)
1609 end
1610
1611 if zip_error then
1612 -- something went wrong with the compression
1613 -- we expect to see some lines with the last line showing a passing test
1614 ColourNote("yellow","red", "ERROR executing system command: "..execute_string.."\n")
1615 for _,v in ipairs(zip_lines) do
1616 ColourNote("yellow", "red", v) -- flaming output
1617 end
1618 -- failure
1619 else
1620 for _,v in ipairs(zip_lines) do
1621 maybe_Note(v, manual) -- less intense output
1622 end
1623 success = true
1624 end
1625 end
1626 else
1627 -- no compression, just copy
1628 if type(jit) == 'table' then
1629 succ = ffi.C.CopyFileA(worldPath..".db", firstBackup, false)
1630 err_no = ffi.C.GetLastError()
1631 else
1632 -- succ, err_no = utils.shellexecute("cmd", "/C copy "..worldPath..".db "..firstBackup, "", "open", 0);
1633 local inp, outp, errmsg
1634 succ = false
1635 inp, errmsg, err_no = io.open(worldPath..".db", "rb")
1636 if err_no == nil then
1637 outp, errmsg, err_no = io.open(firstBackup, "wb")
1638 if err_no == nil then
1639 local data = inp:read("*all")
1640 outp:write(data)
1641 outp:close()
1642 inp:close()
1643 succ = true
1644 end
1645 end
1646 if succ == false then
1647 ColourNote("yellow","red", "ERROR: "..errmsg)
1648 end
1649 end
1650 if succ == false then
1651 ColourNote("yellow","red", "ERROR ("..err_no..") trying to copy database from '"..worldPath..".db' to '"..firstBackup.."'")
1652 else
1653 success = true
1654 end
1655 end
1656 end
1657
1658 if success then
1659 rotate_backups(manual) -- rotate backup folders (obviously)
1660 maybe_Note("FINISHED DATABASE BACKUP. YOU MAY NOW GO BACK TO MUDDING.", manual)
1661 else
1662 Note("ABORTING BACKUP.")
1663 CorruptionAlert()
1664 end
1665 if not compact_mode then Note("") end
1666 db = assert (sqlite3.open(worldPath..".db")) -- re-open database
1667 Repaint()
1668 performing_maintenance = false
1669 satisfy_broadcast_queue()
1670 end)
1671end
1672
1673
1674function Door_Closed (name, line, wildcards)
1675
1676local dirs = {
1677 n = "north",
1678 s = "south",
1679 e = "east",
1680 w = "west",
1681 u = "up",
1682 d = "down",
1683 ne = "northeast",
1684 sw = "southwest",
1685 nw = "northwest",
1686 se = "southeast",
1687 ['in'] = "in",
1688 out = "out",
1689 } -- end of available
1690
1691 if last_direction_moved then
1692 Send ("open " .. dirs [last_direction_moved])
1693 Send (dirs [last_direction_moved])
1694 end -- if
1695
1696end -- Door_Closed
1697
1698]]>
1699</script>
1700
1701
1702</muclient>