· 6 years ago · Apr 01, 2019, 02:54 PM
1-- mapper.lua
2
3--[[
4
5Author: Nick Gammon
6Date: 11th March 2010
7Amended: 15th August 2010
8Amended: 2nd October 2010
9Amended: 18th October 2010 to added find callback
10Amended: 16th November 2010 to add symbolic constants (miniwin.xxxx)
11Amended: 18th November 2010 to add more timing and count of times called
12 Also added zooming with the mouse wheel.
13Amended: 26th November 2010 to check timers are enabled when speedwalking.
14Amended: 11th November 2014 to allow for detecting mouse-overs of rooms
15
16Generic MUD mapper.
17
18Exposed functions:
19
20init (t) -- call once, supply:
21 t.config -- ie. colours, sizes
22 t.get_room -- info about room (uid)
23 t.show_help -- function that displays some help
24 t.room_click -- function that handles RH click on room (uid, flags)
25 t.room_mouseover -- function that handles mouse-over a room (uid, flags)
26 t.room_cancelmouseover -- function that handles cancelled mouse-over of a room (uid, flags)
27 t.timing -- true to show timing
28 t.show_completed -- true to show "Speedwalk completed."
29 t.show_other_areas -- true to show non-current areas
30 t.show_up_down -- follow up/down exits
31 t.show_area_exits -- true to draw a circle around rooms leading to other areas
32 t.speedwalk_prefix -- if not nil, speedwalk by prefixing with this
33
34zoom_in () -- zoom in map view
35zoom_out () -- zoom out map view
36mapprint (message) -- like print, but uses mapper colour
37maperror (message) -- like print, but prints in red
38hide () -- hides map window (eg. if plugin disabled)
39show () -- show map window (eg. if plugin enabled)
40save_state () -- call to save plugin state (ie. in OnPluginSaveState)
41draw (uid) -- draw map - starting at room 'uid'
42start_speedwalk (path) -- starts speedwalking. path is a table of directions/uids
43build_speedwalk (path) -- builds a client speedwalk string from path
44cancel_speedwalk () -- cancel current speedwalk, if any
45check_we_can_find () -- returns true if doing a find is OK right now
46find (f, show_uid, count, walk) -- generic room finder
47find_paths (uid, f) -- lower-level room finder (for getting back a path)
48
49Exposed variables:
50
51win -- the window (in case you want to put up menus)
52VERSION -- mapper version
53last_hyperlink_uid -- room uid of last hyperlink click (destination)
54last_speedwalk_uid -- room uid of last speedwalk attempted (destination)
55<various functions> -- functions required to be global by the client (eg. for mouseup)
56
57Room info should include:
58
59 name (what to show as room name)
60 exits (table keyed by direction, value is exit uid)
61 area (area name)
62 hovermessage (what to show when you mouse-over the room)
63 bordercolour (colour of room border) - RGB colour
64 borderpen (pen style of room border) - see WindowCircleOp (values 0 to 6)
65 borderpenwidth(pen width of room border) - eg. 1 for normal, 2 for current room
66 fillcolour (colour to fill room) - RGB colour, nil for default
67 fillbrush (brush to fill room) - see WindowCircleOp (values 0 to 12)
68
69--]]
70
71module (..., package.seeall)
72
73VERSION = 2.6 -- for querying by plugins
74
75require "movewindow"
76require "copytable"
77require "gauge"
78require "pairsbykeys"
79require "mw"
80
81local FONT_ID = "fn" -- internal font identifier
82local FONT_ID_UL = "fnu" -- internal font identifier - underlined
83
84-- size of room box
85local ROOM_SIZE = 10
86
87-- how far away to draw rooms from each other
88local DISTANCE_TO_NEXT_ROOM = 15
89
90-- supplied in init
91local config -- configuration table
92local supplied_get_room
93local room_click
94local room_mouseover
95local room_cancelmouseover
96local timing -- true to show timing and other info
97local show_completed -- true to show "Speedwalk completed."
98local show_other_areas -- true to draw other areas
99local show_area_exits -- true to show area exits
100local show_up_down -- true to show up/down exits
101
102-- current room number
103local current_room
104
105-- our copy of rooms info
106local rooms = {}
107local last_visited = {}
108
109-- other locals
110local HALF_ROOM, connectors, half_connectors, arrows
111local plan_to_draw, speedwalks, drawn, drawn_coords
112local last_drawn, depth, windowinfo, font_height
113local walk_to_roomname
114local total_times_drawn = 0
115local total_time_taken = 0
116
117local function build_room_info ()
118
119 HALF_ROOM = ROOM_SIZE / 2
120 local THIRD_WAY = DISTANCE_TO_NEXT_ROOM / 3
121 local DISTANCE_LESS1 = DISTANCE_TO_NEXT_ROOM - 1
122
123 -- how to draw a line from this room to the next one (relative to the center of the room)
124 connectors = {
125 n = { x1 = 0, y1 = - HALF_ROOM, x2 = 0, y2 = - HALF_ROOM - DISTANCE_LESS1, at = { 0, -1 } },
126 s = { x1 = 0, y1 = HALF_ROOM, x2 = 0, y2 = HALF_ROOM + DISTANCE_LESS1, at = { 0, 1 } },
127 e = { x1 = HALF_ROOM, y1 = 0, x2 = HALF_ROOM + DISTANCE_LESS1, y2 = 0, at = { 1, 0 }},
128 w = { x1 = - HALF_ROOM, y1 = 0, x2 = - HALF_ROOM - DISTANCE_LESS1, y2 = 0, at = { -1, 0 }},
129
130 ne = { x1 = HALF_ROOM, y1 = - HALF_ROOM, x2 = HALF_ROOM + DISTANCE_LESS1 , y2 = - HALF_ROOM - DISTANCE_LESS1, at = { 1, -1 } },
131 se = { x1 = HALF_ROOM, y1 = HALF_ROOM, x2 = HALF_ROOM + DISTANCE_LESS1 , y2 = HALF_ROOM + DISTANCE_LESS1, at = { 1, 1 } },
132 nw = { x1 = - HALF_ROOM, y1 = - HALF_ROOM, x2 = - HALF_ROOM - DISTANCE_LESS1 , y2 = - HALF_ROOM - DISTANCE_LESS1, at = {-1, -1 } },
133 sw = { x1 = - HALF_ROOM, y1 = HALF_ROOM, x2 = - HALF_ROOM - DISTANCE_LESS1 , y2 = HALF_ROOM + DISTANCE_LESS1, at = {-1, 1 } },
134
135 } -- end connectors
136
137 -- how to draw a stub line
138 half_connectors = {
139 n = { x1 = 0, y1 = - HALF_ROOM, x2 = 0, y2 = - HALF_ROOM - THIRD_WAY, at = { 0, -1 } },
140 s = { x1 = 0, y1 = HALF_ROOM, x2 = 0, y2 = HALF_ROOM + THIRD_WAY, at = { 0, 1 } },
141 e = { x1 = HALF_ROOM, y1 = 0, x2 = HALF_ROOM + THIRD_WAY, y2 = 0, at = { 1, 0 }},
142 w = { x1 = - HALF_ROOM, y1 = 0, x2 = - HALF_ROOM - THIRD_WAY, y2 = 0, at = { -1, 0 }},
143
144 ne = { x1 = HALF_ROOM, y1 = - HALF_ROOM, x2 = HALF_ROOM + THIRD_WAY , y2 = - HALF_ROOM - THIRD_WAY, at = { 1, -1 } },
145 se = { x1 = HALF_ROOM, y1 = HALF_ROOM, x2 = HALF_ROOM + THIRD_WAY , y2 = HALF_ROOM + THIRD_WAY, at = { 1, 1 } },
146 nw = { x1 = - HALF_ROOM, y1 = - HALF_ROOM, x2 = - HALF_ROOM - THIRD_WAY , y2 = - HALF_ROOM - THIRD_WAY, at = {-1, -1 } },
147 sw = { x1 = - HALF_ROOM, y1 = HALF_ROOM, x2 = - HALF_ROOM - THIRD_WAY , y2 = HALF_ROOM + THIRD_WAY, at = {-1, 1 } },
148
149 } -- end half_connectors
150
151 -- how to draw one-way arrows (relative to the center of the room)
152 arrows = {
153 n = { - 2, - HALF_ROOM - 2, 2, - HALF_ROOM - 2, 0, - HALF_ROOM - 6 },
154 s = { - 2, HALF_ROOM + 2, 2, HALF_ROOM + 2, 0, HALF_ROOM + 6 },
155 e = { HALF_ROOM + 2, -2, HALF_ROOM + 2, 2, HALF_ROOM + 6, 0 },
156 w = { - HALF_ROOM - 2, -2, - HALF_ROOM - 2, 2, - HALF_ROOM - 6, 0 },
157
158 ne = { HALF_ROOM + 3, - HALF_ROOM, HALF_ROOM + 3, - HALF_ROOM - 3, HALF_ROOM, - HALF_ROOM - 3 },
159 se = { HALF_ROOM + 3, HALF_ROOM, HALF_ROOM + 3, HALF_ROOM + 3, HALF_ROOM, HALF_ROOM + 3 },
160 nw = { - HALF_ROOM - 3, - HALF_ROOM, - HALF_ROOM - 3, - HALF_ROOM - 3, - HALF_ROOM, - HALF_ROOM - 3 },
161 sw = { - HALF_ROOM - 3, HALF_ROOM, - HALF_ROOM - 3, HALF_ROOM + 3, - HALF_ROOM, HALF_ROOM + 3},
162
163 } -- end of arrows
164
165end -- build_room_info
166
167local default_config = {
168 -- assorted colours
169 BACKGROUND_COLOUR = { name = "Background", colour = ColourNameToRGB "lightseagreen", },
170 ROOM_COLOUR = { name = "Room", colour = ColourNameToRGB "cyan", },
171 EXIT_COLOUR = { name = "Exit", colour = ColourNameToRGB "darkgreen", },
172 EXIT_COLOUR_UP_DOWN = { name = "Exit up/down", colour = ColourNameToRGB "darkmagenta", },
173 EXIT_COLOUR_IN_OUT = { name = "Exit in/out", colour = ColourNameToRGB "#3775E8", },
174 UNKNOWN_ROOM_COLOUR = { name = "Unknown room", colour = ColourNameToRGB "#00CACA", },
175 MAPPER_NOTE_COLOUR = { name = "Messages", colour = ColourNameToRGB "lightgreen" },
176
177 roomname_TEXT = { name = "Room name text", colour = ColourNameToRGB "#BEF3F1", },
178 roomname_FILL = { name = "Room name fill", colour = ColourNameToRGB "#105653", },
179 roomname_BORDER = { name = "Room name box", colour = ColourNameToRGB "black", },
180
181 AREA_NAME_TEXT = { name = "Area name text", colour = ColourNameToRGB "#BEF3F1",},
182 AREA_NAME_FILL = { name = "Area name fill", colour = ColourNameToRGB "#105653", },
183 AREA_NAME_BORDER = { name = "Area name box", colour = ColourNameToRGB "black", },
184
185 FONT = { name = get_preferred_font {"Dina", "Lucida Console", "Fixedsys", "Courier", "Sylfaen",} ,
186 size = 8
187 } ,
188
189 -- size of map window
190 WINDOW = { width = 400, height = 400 },
191
192 -- how far from where we are standing to draw (rooms)
193 SCAN = { depth = 30 },
194
195 -- speedwalk delay
196 DELAY = { time = 0.3 },
197
198 -- how many seconds to show "recent visit" lines (default 3 minutes)
199 LAST_VISIT_TIME = { time = 60 * 3 },
200
201 }
202
203local expand_direction = {
204 n = "north",
205 s = "south",
206 e = "east",
207 w = "west",
208 u = "up",
209 d = "down",
210 ne = "northeast",
211 sw = "southwest",
212 nw = "northwest",
213 se = "southeast",
214 ['in'] = "in",
215 out = "out",
216 } -- end of expand_direction
217
218local function get_room (uid)
219 local room = supplied_get_room (uid)
220 room = room or { unknown = true }
221
222 -- defaults in case they didn't supply them ...
223 room.name = room.name or string.format ("Room %s", uid or "<none>")
224 room.name = mw.strip_colours (room.name) -- no colour codes for now
225 room.exits = room.exits or {}
226 room.area = room.area or "<No area>"
227 room.hovermessage = room.hovermessage or "<Unexplored room>"
228 room.bordercolour = room.bordercolour or config.ROOM_COLOUR.colour
229 room.borderpen = room.borderpen or 0 -- solid
230 room.borderpenwidth = room.borderpenwidth or 1
231 room.fillcolour = room.fillcolour or 0x000000
232 room.fillbrush = room.fillbrush or 1 -- no fill
233
234 return room
235end -- get_room
236
237local function check_connected ()
238 if not IsConnected() then
239 mapprint ("You are not connected to", WorldName())
240 return false
241 end -- if not connected
242 return true
243end -- check_connected
244
245local function make_number_checker (title, min, max, decimals)
246 return function (s)
247 local n = tonumber (s)
248 if not n then
249 utils.msgbox (title .. " must be a number", "Incorrect input", "ok", "!", 1)
250 return false -- bad input
251 end -- if
252
253 if n < min or n > max then
254 utils.msgbox (title .. " must be in range " .. min .. " to " .. max, "Incorrect input", "ok", "!", 1)
255 return false -- bad input
256 end -- if
257
258 if not decimals then
259 if string.match (s, "%.") then
260 utils.msgbox (title .. " cannot have decimal places", "Incorrect input", "ok", "!", 1)
261 return false -- bad input
262 end -- if
263 end -- no decimals
264
265 return true -- good input
266 end -- generated function
267
268end -- make_number_checker
269
270
271local function get_number_from_user (msg, title, current, min, max, decimals)
272 local max_length = math.ceil (math.log10 (max) + 1)
273
274 -- if decimals allowed, allow room for them
275 if decimals then
276 max_length = max_length + 2 -- allow for 0.x
277 end -- if
278
279 -- if can be negative, allow for minus sign
280 if min < 0 then
281 max_length = max_length + 1
282 end -- if can be negative
283
284 return tonumber (utils.inputbox (msg, title, current, nil, nil,
285 { validate = make_number_checker (title, min, max, decimals),
286 prompt_height = 14,
287 box_height = 130,
288 box_width = 300,
289 reply_width = 150,
290 max_length = max_length,
291 } -- end extra stuff
292 ))
293end -- get_number_from_user
294
295local function draw_configuration ()
296 local width = max_text_width (win, FONT_ID, {"Configuration", "Font", "Width", "Height", "Depth"}, true)
297 local lines = 6 -- "Configuration", font, width, height, depth, delay
298 local GAP = 5
299 local suppress_colours = false
300
301 for k, v in pairs (config) do
302 if v.colour then
303 width = math.max (width, WindowTextWidth (win, FONT_ID, v.name, true))
304 lines = lines + 1
305 end -- a colour item
306 end -- for each config item
307
308 if (config.WINDOW.height - 13 - font_height * lines) < 10 then
309 suppress_colours = true
310 lines = 6 -- forget all the colours
311 end -- if
312
313 local x = 3
314 local y = config.WINDOW.height - 13 - font_height * lines
315 local box_size = font_height - 2
316 local rh_size = math.max (box_size, max_text_width (win, FONT_ID,
317 {config.FONT.name .. " " .. config.FONT.size,
318 tostring (config.WINDOW.width),
319 tostring (config.WINDOW.height),
320 tostring (config.SCAN.depth)},
321 true))
322 local frame_width = GAP + width + GAP + rh_size + GAP -- gap / text / gap / box / gap
323
324 -- fill entire box with grey
325 WindowRectOp (win, miniwin.rect_fill, x, y, x + frame_width, y + font_height * lines + 10, 0xDCDCDC)
326 -- frame it
327 draw_3d_box (win, x, y, frame_width, font_height * lines + 10)
328
329 y = y + GAP
330 x = x + GAP
331
332 -- title
333 WindowText (win, FONT_ID, "Configuration", x, y, 0, 0, 0x808080, true)
334
335 -- close box
336 WindowRectOp (win,
337 miniwin.rect_frame,
338 x + frame_width - box_size - GAP * 2,
339 y + 1,
340 x + frame_width - GAP * 2,
341 y + 1 + box_size,
342 0x808080)
343 WindowLine (win,
344 x + frame_width - box_size - GAP * 2 + 3,
345 y + 4,
346 x + frame_width - GAP * 2 - 3,
347 y - 2 + box_size,
348 0x808080,
349 miniwin.pen_solid, 1)
350 WindowLine (win,
351 x - 4 + frame_width - GAP * 2,
352 y + 4,
353 x - 1 + frame_width - box_size - GAP * 2 + 3,
354 y - 2 + box_size,
355 0x808080,
356 miniwin.pen_solid, 1)
357
358 -- close configuration hotspot
359 WindowAddHotspot(win, "$<close_configure>",
360 x + frame_width - box_size - GAP * 2,
361 y + 1,
362 x + frame_width - GAP * 2,
363 y + 1 + box_size, -- rectangle
364 "", "", "", "", "mapper.mouseup_close_configure", -- mouseup
365 "Click to close",
366 miniwin.cursor_hand, 0) -- hand cursor
367
368 y = y + font_height
369
370 if not suppress_colours then
371
372 for k, v in pairsByKeys (config) do
373 if v.colour then
374 WindowText (win, FONT_ID, v.name, x, y, 0, 0, 0x000000, true)
375 WindowRectOp (win,
376 miniwin.rect_fill,
377 x + width + rh_size / 2,
378 y + 1,
379 x + width + rh_size / 2 + box_size,
380 y + 1 + box_size,
381 v.colour)
382 WindowRectOp (win,
383 miniwin.rect_frame,
384 x + width + rh_size / 2,
385 y + 1,
386 x + width + rh_size / 2 + box_size,
387 y + 1 + box_size,
388 0x000000)
389
390 -- colour change hotspot
391 WindowAddHotspot(win,
392 "$colour:" .. k,
393 x + GAP,
394 y + 1,
395 x + width + rh_size / 2 + box_size,
396 y + 1 + box_size, -- rectangle
397 "", "", "", "", "mapper.mouseup_change_colour", -- mouseup
398 "Click to change colour",
399 miniwin.cursor_hand, 0) -- hand cursor
400
401 y = y + font_height
402 end -- a colour item
403 end -- for each config item
404 end -- if
405
406 -- depth
407 WindowText (win, FONT_ID, "Depth", x, y, 0, 0, 0x000000, true)
408 WindowText (win, FONT_ID_UL, tostring (config.SCAN.depth), x + width + GAP, y, 0, 0, 0x808080, true)
409
410 -- depth hotspot
411 WindowAddHotspot(win,
412 "$<depth>",
413 x + GAP,
414 y,
415 x + frame_width,
416 y + font_height, -- rectangle
417 "", "", "", "", "mapper.mouseup_change_depth", -- mouseup
418 "Click to change scan depth",
419 miniwin.cursor_hand, 0) -- hand cursor
420 y = y + font_height
421
422 -- font
423 WindowText (win, FONT_ID, "Font", x, y, 0, 0, 0x000000, true)
424 WindowText (win, FONT_ID_UL, config.FONT.name .. " " .. config.FONT.size, x + width + GAP, y, 0, 0, 0x808080, true)
425
426 -- colour font hotspot
427 WindowAddHotspot(win,
428 "$<font>",
429 x + GAP,
430 y,
431 x + frame_width,
432 y + font_height, -- rectangle
433 "", "", "", "", "mapper.mouseup_change_font", -- mouseup
434 "Click to change font",
435 miniwin.cursor_hand, 0) -- hand cursor
436 y = y + font_height
437
438
439 -- width
440 WindowText (win, FONT_ID, "Width", x, y, 0, 0, 0x000000, true)
441 WindowText (win, FONT_ID_UL, tostring (config.WINDOW.width), x + width + GAP, y, 0, 0, 0x808080, true)
442
443 -- width hotspot
444 WindowAddHotspot(win,
445 "$<width>",
446 x + GAP,
447 y,
448 x + frame_width,
449 y + font_height, -- rectangle
450 "", "", "", "", "mapper.mouseup_change_width", -- mouseup
451 "Click to change window width",
452 miniwin.cursor_hand, 0) -- hand cursor
453 y = y + font_height
454
455 -- height
456 WindowText (win, FONT_ID, "Height", x, y, 0, 0, 0x000000, true)
457 WindowText (win, FONT_ID_UL, tostring (config.WINDOW.height), x + width + GAP, y, 0, 0, 0x808080, true)
458
459 -- height hotspot
460 WindowAddHotspot(win,
461 "$<height>",
462 x + GAP,
463 y,
464 x + frame_width,
465 y + font_height, -- rectangle
466 "", "", "", "", "mapper.mouseup_change_height", -- mouseup
467 "Click to change window height",
468 miniwin.cursor_hand, 0) -- hand cursor
469 y = y + font_height
470
471 -- delay
472 WindowText (win, FONT_ID, "Walk delay", x, y, 0, 0, 0x000000, true)
473 WindowText (win, FONT_ID_UL, tostring (config.DELAY.time), x + width + GAP, y, 0, 0, 0x808080, true)
474
475 -- height hotspot
476 WindowAddHotspot(win,
477 "$<delay>",
478 x + GAP,
479 y,
480 x + frame_width,
481 y + font_height, -- rectangle
482 "", "", "", "", "mapper.mouseup_change_delay", -- mouseup
483 "Click to change speedwalk delay",
484 miniwin.cursor_hand, 0) -- hand cursor
485 y = y + font_height
486
487end -- draw_configuration
488
489-- for calculating one-way paths
490local inverse_direction = {
491 n = "s",
492 s = "n",
493 e = "w",
494 w = "e",
495 u = "d",
496 d = "u",
497 ne = "sw",
498 sw = "ne",
499 nw = "se",
500 se = "nw",
501 ['in'] = "out",
502 out = "in",
503 } -- end of inverse_direction
504
505local function add_another_room (uid, path, x, y)
506 local path = path or {}
507 return {uid=uid, path=path, x = x, y = y}
508end -- add_another_room
509
510local function draw_room (uid, path, x, y)
511
512 local coords = string.format ("%i,%i", math.floor (x), math.floor (y))
513
514 -- need this for the *current* room !!!
515 drawn_coords [coords] = uid
516
517 -- print ("drawing", uid, "at", coords)
518
519 if drawn [uid] then
520 return
521 end -- done this one
522
523 -- don't draw the same room more than once
524 drawn [uid] = { coords = coords, path = path }
525
526 local room = rooms [uid]
527
528 -- not cached - get from caller
529 if not room then
530 room = get_room (uid)
531 rooms [uid] = room
532 end -- not in cache
533
534
535 local left, top, right, bottom = x - HALF_ROOM, y - HALF_ROOM, x + HALF_ROOM, y + HALF_ROOM
536
537 -- forget it if off screen
538 if x < HALF_ROOM or y < HALF_ROOM or
539 x > config.WINDOW.width - HALF_ROOM or y > config.WINDOW.height - HALF_ROOM then
540 return
541 end -- if
542
543 -- exits
544
545 local texits = {}
546
547 for dir, exit_uid in pairs (room.exits) do
548 table.insert (texits, dir)
549 local exit_info = connectors [dir]
550 local stub_exit_info = half_connectors [dir]
551 local exit_line_colour = config.EXIT_COLOUR.colour
552 local arrow = arrows [dir]
553
554 -- draw up in the ne/nw position if not already an exit there at this level
555 if dir == "u" then
556 if not room.exits.nw then
557 exit_info = connectors.nw
558 stub_exit_info = half_connectors.nw
559 arrow = arrows.nw
560 exit_line_colour = config.EXIT_COLOUR_UP_DOWN.colour
561 end -- if available
562 elseif dir == "in" then
563 if not room.exits.ne then
564 exit_info = connectors.ne
565 stub_exit_info = half_connectors.ne
566 arrow = arrows.ne
567 exit_line_colour = config.EXIT_COLOUR_IN_OUT.colour
568 end -- if
569 elseif dir == "d" then
570 if not room.exits.se then
571 exit_info = connectors.se
572 stub_exit_info = half_connectors.se
573 arrow = arrows.se
574 exit_line_colour = config.EXIT_COLOUR_UP_DOWN.colour
575 end -- if available
576 elseif dir == "out" then
577 if not room.exits.sw then
578 exit_info = connectors.sw
579 stub_exit_info = half_connectors.sw
580 arrow = arrows.sw
581 exit_line_colour = config.EXIT_COLOUR_IN_OUT.colour
582 end -- if
583 end -- if down
584
585 if exit_info then
586 local linetype = miniwin.pen_solid -- unbroken
587 local linewidth = 1 -- not recent
588
589 -- try to cache room
590 if not rooms [exit_uid] then
591 rooms [exit_uid] = get_room (exit_uid)
592 end -- if
593
594 if rooms [exit_uid].unknown then
595 linetype = miniwin.pen_dot -- dots
596 end -- if
597
598 local next_x = x + exit_info.at [1] * (ROOM_SIZE + DISTANCE_TO_NEXT_ROOM)
599 local next_y = y + exit_info.at [2] * (ROOM_SIZE + DISTANCE_TO_NEXT_ROOM)
600
601 local next_coords = string.format ("%i,%i", math.floor (next_x), math.floor (next_y))
602
603 -- remember if a zone exit (first one only)
604 if show_area_exits and room.area ~= rooms [exit_uid].area then
605 area_exits [ rooms [exit_uid].area ] = area_exits [ rooms [exit_uid].area ] or {x = x, y = y}
606 end -- if
607
608 -- if another room (not where this one leads to) is already there, only draw "stub" lines
609 if drawn_coords [next_coords] and drawn_coords [next_coords] ~= exit_uid then
610 exit_info = stub_exit_info
611 elseif exit_uid == uid then
612
613 -- here if room leads back to itself
614 exit_info = stub_exit_info
615 linetype = miniwin.pen_dash -- dash
616
617 else
618 if (not show_other_areas and rooms [exit_uid].area ~= current_area) or
619 (not show_up_down and (dir == "u" or dir == "d")) then
620 exit_info = stub_exit_info -- don't show other areas
621 else
622 -- if we are scheduled to draw the room already, only draw a stub this time
623 if plan_to_draw [exit_uid] and plan_to_draw [exit_uid] ~= next_coords then
624 -- here if room already going to be drawn
625 exit_info = stub_exit_info
626 linetype = miniwin.pen_dash -- dash
627 else
628 -- remember to draw room next iteration
629 local new_path = copytable.deep (path)
630 table.insert (new_path, { dir = dir, uid = exit_uid })
631 table.insert (rooms_to_be_drawn, add_another_room (exit_uid, new_path, next_x, next_y))
632 drawn_coords [next_coords] = exit_uid
633 plan_to_draw [exit_uid] = next_coords
634
635 -- if exit room known
636 if not rooms [exit_uid].unknown then
637 local exit_time = last_visited [exit_uid] or 0
638 local this_time = last_visited [uid] or 0
639 local now = os.time ()
640 if exit_time > (now - config.LAST_VISIT_TIME.time) and
641 this_time > (now - config.LAST_VISIT_TIME.time) then
642 linewidth = 2
643 end -- if
644 end -- if
645 end -- if
646 end -- if
647 end -- if drawn on this spot
648
649 WindowLine (win, x + exit_info.x1, y + exit_info.y1, x + exit_info.x2, y + exit_info.y2, exit_line_colour, linetype, linewidth)
650
651 -- one-way exit?
652
653 if not rooms [exit_uid].unknown then
654 local dest = rooms [exit_uid]
655 -- if inverse direction doesn't point back to us, this is one-way
656 if dest.exits [inverse_direction [dir]] ~= uid then
657
658 -- turn points into string, relative to where the room is
659 local points = string.format ("%i,%i,%i,%i,%i,%i",
660 x + arrow [1],
661 y + arrow [2],
662 x + arrow [3],
663 y + arrow [4],
664 x + arrow [5],
665 y + arrow [6])
666
667 -- draw arrow
668 WindowPolygon(win, points,
669 exit_line_colour, miniwin.pen_solid, 1,
670 exit_line_colour, miniwin.brush_solid,
671 true, true)
672
673 end -- one way
674
675 end -- if we know of the room where it does
676
677 end -- if we know what to do with this direction
678 end -- for each exit
679
680
681 if room.unknown then
682 WindowCircleOp (win, miniwin.circle_rectangle, left, top, right, bottom,
683 config.UNKNOWN_ROOM_COLOUR.colour, miniwin.pen_dot, 1, -- dotted single pixel pen
684 -1, miniwin.brush_null) -- opaque, no brush
685 else
686 WindowCircleOp (win, miniwin.circle_rectangle, left, top, right, bottom,
687 0, miniwin.pen_null, 0, -- no pen
688 room.fillcolour, room.fillbrush) -- brush
689
690 WindowCircleOp (win, miniwin.circle_rectangle, left, top, right, bottom,
691 room.bordercolour, room.borderpen, room.borderpenwidth, -- pen
692 -1, miniwin.brush_null) -- opaque, no brush
693 end -- if
694
695
696 -- show up and down in case we can't get a line in
697
698 if room.exits.u then -- line at top
699 WindowLine (win, left, top, left + ROOM_SIZE, top, config.EXIT_COLOUR_UP_DOWN.colour, miniwin.pen_solid, 1)
700 end -- if
701 if room.exits.d then -- line at bottom
702 WindowLine (win, left, bottom, left + ROOM_SIZE, bottom, config.EXIT_COLOUR_UP_DOWN.colour, miniwin.pen_solid, 1)
703 end -- if
704 if room.exits ['in'] then -- line at right
705 WindowLine (win, left + ROOM_SIZE, top, left + ROOM_SIZE, bottom, config.EXIT_COLOUR_IN_OUT.colour, miniwin.pen_solid, 1)
706 end -- if
707 if room.exits.out then -- line at left
708 WindowLine (win, left, top, left, bottom, config.EXIT_COLOUR_IN_OUT.colour, miniwin.pen_solid , 1)
709 end -- if
710
711 speedwalks [uid] = path -- so we know how to get here
712
713 WindowAddHotspot(win, uid,
714 left, top, right, bottom, -- rectangle
715 "mapper.mouseover_room", -- mouseover
716 "mapper.cancelmouseover_room", -- cancelmouseover
717 "", -- mousedown
718 "", -- cancelmousedown
719 "mapper.mouseup_room", -- mouseup
720 room.hovermessage,
721 miniwin.cursor_hand, 0) -- hand cursor
722
723 WindowScrollwheelHandler (win, uid, "mapper.zoom_map")
724
725end -- draw_room
726
727local function changed_room (uid)
728
729 hyperlink_paths = nil -- those hyperlinks are meaningless now
730 speedwalks = {} -- old speedwalks are irrelevant
731
732 if current_speedwalk then
733
734 if uid ~= expected_room then
735 local exp = rooms [expected_room]
736 if not exp then
737 exp = get_room (expected_room) or { name = expected_room }
738 end -- if
739 local here = rooms [uid]
740 if not here then
741 here = get_room (uid) or { name = uid }
742 end -- if
743 exp = expected_room
744 here = uid
745 maperror (string.format ("Speedwalk failed! Expected to be in '%s' but ended up in '%s'.", exp or "<none>", here))
746 cancel_speedwalk ()
747 else
748 if #current_speedwalk > 0 then
749 local dir = table.remove (current_speedwalk, 1)
750 SetStatus ("Walking " .. (expand_direction [dir.dir] or dir.dir) ..
751 " to " .. walk_to_roomname ..
752 ". Speedwalks to go: " .. #current_speedwalk + 1)
753 expected_room = dir.uid
754 if config.DELAY.time > 0 then
755 if GetOption ("enable_timers") ~= 1 then
756 maperror ("WARNING! Timers not enabled. Speedwalking will not work properly.")
757 end -- if timers disabled
758 DoAfter (config.DELAY.time, dir.dir)
759 else
760 Send (dir.dir)
761 end -- if
762 else
763 last_hyperlink_uid = nil
764 last_speedwalk_uid = nil
765 if show_completed then
766 mapprint ("Speedwalk completed.")
767 end -- if wanted
768 cancel_speedwalk ()
769 end -- if any left
770 end -- if expected room or not
771 end -- if have a current speedwalk
772
773end -- changed_room
774
775local function draw_zone_exit (exit)
776
777 local x, y = exit.x, exit.y
778 local offset = ROOM_SIZE
779
780 -- draw circle around us
781 WindowCircleOp (win, miniwin.circle_ellipse,
782 x - offset, y - offset,
783 x + offset, y + offset,
784 ColourNameToRGB "cornflowerblue", -- pen colour
785 miniwin.pen_solid, -- solid pen
786 3, -- pen width
787 0, -- brush colour
788 miniwin.brush_null) -- null brush
789
790 WindowCircleOp (win, miniwin.circle_ellipse,
791 x - offset, y - offset,
792 x + offset, y + offset,
793 ColourNameToRGB "cyan", -- pen colour
794 miniwin.pen_solid, -- solid pen
795 1, -- pen width
796 0, -- brush colour
797 miniwin.brush_null) -- null brush
798
799end -- draw_zone_exit
800
801
802----------------------------------------------------------------------------------
803-- EXPOSED FUNCTIONS
804----------------------------------------------------------------------------------
805
806-- can we find another room right now?
807
808function check_we_can_find ()
809
810 if not check_connected () then
811 return
812 end -- if
813
814 if not current_room then
815 mapprint ("I don't know where you are right now - try: LOOK")
816 return false
817 end -- if
818
819 if current_speedwalk then
820 mapprint ("No point doing this while you are speedwalking.")
821 return false
822 end -- if
823
824 return true
825end -- check_we_can_find
826
827-- see: http://www.gammon.com.au/forum/?id=7306&page=2
828-- Thanks to Ked.
829
830-- uid is starting room
831-- f returns true (or a "reason" string) if we want to store this one, and true,true if
832-- we have done searching (ie. all wanted rooms found)
833
834function find_paths (uid, f)
835
836 local function make_particle (curr_loc, prev_path)
837 local prev_path = prev_path or {}
838 return {current_room=curr_loc, path=prev_path}
839 end
840
841 local depth = 0
842 local count = 0
843 local done = false
844 local found, reason
845 local explored_rooms, particles = {}, {}
846
847 -- this is where we collect found paths
848 -- the table is keyed by destination, with paths as values
849 local paths = {}
850
851 -- create particle for the initial room
852 table.insert (particles, make_particle (uid))
853
854 while (not done) and #particles > 0 and depth < config.SCAN.depth do
855
856 -- create a new generation of particles
857 new_generation = {}
858 depth = depth + 1
859
860 SetStatus (string.format ("Scanning: %i/%i depth (%i rooms)", depth, config.SCAN.depth, count))
861
862 -- process each active particle
863 for i, part in ipairs (particles) do
864
865 count = count + 1
866
867 if not rooms [part.current_room] then
868 rooms [part.current_room] = get_room (part.current_room)
869 end -- if not in memory yet
870
871 -- if room doesn't exist, forget it
872 if rooms [part.current_room] then
873
874 -- get a list of exits from the current room
875 exits = rooms [part.current_room].exits
876
877 -- create one new particle for each exit
878 for dir, dest in pairs(exits) do
879
880 -- if we've been in this room before, drop it
881 if not explored_rooms[dest] then
882 explored_rooms[dest] = true
883 rooms [dest] = supplied_get_room (dest) -- make sure this room in table
884 if rooms [dest] then
885 new_path = copytable.deep (part.path)
886 table.insert(new_path, { dir = dir, uid = dest } )
887
888 -- if this room is in the list of destinations then save its path
889 found, done = f (dest)
890 if found then
891 paths[dest] = { path = new_path, reason = found }
892 end -- found one!
893
894 -- make a new particle in the new room
895 table.insert(new_generation, make_particle(dest, new_path))
896 end -- if room exists
897 end -- not explored this room
898 if done then
899 break
900 end
901
902 end -- for each exit
903
904 end -- if room exists
905
906 if done then
907 break
908 end
909 end -- for each particle
910
911 particles = new_generation
912 end -- while more particles
913
914 SetStatus "Ready"
915 return paths, count, depth
916end -- function find_paths
917
918-- draw our map starting at room: uid
919
920function draw (uid)
921
922 if not uid then
923 maperror "Cannot draw map right now, I don't know where you are - try: LOOK"
924 return
925 end -- if
926
927 if current_room and current_room ~= uid then
928 changed_room (uid)
929 end -- if
930
931 current_room = uid -- remember where we are
932
933 -- timing
934 local start_time = utils.timer ()
935
936 -- start with initial room
937 rooms = { [uid] = get_room (uid) }
938
939 -- lookup current room
940 local room = rooms [uid]
941
942 room = room or { name = "<Unknown room>", area = "<Unknown area>" }
943 last_visited [uid] = os.time ()
944
945 current_area = room.area
946
947 -- we are recreating the window so any mouse-over is not valid any more
948 if WindowInfo (win, 19) and WindowInfo (win, 19) ~= "" then
949 if type (room_cancelmouseover) == "function" then
950 room_cancelmouseover (WindowInfo (win, 19), 0) -- cancelled mouse over
951 end -- if
952 end -- have a hotspot
953
954 WindowDeleteAllHotspots (win)
955
956 WindowCreate (win,
957 windowinfo.window_left,
958 windowinfo.window_top,
959 config.WINDOW.width,
960 config.WINDOW.height,
961 windowinfo.window_mode, -- top right
962 windowinfo.window_flags,
963 config.BACKGROUND_COLOUR.colour)
964
965 -- let them move it around
966 movewindow.add_drag_handler (win, 0, 0, 0, font_height + 4)
967
968 -- for zooming
969 WindowAddHotspot(win,
970 "zzz_zoom",
971 0, 0, 0, 0,
972 "", "", "", "", "",
973 "", -- hint
974 miniwin.cursor_arrow, 0)
975
976 WindowScrollwheelHandler (win, "zzz_zoom", "mapper.zoom_map")
977
978 -- set up for initial room, in middle
979 drawn, drawn_coords, rooms_to_be_drawn, speedwalks, plan_to_draw, area_exits = {}, {}, {}, {}, {}, {}
980 depth = 0
981
982 -- insert initial room
983 table.insert (rooms_to_be_drawn, add_another_room (uid, {}, config.WINDOW.width / 2, config.WINDOW.height / 2))
984
985 while #rooms_to_be_drawn > 0 and depth < config.SCAN.depth do
986 local old_generation = rooms_to_be_drawn
987 rooms_to_be_drawn = {} -- new generation
988 for i, part in ipairs (old_generation) do
989 draw_room (part.uid, part.path, part.x, part.y)
990 end -- for each existing room
991 depth = depth + 1
992 end -- while all rooms_to_be_drawn
993
994 for area, zone_exit in pairs (area_exits) do
995 draw_zone_exit (zone_exit)
996 end -- for
997
998 local roomname = room.roomname
999 local name_width = WindowTextWidth (win, FONT_ID, roomname, true)
1000 local add_dots = false
1001
1002
1003
1004 if add_dots then
1005 roomname = roomname .. " ..."
1006 end -- if
1007
1008 -- roomname
1009
1010 draw_text_box (win, FONT_ID,
1011 (config.WINDOW.width - WindowTextWidth (win, FONT_ID, roomname, true)) / 2, -- left
1012 3, -- top
1013 roomname, true, -- what to draw, utf8 (changed roomname to roomname)
1014 config.roomname_TEXT.colour, -- text colour
1015 config.roomname_FILL.colour, -- fill colour
1016 config.roomname_BORDER.colour) -- border colour
1017
1018-- uid below roomname
1019 draw_text_box (win, FONT_ID,
1020 (config.WINDOW.width - WindowTextWidth (win, FONT_ID, uid, true)) / 2, -- left
1021 18, -- top
1022 uid, true, -- what to draw, utf8
1023 config.roomname_TEXT.colour, -- text colour
1024 config.roomname_FILL.colour, -- fill colour
1025 config.roomname_BORDER.colour) -- border colour
1026
1027
1028 -- area name
1029
1030 local areaname = room.area
1031
1032 if areaname then
1033 draw_text_box (win, FONT_ID,
1034 (config.WINDOW.width - WindowTextWidth (win, FONT_ID, areaname, true)) / 2, -- left
1035 config.WINDOW.height - 6 - font_height, -- top
1036 areaname, true, -- what to draw, utf8
1037 config.AREA_NAME_TEXT.colour, -- text colour
1038 config.AREA_NAME_FILL.colour, -- fill colour
1039 config.AREA_NAME_BORDER.colour) -- border colour
1040 end -- if area known
1041
1042 -- configure?
1043
1044 if draw_configure_box then
1045 draw_configuration ()
1046 else
1047
1048 local x = 5
1049 local y = config.WINDOW.height - 6 - font_height
1050 local width = draw_text_box (win, FONT_ID,
1051 x, -- left
1052 y, -- top (ie. at bottom)
1053 "*", true, -- what to draw, utf8
1054 config.AREA_NAME_TEXT.colour, -- text colour
1055 config.AREA_NAME_FILL.colour, -- fill colour
1056 config.AREA_NAME_BORDER.colour) -- border colour
1057
1058 WindowAddHotspot(win, "<configure>",
1059 x, y, x + width, y + font_height, -- rectangle
1060 "", -- mouseover
1061 "", -- cancelmouseover
1062 "", -- mousedown
1063 "", -- cancelmousedown
1064 "mapper.mouseup_configure", -- mouseup
1065 "Click to configure map",
1066 miniwin.cursor_hand, 0) -- hand cursor
1067 end -- if
1068
1069 if type (show_help) == "function" then
1070 local x = config.WINDOW.width - WindowTextWidth (win, FONT_ID, "?", true) - 5
1071 local y = config.WINDOW.height - 6 - font_height
1072 local width = draw_text_box (win, FONT_ID,
1073 x, -- left
1074 y, -- top (ie. at bottom)
1075 "?", true, -- what to draw, utf8
1076 config.AREA_NAME_TEXT.colour, -- text colour
1077 config.AREA_NAME_FILL.colour, -- fill colour
1078 config.AREA_NAME_BORDER.colour) -- border colour
1079
1080 WindowAddHotspot(win, "<help>",
1081 x, y, x + width, y + font_height, -- rectangle
1082 "", -- mouseover
1083 "", -- cancelmouseover
1084 "", -- mousedown
1085 "", -- cancelmousedown
1086 "mapper.show_help", -- mouseup
1087 "Click for help",
1088 miniwin.cursor_hand, 0) -- hand cursor
1089 end -- if
1090
1091 -- 3D box around whole thing
1092
1093 draw_3d_box (win, 0, 0, config.WINDOW.width, config.WINDOW.height)
1094
1095 -- make sure window visible
1096 WindowShow (win, not hidden)
1097
1098 last_drawn = uid -- last room number we drew (for zooming)
1099
1100 local end_time = utils.timer ()
1101
1102 -- timing stuff
1103 if timing then
1104 local count= 0
1105 for k in pairs (drawn) do
1106 count = count + 1
1107 end
1108 print (string.format ("Time to draw %i rooms = %0.3f seconds, search depth = %i", count, end_time - start_time, depth))
1109
1110 total_times_drawn = total_times_drawn + 1
1111 total_time_taken = total_time_taken + end_time - start_time
1112
1113 print (string.format ("Total times map drawn = %i, average time to draw = %0.3f seconds",
1114 total_times_drawn,
1115 total_time_taken / total_times_drawn))
1116 end -- if
1117
1118end -- draw
1119
1120local credits = {
1121 "MUSHclient mapper",
1122 string.format ("Version %0.1f", VERSION),
1123 "Written by Nick Gammon",
1124 WorldName (),
1125 GetInfo (3),
1126 }
1127
1128-- call once to initialize the mapper
1129function init (t)
1130
1131 -- make copy of colours, sizes etc.
1132 config = t.config
1133 assert (type (config) == "table", "No 'config' table supplied to mapper.")
1134
1135 supplied_get_room = t.get_room
1136 assert (type (supplied_get_room) == "function", "No 'get_room' function supplied to mapper.")
1137
1138 show_help = t.show_help -- "help" function
1139 room_click = t.room_click -- RH mouse-click function
1140 room_mouseover = t.room_mouseover -- mouse-over function
1141 room_cancelmouseover = t.room_cancelmouseover -- cancel mouse-over function
1142 timing = t.timing -- true for timing info
1143 show_completed = t.show_completed -- true to show "Speedwalk completed." message
1144 show_other_areas = t.show_other_areas -- true to show other areas
1145 show_up_down = t.show_up_down -- true to show up or down
1146 show_area_exits = t.show_area_exits -- true to show area exits
1147 speedwalk_prefix = t.speedwalk_prefix -- how to speedwalk (prefix)
1148
1149 -- force some config defaults if not supplied
1150 for k, v in pairs (default_config) do
1151 config [k] = config [k] or v
1152 end -- for
1153
1154 win = GetPluginID () .. "_mapper"
1155
1156 WindowCreate (win, 0, 0, 0, 0, 0, 0, 0)
1157
1158 -- add the fonts
1159 WindowFont (win, FONT_ID, config.FONT.name, config.FONT.size)
1160 WindowFont (win, FONT_ID_UL, config.FONT.name, config.FONT.size, false, false, true)
1161
1162 -- see how high it is
1163 font_height = WindowFontInfo (win, FONT_ID, 1) -- height
1164
1165 -- find where window was last time
1166 windowinfo = movewindow.install (win, miniwin.pos_center_right)
1167
1168 -- calculate box sizes, arrows, connecting lines etc.
1169 build_room_info ()
1170
1171 WindowCreate (win,
1172 windowinfo.window_left,
1173 windowinfo.window_top,
1174 config.WINDOW.width,
1175 config.WINDOW.height,
1176 windowinfo.window_mode, -- top right
1177 windowinfo.window_flags,
1178 config.BACKGROUND_COLOUR.colour)
1179
1180 -- let them move it around
1181 movewindow.add_drag_handler (win, 0, 0, 0, font_height + 4)
1182
1183 local top = (config.WINDOW.height - #credits * font_height) /2
1184
1185 for _, v in ipairs (credits) do
1186 local width = WindowTextWidth (win, FONT_ID, v, true)
1187 local left = (config.WINDOW.width - width) / 2
1188 WindowText (win, FONT_ID, v, left, top, 0, 0, config.ROOM_COLOUR.colour, true)
1189 top = top + font_height
1190 end -- for
1191
1192 draw_3d_box (win, 0, 0, config.WINDOW.width, config.WINDOW.height)
1193
1194 WindowShow (win, true)
1195
1196end -- init
1197
1198function zoom_in ()
1199 if last_drawn and ROOM_SIZE < 40 then
1200 ROOM_SIZE = ROOM_SIZE + 2
1201 DISTANCE_TO_NEXT_ROOM = DISTANCE_TO_NEXT_ROOM + 2
1202 build_room_info ()
1203 draw (last_drawn)
1204 end -- if
1205end -- zoom_in
1206
1207
1208function zoom_out ()
1209 if last_drawn and ROOM_SIZE > 4 then
1210 ROOM_SIZE = ROOM_SIZE - 2
1211 DISTANCE_TO_NEXT_ROOM = DISTANCE_TO_NEXT_ROOM - 2
1212 build_room_info ()
1213 draw (last_drawn)
1214 end -- if
1215end -- zoom_out
1216
1217function mapprint (...)
1218 local old_note_colour = GetNoteColourFore ()
1219 SetNoteColourFore(config.MAPPER_NOTE_COLOUR.colour)
1220 print (...)
1221 SetNoteColourFore (old_note_colour)
1222end -- mapprint
1223
1224function maperror (...)
1225 local old_note_colour = GetNoteColourFore ()
1226 SetNoteColourFore(ColourNameToRGB "red")
1227 print (...)
1228 SetNoteColourFore (old_note_colour)
1229end -- maperror
1230
1231function show ()
1232 WindowShow (win, true)
1233 hidden = false
1234end -- show
1235
1236function hide ()
1237 WindowShow (win, false)
1238 hidden = true
1239end -- hide
1240
1241function save_state ()
1242 movewindow.save_state (win)
1243end -- save_state
1244
1245
1246-- generic room finder
1247
1248-- f (uid) is a function which returns: found, done
1249-- found is not nil if uid is a wanted room - if it is a string it is the reason it matched (eg. shop)
1250-- done is true if we know there is nothing else to search for (eg. all rooms found)
1251
1252-- show_uid is true if you want the room uid to be displayed
1253
1254-- expected_count is the number we expect to find (eg. the number found on a database)
1255
1256-- if 'walk' is true, we walk to the first match rather than displaying hyperlinks
1257
1258-- if fcb is a function, it is called back after displaying each line
1259
1260function find (f, show_uid, expected_count, walk, fcb)
1261
1262 if not check_we_can_find () then
1263 return
1264 end -- if
1265
1266 if fcb then
1267 assert (type (fcb) == "function")
1268 end -- if
1269
1270 local start_time = utils.timer ()
1271 local paths, count, depth = find_paths (current_room, f)
1272 local end_time = utils.timer ()
1273
1274 local t = {}
1275 local found_count = 0
1276 for k in pairs (paths) do
1277 table.insert (t, k)
1278 found_count = found_count + 1
1279 end -- for
1280
1281 -- timing stuff
1282 if timing then
1283 print (string.format ("Time to search %i rooms = %0.3f seconds, search depth = %i",
1284 count, end_time - start_time, depth))
1285 end -- if
1286
1287 if found_count == 0 then
1288 mapprint ("No matches.")
1289 return
1290 end -- if
1291
1292 if found_count == 1 and walk then
1293 uid, item = next (paths, nil)
1294 mapprint ("Walking to:", rooms [uid].name)
1295 start_speedwalk (item.path)
1296 return
1297 end -- if walking wanted
1298
1299 -- sort so closest ones are first
1300 table.sort (t, function (a, b) return #paths [a].path < #paths [b].path end )
1301
1302 hyperlink_paths = {}
1303
1304 for _, uid in ipairs (t) do
1305 local room = rooms [uid] -- ought to exist or wouldn't be in table
1306
1307 assert (room, "Room " .. uid .. " is not in rooms table.")
1308
1309 if current_room == uid then
1310 mapprint (room.roomname, "is your current location")
1311 else
1312 local distance = #paths [uid].path .. " room"
1313 if #paths [uid].path > 1 then
1314 distance = distance .. "s"
1315 end -- if
1316 distance = distance .. " away"
1317
1318 local roomname = room.roomname
1319 if show_uid then
1320 roomname = roomname .. " (" .. uid .. ")"
1321 end -- if
1322
1323 -- in case the same UID shows up later, it is only valid from the same room
1324 local hash = utils.tohex (utils.md5 (tostring (current_room) .. "<-->" .. tostring (uid)))
1325
1326 Hyperlink ("!!" .. GetPluginID () .. ":mapper.do_hyperlink(" .. hash .. ")",
1327 roomname, "Click to speedwalk there (" .. distance .. ")", "", "", false)
1328 local info = ""
1329 if type (paths [uid].reason) == "string" and paths [uid].reason ~= "" then
1330 info = " [" .. paths [uid].reason .. "]"
1331 end -- if
1332 mapprint (" - " .. distance .. info) -- new line
1333
1334 -- callback to display extra stuff (like find context, room description)
1335 if fcb then
1336 fcb (uid)
1337 end -- if callback
1338 hyperlink_paths [hash] = paths [uid].path
1339 end -- if
1340 end -- for each room
1341
1342 if expected_count and found_count < expected_count then
1343 local diff = expected_count - found_count
1344 local were, matches = "were", "matches"
1345 if diff == 1 then
1346 were, matches = "was", "match"
1347 end -- if
1348 mapprint ("There", were, diff, matches,
1349 "which I could not find a path to within",
1350 config.SCAN.depth, "rooms.")
1351 end -- if
1352
1353end -- map_find_things
1354
1355-- executed when the mapper draws a hyperlink to a room
1356
1357function do_hyperlink (hash)
1358
1359 if not check_connected () then
1360 return
1361 end -- if
1362
1363 if not hyperlink_paths or not hyperlink_paths [hash] then
1364 mapprint ("Hyperlink is no longer valid, as you have moved.")
1365 return
1366 end -- if
1367
1368 local path = hyperlink_paths [hash]
1369 if #path > 0 then
1370 last_hyperlink_uid = path [#path].uid
1371 end -- if
1372 start_speedwalk (path)
1373
1374end -- do_hyperlink
1375
1376-- build a speedwalk from a path into a string
1377
1378function build_speedwalk (path, prefix)
1379
1380 stack_char = ";"
1381 if GetOption("enable_command_stack")==1 then
1382 stack_char = GetAlphaOption("command_stack_character")
1383 else
1384 stack_char = "\r\n"
1385 end
1386
1387 -- build speedwalk string (collect identical directions)
1388 local tspeed = {}
1389 for _, dir in ipairs (path) do
1390 local n = #tspeed
1391 if n == 0 then
1392 table.insert (tspeed, { dir = dir.dir, count = 1 })
1393 else
1394 if expand_direction[dir.dir] ~= nil and tspeed [n].dir == dir.dir then
1395 tspeed [n].count = tspeed [n].count + 1
1396 else
1397 table.insert (tspeed, { dir = dir.dir, count = 1 })
1398 end -- if different direction
1399 end -- if
1400 end -- for
1401
1402 if #tspeed == 0 then
1403 return
1404 end -- nowhere to go (current room?)
1405
1406 -- now build string like: 2n3e4(sw)
1407 local s = ""
1408
1409 local new_command = false
1410 for _, dir in ipairs (tspeed) do
1411 if expand_direction[dir.dir] ~= nil then
1412 if new_command then
1413 s = s .. stack_char .. speedwalk_prefix .. " "
1414 new_command = false
1415 end
1416 if dir.count > 1 then
1417 s = s .. dir.count
1418 end -- if
1419 s = s .. dir.dir
1420 else
1421 s = s .. stack_char .. dir.dir
1422 new_command = true
1423 end -- if
1424 end -- if
1425
1426 if prefix ~= nil then
1427 if s:sub(1,1) == stack_char then
1428 return string.gsub(s:sub(2),";",stack_char)
1429 else
1430 return string.gsub(prefix.." "..s,";",stack_char)
1431 end
1432 end
1433 return string.gsub(s,";",stack_char)
1434end -- build_speedwalk
1435
1436-- start a speedwalk to a path
1437
1438function start_speedwalk (path)
1439
1440 if not check_connected () then
1441 return
1442 end -- if
1443
1444 if current_speedwalk and #current_speedwalk > 0 then
1445 mapprint ("You are already speedwalking! (Ctrl + LH-click on any room to cancel)")
1446 return
1447 end -- if
1448
1449 current_speedwalk = path
1450
1451 if current_speedwalk then
1452 if #current_speedwalk > 0 then
1453 last_speedwalk_uid = current_speedwalk [#current_speedwalk].uid
1454
1455 -- fast speedwalk: just send # 4s 3e etc.
1456 if type (speedwalk_prefix) == "string" and speedwalk_prefix ~= "" then
1457 local s = speedwalk_prefix .. " " .. build_speedwalk (path)
1458 Execute (s)
1459 current_speedwalk = nil
1460 return
1461 end -- if
1462
1463 local dir = table.remove (current_speedwalk, 1)
1464 local room = get_room (dir.uid)
1465 walk_to_roomname = room.roomname
1466 SetStatus ("Walking " .. (expand_direction [dir.dir] or dir.dir) ..
1467 " to " .. walk_to_roomname ..
1468 ". Speedwalks to go: " .. #current_speedwalk + 1)
1469 Send (dir.dir)
1470 expected_room = dir.uid
1471 else
1472 cancel_speedwalk ()
1473 end -- if any left
1474 end -- if
1475
1476end -- start_speedwalk
1477
1478-- cancel the current speedwalk
1479
1480function cancel_speedwalk ()
1481 if current_speedwalk and #current_speedwalk > 0 then
1482 mapprint "Speedwalk cancelled."
1483 end -- if
1484 current_speedwalk = nil
1485 expected_room = nil
1486 hyperlink_paths = nil
1487 SetStatus ("Ready")
1488end -- cancel_speedwalk
1489
1490
1491-- ------------------------------------------------------------------
1492-- mouse-up handlers (need to be exposed)
1493-- these are for clicking on the map, or the configuration box
1494-- ------------------------------------------------------------------
1495
1496function mouseup_room (flags, hotspot_id)
1497 local uid = hotspot_id
1498
1499 if bit.band (flags, miniwin.hotspot_got_rh_mouse) ~= 0 then
1500
1501 -- RH click
1502
1503 if type (room_click) == "function" then
1504 room_click (uid, flags)
1505 end -- if
1506
1507 return
1508 end -- if RH click
1509
1510 -- here for LH click
1511
1512 -- Control key down?
1513 if bit.band (flags, miniwin.hotspot_got_control) ~= 0 then
1514 cancel_speedwalk ()
1515 return
1516 end -- if ctrl-LH click
1517
1518 start_speedwalk (speedwalks [uid])
1519
1520end -- mouseup_room
1521
1522-- ------------------------------------------------------------------
1523-- mouse-over handlers (need to be exposed)
1524-- these are for mousing over a room
1525-- ------------------------------------------------------------------
1526
1527function mouseover_room (flags, hotspot_id)
1528 if type (room_mouseover) == "function" then
1529 room_mouseover (hotspot_id, flags) -- moused over
1530 end -- if
1531end -- mouseover_room
1532
1533function cancelmouseover_room (flags, hotspot_id)
1534 if type (room_cancelmouseover) == "function" then
1535 room_cancelmouseover (hotspot_id, flags) -- cancled mouse over
1536 end -- if
1537end -- cancelmouseover_room
1538
1539function mouseup_configure (flags, hotspot_id)
1540 draw_configure_box = true
1541 draw (current_room)
1542end -- mouseup_configure
1543
1544function mouseup_close_configure (flags, hotspot_id)
1545 draw_configure_box = false
1546 draw (current_room)
1547end -- mouseup_player
1548
1549function mouseup_change_colour (flags, hotspot_id)
1550
1551 local which = string.match (hotspot_id, "^$colour:([%a%d_]+)$")
1552 if not which then
1553 return -- strange ...
1554 end -- not found
1555
1556 local newcolour = PickColour (config [which].colour)
1557
1558 if newcolour == -1 then
1559 return
1560 end -- if dismissed
1561
1562 config [which].colour = newcolour
1563
1564 draw (current_room)
1565end -- mouseup_change_colour
1566
1567function mouseup_change_font (flags, hotspot_id)
1568
1569 local newfont = utils.fontpicker (config.FONT.name, config.FONT.size, config.roomname_TEXT.colour)
1570
1571 if not newfont then
1572 return
1573 end -- if dismissed
1574
1575 config.FONT.name = newfont.name
1576
1577 if newfont.size > 12 then
1578 utils.msgbox ("Maximum allowed font size is 12 points.", "Font too large", "ok", "!", 1)
1579 else
1580 config.FONT.size = newfont.size
1581 end -- if
1582
1583 config.roomname_TEXT.colour = newfont.colour
1584
1585 -- reload new font
1586 WindowFont (win, FONT_ID, config.FONT.name, config.FONT.size)
1587 WindowFont (win, FONT_ID_UL, config.FONT.name, config.FONT.size, false, false, true)
1588
1589 -- see how high it is
1590 font_height = WindowFontInfo (win, FONT_ID, 1) -- height
1591
1592 draw (current_room)
1593end -- mouseup_change_font
1594
1595
1596function mouseup_change_width (flags, hotspot_id)
1597
1598 local width = get_number_from_user ("Choose window width (200 to 1000 pixels)", "Width", config.WINDOW.width, 200, 1000)
1599
1600 if not width then
1601 return
1602 end -- if dismissed
1603
1604 config.WINDOW.width = width
1605 draw (current_room)
1606end -- mouseup_change_width
1607
1608function mouseup_change_height (flags, hotspot_id)
1609
1610 local height = get_number_from_user ("Choose window height (200 to 1000 pixels)", "Width", config.WINDOW.height, 200, 1000)
1611
1612 if not height then
1613 return
1614 end -- if dismissed
1615
1616 config.WINDOW.height = height
1617 draw (current_room)
1618end -- mouseup_change_height
1619
1620function mouseup_change_depth (flags, hotspot_id)
1621
1622 local depth = get_number_from_user ("Choose scan depth (3 to 100 rooms)", "Depth", config.SCAN.depth, 3, 100)
1623
1624 if not depth then
1625 return
1626 end -- if dismissed
1627
1628 config.SCAN.depth = depth
1629 draw (current_room)
1630end -- mouseup_change_depth
1631
1632function mouseup_change_delay (flags, hotspot_id)
1633
1634 local delay = get_number_from_user ("Choose speedwalk delay time (0 to 10 seconds)", "Delay in seconds", config.DELAY.time, 0, 10, true)
1635
1636 if not delay then
1637 return
1638 end -- if dismissed
1639
1640 config.DELAY.time = delay
1641 draw (current_room)
1642end -- mouseup_change_delay
1643
1644function zoom_map (flags, hotspot_id)
1645
1646 if bit.band (flags, 0x100) ~= 0 then
1647 zoom_out ()
1648 else
1649 zoom_in ()
1650 end -- if
1651end -- zoom_map