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