I have a script in zMUD and I want to convert it for use in MUSHclient, but I don't know enough about MUSHclient and I don't really have the time right now to figure out how to convert it.
The purpose of this script is to draw a local copy of the continent maps for Aardwolf and update them fluidly with player movement. I don't really know of a better way to describe it other than to say that there's a video (32MB) of what it does at:
http://www.not-porn.com/Aardwolf/BigmapWalker/index.html
It's pretty complicated, but here it is. I hope that someone has the time and willingness to help out.
Note 1: I've been told that there's no analog for #PSUB in MUSHclient, so that'll need to be changed to work in a different way.
Note 2: The goal, beyond making the new script do the same thing (of course), is that there needs to not be a redraw flicker.
Note 3: This may not be the most efficient way to do this even in zMUD. Liberties should be taken in the interest of performance even if it means that the scripts achieve the same result in different ways.
How the map gets cached and displayed isn't important as long as it happens and it's fast.
Also, the internal class (continentupdateinternals) is just used to update the stored maps. This part isn't as important, since it can be greatly simplified with a little help from the server.
This script was made a long time ago. The first form was done even before the continents were officially introduced 2 years ago, because I knew that there would be a forced delay after using the game command to show the map. It was made originally just to get around that delay, and then became an exercise to see what was possible in terms of maintaining a world view for the user with extremely limited assistance from the mud.
My preliminary attempt, which isn't perfect by any means, seems to show that, on my PC at least, which is fairly fast, I can't really see a flicker.
You are welcome to try it, the plugin is:
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Saturday, June 30, 2007, 10:48 -->
<!-- MuClient version 4.13 -->
<muclient>
<plugin
name="Aardwolf_BigMap"
author="Nick Gammon"
id="1768db7d075059476f4879a0"
language="Lua"
purpose="Redirects Aardwolf bigmap messages to another world"
date_written="2008-06-29"
requires="4.28"
version="1.0"
>
<description trim="y">
Redirects the bigmap to the specified world.
</description>
</plugin>
<!-- Triggers -->
<triggers>
<trigger
enabled="y"
match="+------------------------------------------------------------+"
script="map_redirect"
omit_from_output="y"
name="map_start"
sequence="100"
>
</trigger>
<trigger
enabled="n"
match="*"
script="map_redirect"
name="multi_line_map"
omit_from_output="y"
sequence="10"
>
</trigger>
<trigger
enabled="y"
match="^You are in (.+?)\, coordinates\: (\d+?)\, (\d+?)$"
omit_from_output="y"
regexp="y"
script="updatelocation"
sequence="100"
>
</trigger>
<trigger
enabled="y"
match="[Exits: *]"
send_to="12"
sequence="100"
>
<send>
SendNoEcho ("coordinates")
</send>
</trigger>
</triggers>
<aliases>
<alias
match="blah"
enabled="y"
send_to="12"
sequence="100"
>
<send>DrawMap ()</send>
</alias>
</aliases>
<!-- Script -->
<script>
<![CDATA[
map_world = "Aardwolf Big Map"
require "getworld"
map = {}
function InsertLocation (line, x)
t = {}
col = 1
for i, item in ipairs (line) do
-- to make it easier for me, I will expand out multiple-column runs
if item.length > 1 then
for j = item.length, 2, -1 do
table.insert (line, i + 1,
{ text = item.text:sub (j, j),
textcolour = item.textcolour,
backcolour = item.backcolour,
length = 1,
style = style } )
end -- adding new ones
item.length = 1
item.text = item.text:sub (1, 1)
end -- if multiple columns
-- at column, do *
if col == x then
table.insert (t, { text = "*", textcolour = 0x00FFFF, backcolour = 0x000000 } )
else
table.insert (t, item)
end -- if column
col = col + item.length
end -- for
return t
end -- InsertLocation
function DrawMap ()
w = get_a_world (map_world)
if not w then return end
w:DeleteOutput ()
for i, v in ipairs (map) do
if i == y then
send_to_world (map_world, InsertLocation (v, x))
else
send_to_world (map_world, v)
end
end -- for
end -- DrawMap
function updatelocation (name, line, wildcards)
continent = wildcards [1]
x = tonumber (wildcards [2]) + 2 -- allow for border, make 1-relative
y = tonumber (wildcards [3]) + 2 -- allow for border, make 1-relative
DrawMap ()
end -- updatelocation
-- map redirector
function map_redirect (name, line, wildcards, styles)
w = get_a_world (map_world)
if not w then return end
EnableTrigger ("multi_line_map", true) -- capture subsequent lines
if name == "map_start" then
map = {} -- start new map
table.insert (map, styles)
elseif line == "+------------------------------------------------------------+" then
EnableTrigger ("multi_line_map", false) -- no more lines to go
table.insert (map, styles)
DrawMap ()
else
table.insert (map, styles)
end -- if
end -- function map_redirect
function OnPluginInstall ()
-- ensure world file exists
make_world (map_world, [[
<!-- plugins -->
<include name="Send_Input_To_Main_World.xml" plugin="y" />
<include name="Copy_Output.xml" plugin="y" />
]])
local w = get_a_world (map_world)
if w then
w:Note "The map will appear here."
w:SetOption ("do_not_show_outstanding_lines", 1)
w:SetCommandWindowHeight (0) -- no command window
end -- no world
assert (GetOption ("enable_triggers") == 1, "Triggers not enabled")
end -- OnPluginInstall
]]>
</script>
</muclient>
It calls an improved getworld.lua file, see below. This goes in the lua subdirectory under MUSHclient, and you need to trust the plugin, because it opens a world file.
-- table of worlds we couldn't open
cannot_open_world = cannot_open_world or {} -- set flag here if can't open world
-- getworld.lua
--
--[[
See forum thread: http://www.gammon.com.au/forum/?id=7991
This simplifies sending triggered lines to another, dummy, world.
get_a_world (name) - returns a world pointer to the named world, opening it if necessary
send_to_world (name, styles) - sends the style runs to the named world, calling get_a_world
to get it
--]]
-- make the named world, if necessary - adds "extra" lines to the world file (eg. plugins)
function make_world (name, extra)
local filename = GetInfo (57) .. name .. ".mcl"
local f = io.open (filename, "r")
if f then
f:close ()
return
end -- world file exists
f = io.output (filename) -- create world file
assert (f:write ([[
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- MUSHclient world file -->
<!-- Written by Nick Gammon -->
<!-- Home Page: http://www.mushclient.com/ -->
<!-- Generated by getworld.lua plugin -->
<muclient>
<world defaults="y"
name="]] .. name .. [["
site="0.0.0.0"
port="4000"
/>
]] .. extra .. [[
</muclient>
]]))
f:close () -- close world file now
-- and open the file ;P
Open (filename)
end -- make_world
-- open a world by name, return world object or nil if cannot
function get_a_world (name)
-- try to find world
local w = GetWorld (name) -- get world
-- if not found, try to open it in worlds directory
if not cannot_open_world [name] and not w then
local filename = GetInfo (57) .. name .. ".mcl"
Open (filename) -- get MUSHclient to open it
Activate () -- make our original world active again
w = GetWorld (name) -- try again to get the world object
if w then
w:DeleteOutput () -- delete "welcome to MUSHclient" message
else
ColourNote ("white", "red", "Can't open world file: " .. filename)
cannot_open_world [name] = true -- don't repeatedly show failure message
end -- can't find world
end -- can't find world first time around
return w
end -- get_a_world
-- send the styles (eg. from a trigger) to the named world, opening it if necessary
function send_to_world (name, styles)
local w = get_a_world (name)
if w then -- if present
for _, v in ipairs (styles) do
w:ColourTell (RGBColourToName (v.textcolour),
RGBColourToName (v.backcolour),
v.text)
end -- for each style run
w:Note ("") -- wrap up line
end -- world found
return w -- so they can check if we succeeded
end -- send_to_world
Alternatively, delete these lines from the plugin:
... and just make a world file yourself. (IP address 0.0.0.0 and call it "Aardwolf Big Map").
Now to test it, type 'bigmap' which will put the map in the secondary window. I am testing for a specific number of hyphens here, so it works for me outside Aylor, it may not work on other continents right now.
Then as you move the [Exits: *] line triggers sending "coordinates" which are suppressed by the plugin, and update your x/y position.
The plugin cached the whole map's style runs, and then when it gets coordinates it updates where it draws the star. Right now I am doing it in yellow so I can see it, but you can change that back to red.
A couple of problems with it right now:
The red star which it originally got does not move, because that was part of the map. I'm not sure how to know what it should be replaced with.
It doesn't update once you go inside an area.
However as a proof of concept, you may want to see how flicker-free it is for you. This is really only a first preliminary version. :)
It's slower than I would like, but I can't tell if that's because of drawing so much more data or because of something else. It is very promising though.
I do notice an update flicker. It's not as bad as my first try in zMUD was, but it is there. Is it possible to just reset the current output location in the window to be at the beginning and then overwrite the current contents instead of calling that DeleteOutput function? At least then the redraw would be invisible since the map is static.
> The red star which it originally got does not move, because
> that was part of the map. I'm not sure how to know what it
> should be replaced with.
Heh. You have to capture the map twice with your character in different locations and then update the relevant spot in the first stored map with the second one. Don't worry about that, since part of this should be a special bigmap from the server without the red star.
Also, I notice that moving north/south along the map causes some bits of the map to change where they shouldn't, so something is a bit off.
I notice that moving north/south along the map causes some bits of the map to change where they shouldn't, so something is a bit off.
You have sharp eyes. You are right, but you had to look a way away from where the star was, in many cases.
Changed one line to read:
for j = item.length, 2, -1 do
Code above (in the plugin) is amended. Effectively it would have reversed the order of things, which were the same colour, in a style run. Doing it backwards stops that.
As for the flicker, I think a bit is acceptable. After all the main screen scrolls continually, stats keep getting updated, and there is a heap happening.
I found the flicker only slightly noticeable, and if I switched to a smaller font (Dina 8 pt) then there was less drawing, in effect, and I noticed it even less.
It detects the message "No coordinates are available for this area." and if found, minimizes the map, as you don't need it in an area. It also detects if you change continents, and if so requests a new map (and thus it should get one the first time you go outside).
Still haven't solved the problem about the red star in your initial location.
[EDIT] Partly solved it - changed the red star to a space, at least you don't have 2 stars.
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Saturday, June 30, 2007, 10:48 -->
<!-- MuClient version 4.13 -->
<muclient>
<plugin
name="Aardwolf_BigMap"
author="Nick Gammon"
id="1768db7d075059476f4879a0"
language="Lua"
purpose="Redirects Aardwolf bigmap messages to another world"
date_written="2008-06-29"
requires="4.28"
version="1.0"
>
<description trim="y">
Redirects the bigmap to the specified world.
</description>
</plugin>
<!-- Triggers -->
<triggers>
<trigger
enabled="y"
match="^\+[-]{30}([-]{30})?\+$"
script="map_redirect"
omit_from_output="y"
name="map_start"
sequence="100"
regexp="y"
>
</trigger>
<trigger
enabled="n"
match="*"
script="map_redirect"
name="multi_line_map"
omit_from_output="y"
sequence="10"
>
</trigger>
<trigger
enabled="y"
match="^You are in (.+?)\, coordinates\: (\d+?)\, (\d+?)$"
omit_from_output="y"
regexp="y"
script="updatelocation"
sequence="100"
>
</trigger>
<trigger
enabled="y"
match="[Exits: *]"
send_to="12"
sequence="100"
>
<send>
SendNoEcho ("coordinates")
</send>
</trigger>
<trigger
enabled="y"
match="No coordinates are available for this area."
omit_from_output="y"
sequence="100"
send_to="12"
>
<send>
w = get_a_world (map_world)
if not w then return end
w:SetWorldWindowStatus (2) -- minimize map
Activate () -- get main world back
</send>
</trigger>
</triggers>
<aliases>
<alias
match="blah"
enabled="y"
send_to="12"
sequence="100"
>
<send>DrawMap ()</send>
</alias>
</aliases>
<!-- Script -->
<script>
<![CDATA[
map_world = "Aardwolf Big Map"
require "getworld"
map = {}
function InsertLocation (line, x)
t = {}
col = 1
for i, item in ipairs (line) do
-- to make it easier for me, I will expand out multiple-column runs
if item.length > 1 then
for j = item.length, 2, -1 do
table.insert (line, i + 1,
{ text = item.text:sub (j, j),
textcolour = item.textcolour,
backcolour = item.backcolour,
length = 1,
style = style } )
end -- adding new ones
item.length = 1
item.text = item.text:sub (1, 1)
end -- if multiple columns
-- at column, do *
if col == x then
table.insert (t, { text = "*", textcolour = 0x0000FF, backcolour = 0x000000 } )
else
table.insert (t, item)
end -- if column
col = col + item.length
end -- for
return t
end -- InsertLocation
function DrawMap ()
w = get_a_world (map_world)
if not w then return end
w:DeleteOutput ()
w:SetWorldWindowStatus (3) -- restore map
for i, v in ipairs (map) do
if i == y then
send_to_world (map_world, InsertLocation (v, x))
else
send_to_world (map_world, v)
end
end -- for
end -- DrawMap
function updatelocation (name, line, wildcards)
continent = wildcards [1]
if old_continent ~= continent then
Send "bigmap" -- grab map
old_continent = continent
end -- need big map
x = tonumber (wildcards [2]) + 2 -- allow for border, make 1-relative
y = tonumber (wildcards [3]) + 2 -- allow for border, make 1-relative
DrawMap ()
end -- updatelocation
-- map redirector
function map_redirect (name, line, wildcards, styles)
w = get_a_world (map_world)
if not w then return end
EnableTrigger ("multi_line_map", true) -- capture subsequent lines
if name == "map_start" then
map = {} -- start new map
table.insert (map, styles)
elseif string.match (line, "^%+%-+%+$") then
EnableTrigger ("multi_line_map", false) -- no more lines to go
table.insert (map, styles)
-- get rid of red star
for _, v in ipairs (map) do
for _, w in ipairs (v) do
if w.text == "*" and w.textcolour == 0xFF then
w.text = " " -- a space isn't as annoying
end -- if red star
end -- for
end -- for loop
DrawMap ()
else
table.insert (map, styles)
end -- if
end -- function map_redirect
function OnPluginInstall ()
-- ensure world file exists
make_world (map_world, [[
<!-- plugins -->
<include name="Send_Input_To_Main_World.xml" plugin="y" />
<include name="Copy_Output.xml" plugin="y" />
]])
local w = get_a_world (map_world)
if w then
w:Note "The map will appear here."
w:SetOption ("do_not_show_outstanding_lines", 1)
w:SetCommandWindowHeight (0) -- no command window
end -- no world
assert (GetOption ("enable_triggers") == 1, "Triggers not enabled")
end -- OnPluginInstall
]]>
</script>
</muclient>
> You have sharp eyes. You are right, but you had to look a way away from where the star was, in many cases.
I didn't, honest. I noticed jumps from peripheral vision. Peripheral vision is very sensitive to motion.
> As for the flicker, I think a bit is acceptable.
I disagree. Speed and fluidity are pretty important here. Keep in mind that movement across continents is something that will happen very rapidly and for long bursts.
> After all the main screen scrolls continually, stats keep getting updated, and there is a heap happening.
If anything important is going to happen in the main window, it's not going to happen while running around on the continents. Also, the screen doesn't scroll that quickly once you get used to it. Most important is that the whole process needs to not slow down movement noticeably. If it does not work seamlessly, then people will not enjoy using it. Right now it's noticeably slower and visually distracting than what I have in zMUD.
The way I see it, if the script can't be done to perfection then it shouldn't be released to other players. That's why I never published the zMUD script until now.
When you say "noticeably slower" do you mean the drawing? Or the reaction, before it draws?
I notice your original actually had aliases on the directions. That seemed to send the movement (eg. n) followed by "coord". That means you requested the coordinates a bit faster than I did, because I waited for the Exits line. You could try adding this alias (to your main world window should work for now):
That will do what the zMud script did - request the coordinates immediately you move. That might conceivably remove a small delay.
Other than that, I agree the star doesn't move instantly, but the server has had to send the room description, the exits, the coordinates, and the small map. That all takes time.
I can't see why the mapping would be slowing things down to the extent that you say. I put some timing code into the DrawMap function (see below, lines in bold) and these are the results I got:
time taken = 0.0277945862 seconds
time taken = 0.0256247580 seconds
time taken = 0.0270729864 seconds
time taken = 0.0282102814 seconds
time taken = 0.0276387006 seconds
time taken = 0.0348731375 seconds
time taken = 0.0272582054 seconds
time taken = 0.0273210625 seconds
time taken = 0.0271123768 seconds
time taken = 0.0277895577 seconds
time taken = 0.0300616335 seconds
time taken = 0.0296383955 seconds
time taken = 0.0307145096 seconds
time taken = 0.0290098242 seconds
time taken = 0.0280870814 seconds
time taken = 0.0290944718 seconds
We are talking about 1/40th of a second here to totally draw the map. It must take somewhat longer than that for the server to process the command, send the description and everything. I don't think 0.03 of a second drawing a map is going to significantly destroy the enjoyability of using it.
function DrawMap ()
w = get_a_world (map_world)
if not w then return end
timer = GetInfo (232)
w:DeleteOutput ()
for i, v in ipairs (map) do
if i == y then
send_to_world (map_world, InsertLocation (v, x))
else
send_to_world (map_world, v)
end
end -- for
w:SetWorldWindowStatus (3) -- restore map
time_taken = GetInfo (232) - timer
AppendToNotepad ("timing", string.format ("time taken = %3.10f seconds\r\n", time_taken))
end -- DrawMap
Right now it's noticeably slower and visually distracting than what I have in zMUD.
Bearing in mind my comments about the "slower" part, I tried my plugin on the slowest PC I could get my hands on, and I agree there was noticeable flicker there.
A bit of investigation led to a solution. There are now two changes to MUSHclient (this will be version 4.30).
First, when it erases the window, that is deferred until the window is redrawn.
Second, the window redrawing is now done to an offscreen device context, and then "blitted" to the screen in one operation.
This effectively removes the flicker, because there is no split-second where there is a black screen. One moment you have the old map, the next moment you have the new one.
I could not discern any flicker on my old PC with the new code in place.
Oh, by the way. The map data needs to be stored to the local computer and then accessed from there, so that bigmap is only required when the continent actually changes appearance.
Yeah, I have a habit of finalizing notes before I'm actually done with them. I wish there were a way to go back and edit. Anyway, what I mean is...
The part that looks like:
if old_continent ~= continent then
Send "bigmap" -- grab map
old_continent = continent
end -- need big map
Needs to not do that. Instead it needs to pull the map from internal storage. The stored maps should get updated with "bigmap" only when a special update routine is used, and that update routine would only need to be used when a continent actually changes like when a new area goes in. There's no good reason to use "bigmap" if the continent doesn't look any different than the last time you were there, and there are a few good reasons to not do it.
In my version I just store each map as a text file in the mud directory.
Yes, well the plugin started out as a test of the flicker, but seems to have grown into something almost useable in its own right.
The latest version caches each continent in memory, and uses the "save state" functionality of plugins to remember that for next time.
Now it will only need to do "bigmap" the first time you ever visit a continent. We still need to get rid of that star that appears when you first do it, but I think a server change will achieve that.
A bit more tweaking, to allow for changes to continents (eg. some alias), and perhaps resizing the window to exactly hold the map, and it is about done. :-)
Now if you wanted to get *really* fancy you would find a nice font that had trees, water, er ... caves, mountains and so on, so the map looks more realistic.
A simple map of the standard characters to the improved ones would fix the translation, that part would be easy.
> A simple map of the standard characters to the improved ones would fix the translation, that part would be easy.
This will require a special bigmap that uses a unique symbol for each sector type to be compatible with other clients. I tried doing the same thing in the past, and I found that zMUD has a pretty awful bug when using #sub on colored patterns.
http://forums.zuggsoft.com/forums/viewtopic.php?t=28582
If the map does unique symbols and the font is customized anyway, then there's really no reason to do any processing in the client. I think it'd be better to use a font that already has the symbols in the right places.
On the topic of fixing drawing glitches... (maybe I should make a new topic but hey >_>)
When you have relatively length scripts that do relatively much, you get a drawing glitch like in hxxp://qs.merseine.nu/images/mush_bug_drawing.png
The left image is while my plugin is loading, the right is as it is after loading. While this script shows the bug very exaggeratedly, I also get it in other cases. For example, when in a busy fight, my prompt occasionally looks 'double' like the date line in those screenshots. It can get pretty distracting. I found I can minimize it by making sure nothing is swapped out and having lots of RAM, but imo, it could still use some tweaking.
If 'fixing' it means a bigger penalty to speed, you could additionally make a 'fast draw' setting or some such to deal with that issue.
Safari can’t open the specified address.
Safari can’t open “hxxp://qs.merseine.nu/images/mush_bug_drawing.png” because Mac OS X doesn’t recognize Internet addresses starting with “hxxp:”.
Anyway, that bug seems related to the other one you reported.
It is possible the change I made here will correct that as a side-effect, and possibly not. I think it gets confused about the clipping region, and the update region.
If it is not corrected, I could probably make every screen draw draw the whole window, now that the flicker is gone. Let me know how 4.30 works for you.
This will require a special bigmap that uses a unique symbol for each sector type to be compatible with other clients. I tried doing the same thing in the past, and I found that zMUD has a pretty awful bug when using #sub on colored patterns.
http://forums.zuggsoft.com/forums/viewtopic.php?t=28582
Er, why does it need that? I just thought that if you had the "blue water" symbol you would replace that with something more "water-like".
As for the bug report, I do it differently, as I use style runs, and you don't search for ANSI codes in the first place.
Sorry, old habits die hard. Replace hxxp by http. I'm not too fond of having my personal stuff ending up indexed and I don't trust robots.txt to be foolproof either. =)