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