dMZX Forums: MZX 2.81h+lua - dMZX Forums

Jump to content

  • (5 Pages)
  • +
  • 1
  • 2
  • 3
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

MZX 2.81h+lua Lua support in MZX

#1 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 06 May 2008 - 06:17 AM

Ok, so Dr. Dos was like "put Lua support in MZX" and so I spent a couple of evenings hacking it in.

It's not complete, and it can cause odd crashes in the editor from time to time, but here's what I have so far.

Here are some examples of working Lua scripts:
mzx.star('Hello world!');

while true do
  mzx.go('n', 2);
  mzx.go('s', 2);
  mzx.wait();
end

function when.touch()
  mzx.star('Hello!');
  mzx.char(string.byte('X'));
end
while true do
  mzx.slash('nneessww');
end

function when.dance()
  mzx.slash('wwweee');
  mzx.send('Righty', 'dance');
end
when.dance();
mzx.sleep();
---
function when.dance()
  mzx.slash('eeewww');
  mzx.send('Lefty', 'dance');
end
mzx.sleep();

function when.playershot()
  mzx.color(0xc2);
  mzx.star('OKAY, I GET THE POINT. I NEED MORE IRON');
  when.playershot = function()
    mzx.star('...cut it out. =(');
  end
end
function when.bombed()
  -- Check this out...
  -- (editor) I'm not impressed.
  mzx.star('ARRGHFRRAGGH I\'M ALLERGIC TO BEING BOMBED');
  mzx.die();
end
function when.touch()
  mzx.star('Watch out!');
  mzx.wait(10);
  mzx.shoot('NORTH');
end
mzx.sleep();


To embed Lua in your robots, write each Lua line as a separate comment, and begin the whole block with a ". #!lua" line. That is, "#!lua" in a comment. Robots can be executing in Robotic or Lua mode, depending on the value of the special Robot-specific counter INTERP (0 = Robotic, 1 = Lua). So to run Lua in a robot, you'd do this:

set "INTERP" to "1"
. "#!lua"
. "mzx.star('Hello world!');"
. "mzx.sleep();"


The final mzx.sleep(); is there to prevent the Lua interpreter from falling off the end, which will automatically revert the robot to Robotic state. Of course, you could do this intentionally if you wanted to.

Saving and restoring works as expected. The Lua environment and every robot are saved into the .SAV files. They are incompatible with other versions of MZX. This depends on the Pluto library to work, which is a lightweight (~1500 line) library that adds save states to Lua, basically.

Many operations aren't available in Lua mode at the moment. Robotic and Lua code should also be able to interact more. You should (emphasis on should) be able to exit Lua mode by doing mzx.set('INTERP', 0), but this is completely untested. You can, however, send messages to other robots that are executing in Lua mode, and they can receive them. Just define a function "when.X()" to handle message X, as you can see above.

In order to compile this, you'll need Lua 5.1, Pluto 2.4 (for saved games), and MZX 2.81h. You'll need to apply the patches here:
http://home.uchicago...-patches.tar.gz

You must apply the patch to Pluto. It contains bug fixes to Pluto that will segfault MZX if you don't apply it. If you don't know how to apply patches, then this release is not for you anyway.

Have fun... feel free to ask any questions.
0

#2 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 06 May 2008 - 07:35 AM

Pretty interesting hack. Ignoring all of the inevitable discussion about suitability of this feature in current-day MZX, I've got a few minor technical points..

  • The patches shouldn't contain stuff that's not necessary for them to work. For example, the pluto patch is 450K, most of which is a generated configure script. You probably want to rediff just the file you changed (this patch should also be merged upstream);
  • You should do a "make distclean" on the MZX sources before you generate a diff, that'll ignore the Makefile.platform churn;
  • The World * argument you've introduced to some of the functions should be the first, not the last argument in the prototype. This would be consistent with the rest of the MZX code, and you've already done it that way for some of the functions;
  • The coding style of your changes (whilst generally fine) should be Exo-fied (which basically means using BSD style bracketing, one space indentation on line-broken statements and function protos);
  • exit(0) in game.c? Hmm?
  • There's quite a bit of spurious whitespace in mzxlua.c;
  • The counter.c code has been revamped significantly in current MZX SVN (pertinently, all counters are now correctly versioned). You might want to rebase against SVN, it may even fix some of your editor crashes;
  • C99 comments are preferred, but that really is a minor point.

If you fixed the above up, I'd certainly chuck the patch in the contrib/ directory for people to play around with.

I also think you'd benefit from building some Windows binaries. Most of the community use Windows and they might want to try this out. If anything, it would be an interesting experiment for potential "MZX 3" discussion. Actually, MZX supports cross compiling with mingw32 (on Linux), and there's a bunch of mingw32 binaries available here (lacks Lua obviously) that I use to build release MZXs myself.

--ajs.
0

#3 User is offline   hob nado 

  • Ancient Member
  • PipPipPipPipPipPip
  • Group: Members
  • Posts: 4,718
  • Joined: 23-September 00
  • Gender:Male

Posted 06 May 2008 - 10:32 PM

F0k, I thought of that, but I was too lazy. =O

Kudos! I love Lua.
0

#4 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 07 May 2008 - 05:35 PM

View Postajs, on May 6 2008, 07:35 AM, said:

Pretty interesting hack. Ignoring all of the inevitable discussion about suitability of this feature in current-day MZX, I've got a few minor technical points..

The patches shouldn't contain stuff that's not necessary for them to work. For example, the pluto patch is 450K, most of which is a generated configure script. You probably want to rediff just the file you changed (this patch should also be merged upstream);

The Pluto patch needs that stuff, because it adds Autoconf support. It was using hard-coded Linux-specific stuff in the makefiles before, preventing it from working on Windows. (It also contains bug fixes for x86_64.) I sent it to the maintainer of Pluto... hopefully it'll be integrated soon.

Quote

You should do a "make distclean" on the MZX sources before you generate a diff, that'll ignore the Makefile.platform churn

Did I include Makefile.platform? Whoops, that's a mistake.

Quote

The World * argument you've introduced to some of the functions should be the first, not the last argument in the prototype. This would be consistent with the rest of the MZX code, and you've already done it that way for some of the functions;

I'll try to fix that up.

Quote

The coding style of your changes (whilst generally fine) should be Exo-fied (which basically means using BSD style bracketing, one space indentation on line-broken statements and function protos);

That strikes me as a lot of work for nothing. No offense, but I'm not going to go through 1,000 lines and manually reindent everything.

Quote

exit(0) in game.c? Hmm?

I added that because on my system exiting MZX doesn't exit the program (PulseAudio SDL threads stay around, as far as I can tell), and it was getting obnoxious.

Quote

There's quite a bit of spurious whitespace in mzxlua.c

Huh?

Quote

The counter.c code has been revamped significantly in current MZX SVN (pertinently, all counters are now correctly versioned). You might want to rebase against SVN, it may even fix some of your editor crashes;

Ok.

I rewrote a decent portion of the code yesterday, and I'm a lot more satisfied with the way that Robotic and Lua interact now. MZX now uses the stack machinery already present in the Robotic interpreter. A negative cur_prog_line or stack value indicates a Lua frame stored in the global registry, while positive cur_prog_lines are interpreted as usual. This should be safe, because cur_prog_line was always a signed int, and the top bit wasn't being used before. With this mechanism, Lua code can call Robotic code as a subroutine with a command like mzx.goto("foo"), and Robotic can return to lua with a goto "#return" command. Also, sends and built-in labels like touch() are now handled more elegantly: if the robot has registered a handler for the label, MZX starts a new Lua thread and suspends the old one until the handler finishes.

The upshot of this is that a robot can now handle a message like "shot" and then go back to whatever it was doing, starting where it left off. This is more useful behavior than what was in MZX before, I think, and it comes for free with Lua.
0

#5 User is offline   Dr_Dos 

  • spin1.swf
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,272
  • Joined: 19-September 03
  • Gender:Male

Posted 07 May 2008 - 07:56 PM

This wasn't my idea.

Whatever I'm going to play some MZX Paint.
Posted Image Posted Image
0

#6 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 08 May 2008 - 12:00 AM

As mentioned in my previous post, all of my comments were minor and none need to be acknowledged if you plan to keep improving this hack "for fun". That was really me with my maintainer hat on saying "if ever we collaborated on some version of MZX with Lua these things would have to be changed". Certainly I don't want them to block your progress.

I'm mostly interested to see what (if anything) people make of using MZX in the same way with a completely changed language (pretty much why I want to see a Windows binary for this). IMHO fusing Robotic and Lua together seems like a very bad idea with lots of hidden complexity. My advice is to focus on making Lua a decent (full) replacement for Robotic, perhaps leveraging the Lua compiler's bytecode feature in place of Robotic bytecode. Altering the world file format isn't a problem, because this feature is completely unmergeable for 2.8x series MZX versions anyway.

<ramble>
People's opinions over what needs to change in MZX differ (certainly WRT the mystical "MZX 3"). Some people, like tromdage, obviously thought a completely new GCS was required (e.g. GCSX) with a completely new set of limitations. Other people have proposed extensions to the Robotic would be sufficient to rejuvenate MZX; Exo actually sketched out a backwards compatible extension which he coined "Exotic" (see docs/exotic.txt and docs/exotic_translations.txt), but this has not yet been implemented.

I personally think either a completely new language or an incremental improvement on Robotic is better than intermingling Robotic and Lua, especially in such a way that the editor can't verify the correctness of your Lua.
</ramble>

--ajs.
0

#7 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 08 May 2008 - 01:27 AM

I definitely plan to get a Windows binary up when I think it's in a good enough shape to release. Probably won't be too long from now... I'm not interested in making a shining jewel of perfection, just an experiment. That said, I don't want it to suck, because then people will be soured on the idea forever.

By the way, it's certainly possible to make the Robotic editor verify the correctness of the Lua code. In fact, it used to do that until I made some changes recently to make alt+T in the editor work, and I could easily put it back in.

I also tend to think that backwards compatibility is really, really important. If MZX can't play vanilla 2.51 games, then I'd say it's not really MZX anymore. And nobody wants to rewrite all the code in their robots if they're working on a game and want to try out Lua.
0

#8 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 08 May 2008 - 01:54 AM

Firstly, I'm pleased to hear about the editor integration. I'd like to see this in operation, so I could better experiment with the feature.

I agree with you about the backwards compatibility; it's definitely a hard problem to solve. My concern is that the current implementation relegates Lua to second class status by requiring hacks (semantic comments, Lua code start blocks, INTERP counter). Even things like expressions and the endless counter hacks that have been added over the years are nothing compared to the grotesqueness of a semantic comment[1], and none of these original hacks go away if you want to intermingle Lua and real code.

It goes without saying that Lua has some really great language features to offer, and many of those do "just work". But there are some features that I think would be quite watered down unless most of your code was Lua. I've not looked at this in detail, but things like dealing with Lua data-structures e.g. tables becomes limited to Lua code. Lua hacked in this way further ingrains the idea of extending the language through counters and not through keywords, which has definitely been a mistake from a static analysis point of view[2].

It'll be easier to make a call on the usability of the extension when more commands are bound and the editor can detect faulty Lua programs (within reason). Right now I wouldn't even consider such a feature for 2.8, because it'll pull in a few more dependencies and plenty bugs in a codebase that's in a deep bugfix cycle.

Frankly, I'm absolutely sure I'm being overly fussy about the neatness of the hack (especially WRT some of the other extensions made to MZX in the past), and I'm certain other people will disagree. I don't feel that MZX is at all "mine" to assert creative control over, and if Exo weighed in on this issue or people that actually write games liked/disliked the feature, that could easily alter my perspective.

--ajs.

[1] Does anybody remember the explicit stack stuff?

[2] Originally, Robotic's design (ha) allowed a limited amount of analysis to be performed on user syntax. This made the language relatively easy to learn, and some user-errors were detectable by the editor. This is much less true with modern MZXs, where things like strings and expressions leave a lot of room for user error but which the editor will completely ignore.

(I think MZX definitely lost a lot of its usability when the language was complicated in these ways, and I think if there was ever a language break in some future version, time should be spent making the language better from a static analysis POV. Of course, with some annoyingly ambiguous exceptions, more COULD be done with modern robotic, but it isn't being done at the moment.)
0

#9 User is offline   hob nado 

  • Ancient Member
  • PipPipPipPipPipPip
  • Group: Members
  • Posts: 4,718
  • Joined: 23-September 00
  • Gender:Male

Posted 08 May 2008 - 04:53 AM

View Postajs, on May 7 2008, 07:00 PM, said:

I'm mostly interested to see what (if anything) people make of using MZX in the same way with a completely changed language (pretty much why I want to see a Windows binary for this). IMHO fusing Robotic and Lua together seems like a very bad idea with lots of hidden complexity. My advice is to focus on making Lua a decent (full) replacement for Robotic... [snip]

I've thought about this before and I'm just wondering if the Lua language would even map very well onto the MZX problem domain. Certainly having functions and stronger support for strings and locals would be cleaner than Robotic, but the concurrency model worries me a bit, as well as other things.

The ability to interrupt the flow of other robots through SEND is so engrained in my mental model of MZX that it's hard for me to see it happening any other way. I know that you can rig the same effect with other control structures, but I think it merits some exploration to just get the feel of it. I'd have other questions, too - does every robot get a Lua_state? How is messaging accomplished? Would coroutines fit at all or would we ditch that part of the language entirely? How clunky would the whole thing feel? etc.
0

#10 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 08 May 2008 - 05:53 AM

View PostEsdemo1, on May 8 2008, 05:53 AM, said:

I've thought about this before and I'm just wondering if the Lua language would even map very well onto the MZX problem domain. Certainly having functions and stronger support for strings and locals would be cleaner than Robotic, but the concurrency model worries me a bit, as well as other things.

The ability to interrupt the flow of other robots through SEND is so engrained in my mental model of MZX that it's hard for me to see it happening any other way.

The robots do interrupt each other when messages happen. The only difference is that the robot goes back to what it was doing once it finishes running the message handler (if it was doing anything prior).

Quote

I know that you can rig the same effect with other control structures, but I think it merits some exploration to just get the feel of it. I'd have other questions, too - does every robot get a Lua_state?

Each robot has a stack: exactly the same stack that robots have now. However, each element in the stack can be either a Robotic position or a Lua thread (a lua_State object). You can think of this thread stack as kind of a super-call stack. Only the top frame on the stack ever executes; all other threads are suspended (via coroutine.yield).

Messages are handled this way.
0

#11 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 09 May 2008 - 05:28 AM

I added Lua support to the Robotic editor today.

Screenshot!
Posted Image

Notice that you use Alt+L to switch between Robotic and Lua lines. Internally, the Lua is still stored as comments, but you never see that detail anymore.

Oh yeah, and I also implemented some minor basis library stuff like CHANGE. CHANGE has two syntaxes:

mzx.change('boulder', 'explosion');
mzx.change({ color = 'c0?', thing = 'boulder' }, { thing = 'explosion', param = 'pf0' });

In other words, the color/thing/param parser parses a color/thing/param as either a table that explicitly specifies color/thing/param values or as a string. This should add some convenience to CHANGE, as the Robotic editor lost the ability to parse "change boulder explosion" ever since it got reconstructed.

This post has been edited by Nightwatch: 09 May 2008 - 05:34 AM

0

#12 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 09 May 2008 - 12:18 PM

I think it would be cleaner if interp could go away (with the internal comment hacks) and the user just selects when he places a robot whether it's Lua or Robotic. Inter-robot communication like SEND could be fixed (or limited in some way). I guess the Lua command binding isn't sufficiently mature to achieve this yet.

Do you think this method would be realistically possible to implement?

--ajs.
0

#13 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 09 May 2008 - 03:48 PM

View Postajs, on May 9 2008, 01:18 PM, said:

I think it would be cleaner if interp could go away (with the internal comment hacks) and the user just selects when he places a robot whether it's Lua or Robotic. Inter-robot communication like SEND could be fixed (or limited in some way). I guess the Lua command binding isn't sufficiently mature to achieve this yet.

Do you think this method would be realistically possible to implement?

--ajs.

I agree with you, at some point this is exactly what should happen. Realistically it won't in the first version though, because some things are very tied to Robotic at the moment and will take a lot of time to reimplement in a language-agnostic way (box messages are particularly bad).

I started some basic work on factoring out the individual command logic from runrobo2.c into a new "robotic.c" (which I should probably rename "robo_ops.c"), to avoid code duplication. Hopefully this will make the giant switch statement in runrobo2.c a little less horrendous, as an added bonus.

I'll probably have another release soon. I want to add some more library commands, including an mzx.restart() and mzx.restart(function) in order to allow message handlers to completely change the state of the running robot. I should also add simple syntax checking to the Robotic editor.
0

#14 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 09 May 2008 - 04:16 PM

Okay, this sounds great. As a word of caution, it would be best if you keep the changes you're making to enhance MZX in a generic way (such as the runrobo changes) separate from the Lua changes, where it's sensible to do so. I'll be much more likely to merge your work if it's split up into incremental patches.

EDIT: Perhaps you could even consider an SVN or git tree somewhere (I could give you a branch on the SF project SVN if that's convenient).

Having Lua be completely separate from Robotic in the editor would make it much more likely to be mergeable (from my perspective), especially if the world file can be optimised to accommodate for it.

Keep hacking!

--ajs
0

#15 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 10 May 2008 - 06:23 AM

Here's an example script for the first boss in Caverns. He's significantly harder... I think it's because WAIT actually implicitly multiplies by the cycle count of the robot in Robotic, whereas my wait() function in Lua waits the exact number of frames specified.

local phase = 1;
local threshold = 8;

local fight = function()
    while true do
        if (mzx.counter.playerdist < threshold) then
            mzx.go('opp seek', 1);
            mzx.wait(2);
        elseif (mzx.counter.playerdist == threshold) then
            if (phase == 1) then
                mzx.go('cw seek', 1);
                mzx.wait(2);
            else
                mzx.go('cw opp seek', 1);
                if (mzx.aligned()) then
                    mzx.spitfire('seek');
                    mzx.wait(2);
                end
            end
        else
            if (math.random(0, 20) > 18) then
                mzx.spitfire('seek');
                mzx.wait(2);
            end
        end
    end
end

function when.start()
    mzx.lockself();
    mzx.endMod();
    mzx.wait(2);
    mzx.color(0x08);
    mzx.wait(1);
    mzx.color(0x04);
    mzx.wait(1);
    mzx.star('Do not be fooled by my mediocre appearance.');
    mzx.wait(15);
    mzx.star('I have no yet lost a battle...');
    mzx.wait(15);
    mzx.star('... and I\'m not changing today.');
    mzx.wait(15);
    mzx.star('Hope you enjoy your slow and painful death.');
    mzx.wait(15);
    mzx.mod('cv_boss.mod');
    mzx.unlockself();

    fight();
end

local health = 8;

local die = function()
    mzx.change({ thing = 'Seeker' },        { thing = 'Space' });
    mzx.change({ thing = 'Bullet' },        { thing = 'Space' });
    mzx.change({ thing = 'ShootingFire' },  { thing = 'Space' });
    mzx.lockself();
    mzx.counter.score = mzx.counter.score + 1000;
    mzx.star('AUUUGGGGGGGHHHHHHHHH!!!!!!!!');
    mzx.char(15);
    mzx.color(0x0e);
    mzx.send('quake', 'gog');
    mzx.sfx(36);
    mzx.mod('cv_magic.mod');
    mzx.explode(15);
end

function when.shot()
    mzx.lockself();
    health = health - 1;

    if (phase < 3) then
        mzx.spitfire('seek');
        mzx.spitfire('cw seek');
        mzx.spitfire('cw opp seek');
    else
        mzx.shootseeker('seek');
    end

    mzx.wait(1);

    if (health <= 0) then
        if (phase == 1) then
            mzx.cycle(1);
            health = 5;
            threshold = 6;
            phase = 2;
        elseif (phase == 2) then
            health = 4;
            phase = 3;
        else
            die();
        end
    end
    mzx.unlockself();
end

mzx.cycle(2);

0

#16 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 10 May 2008 - 10:35 AM

Looks pretty good. It's a shame you can't leverage objects more, but as a result maybe "mzx" should just become "m" or something? endMod should be endmod if you're consistent with other double-barrel commands. The aligned() command seems to differ from the Robotic variant? Accessing the counters via '.counter' is cute, and should work fine, since there's no hook facility in MZX.

--ajs.
0

#17 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 10 May 2008 - 07:01 PM

View Postajs, on May 10 2008, 10:35 AM, said:

Looks pretty good. It's a shame you can't leverage objects more, but as a result maybe "mzx" should just become "m" or something?

Yeah, the verbosity of having to type "mzx." all the time is something I've been going back and forth between, but you can always do "local m = mzx" in a robot to get the same effect, so I think I'm going to leave it as "mzx" unless there's some great outcry. You can even use "setmetatable(_G, { __index = mzx })" in the global robot if you want to import everything into the global namespace.

Quote

endMod should be endmod if you're consistent with other double-barrel commands.

I've been following the Robotic names actually, converting them to camel case. So the Robotic command "spitfire" (one word) is "spitfire" in Lua, but the Robotic command "end mod" (with the space) is "endMod" in Lua.

Quote

The aligned() command seems to differ from the Robotic variant?

Hmm? It's a condition in Robotic. Are you thinking of alignedrobot?

Quote

Accessing the counters via '.counter' is cute, and should work fine, since there's no hook facility in MZX.

The raw commands are actually mzx.get() and mzx.set() for counters, just like in Robotic. I just added a metatable for "counter" that goes through them to enable the "mzx.counter" syntax. Metatables enable really powerful stuff: e.g. syntaxes like "mzx.board[5][6] = { thing = 'Explosion', param = 0xf0 }" are all possible right now with just a little added Lua.

Oh, and I also added security checks: all potentially dangerous I/O commands (or worse, those that can load shared libraries written in C) are run through fsafetest().
0

#18 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 10 May 2008 - 07:46 PM

View PostNightwatch, on May 10 2008, 08:01 PM, said:

Yeah, the verbosity of having to type "mzx." all the time is something I've been going back and forth between, but you can always do "local m = mzx" in a robot to get the same effect, so I think I'm going to leave it as "mzx" unless there's some great outcry. You can even use "setmetatable(_G, { __index = mzx })" in the global robot if you want to import everything into the global namespace.

Namespacing is something robotic could actually do with. Take the global robot, where many commands (such as DIE, EXPLODE, etc.) cannot apply. Maybe this would be better as "r" or "robot" Maybe "g" would be "global robot". Either way it's fine for now.

View PostNightwatch, on May 10 2008, 08:01 PM, said:

I've been following the Robotic names actually, converting them to camel case. So the Robotic command "spitfire" (one word) is "spitfire" in Lua, but the Robotic command "end mod" (with the space) is "endMod" in Lua.

Quite right, but in this case Robotic itself is inconsistent. Compare END MOD with ENDGAME or ENDLIFE for example. I can't say I care too much, but camel case is a little bit obscure, especially when robotic itself is case insensitive (arguably Lua should just always use lower case).

View PostNightwatch, on May 10 2008, 08:01 PM, said:

Hmm? It's a condition in Robotic. Are you thinking of alignedrobot?

Lack of caffeine in this case, I was indeed getting the two mixed up. aligned() is obviously wrt the player. Another fairly badly named robotic command (especially when PLAYER appears verbatim in many of the other overloaded commands).

View PostNightwatch, on May 10 2008, 08:01 PM, said:

The raw commands are actually mzx.get() and mzx.set() for counters, just like in Robotic. I just added a metatable for "counter" that goes through them to enable the "mzx.counter" syntax. Metatables enable really powerful stuff: e.g. syntaxes like "mzx.board[5][6] = { thing = 'Explosion', param = 0xf0 }" are all possible right now with just a little added Lua.

The latter is definitely worth doing, as long it can be sanely range checked.

View PostNightwatch, on May 10 2008, 08:01 PM, said:

Oh, and I also added security checks: all potentially dangerous I/O commands (or worse, those that can load shared libraries written in C) are run through fsafetest().

Great. As long as we stick to "the game's directory is its jail", it should continue to work fine. What sort of provisions does the Lua runtime have for a kind of "micro" mode, which doesn't allow Lua scripts to pull in other scripts/dependencies? I think the Lua code must really remain contained to the robot.

--ajs.
0

#19 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 10 May 2008 - 09:18 PM

View Postajs, on May 10 2008, 07:46 PM, said:

Namespacing is something robotic could actually do with. Take the global robot, where many commands (such as DIE, EXPLODE, etc.) cannot apply. Maybe this would be better as "r" or "robot" Maybe "g" would be "global robot". Either way it's fine for now.

Yeah, I think Robotic could really use namespaces too. As for the global robot, I actually have it set up to throw exceptions if you call mzx.explode(), mzx.go(), and so forth in the context of the global robot. I think this encourages much better Robotic programming than silently failing. If you want to silently fail, you can use the standard Lua exception handling mechanisms.

Quote

Quite right, but in this case Robotic itself is inconsistent. Compare END MOD with ENDGAME or ENDLIFE for example. I can't say I care too much, but camel case is a little bit obscure, especially when robotic itself is case insensitive (arguably Lua should just always use lower case).

Now that you mention it, making everything lowercase is actually a very good idea. Consider it done. I always hated the "player char" vs. "playercolor" distinction in Robotic. Even Greg Janson mixed them up (look in runrobo2.c for "spitfire" - he spelled it as "spit fire" in a comment even though the command is "spitfire"!)

Quote

Great. As long as we stick to "the game's directory is its jail", it should continue to work fine. What sort of provisions does the Lua runtime have for a kind of "micro" mode, which doesn't allow Lua scripts to pull in other scripts/dependencies? I think the Lua code must really remain contained to the robot.

Every Lua command that can bring in external dependencies is a function that can be replaced. Before any robot is run, the Lua environment is initialized with the MZX Basis Library, which contains among other things many statements like this:
-- loadfile runs all filenames through fsafetest.
local _loadfile = loadfile;
function loadfile(file)
    return _loadfile(mzx.fsafetest(file));
end

This is safe, because local variables enjoy special privileges in Lua: they can't be accessed in any way by Lua code not in their scope. Once the loadfile() entry in the global symbol table is replaced, there's no way for Robotic code to access the original loadfile() without already having a reference to it, which they can't because they always run after this code. So essentially the MZX Basis Library is in complete control of everything robots have access to.

I realized after writing this that the "mzx" table needs to be write protected for this to be safe. The Blue PiL has an example of how to write protect a namespace: I'll implement that when I release.

By the way, the "interp" variable is going away. I realized that there's no situation where you actually need the variable instead of just starting a robot with Lua code in Lua mode right away, including Robotic/Lua interworking, so the ugly "interp" variable will be removed soon.

As for the question earlier about the suitability of Lua for MZX's problem domain, I look at it like this. The basic requirements of the language that drives MZX's robots are (in roughly descending order of importance):
- Must be easy to use and experiment with for non-programmers.
- Must support serialization/savestates.
- Must support multithreading under application control.
- Must support message passing between threads.
- Must be safe.
- Must be portable.
- Must be lightweight with a minimum of external dependencies.
- Must be GPL compatible.

I'm a believer in not reinventing the wheel unless there's a real need to do so. I think Lua meets all of the above requirements, so it makes sense to me to use Lua in MZX. I guess this is an experiment to see whether I'm right :D

Of course, there's also inertia and people's (totally understandable) uncertainty about having to learn something new. This is especially true in the ZZT/MZX community, which has gone through ZIG, Plastic, a delicious mint julep! and who knows what else without any of them catching on. This is where, I hope, (a) backwards compatibility and (b) stability can come in. Nobody wants to use something that's full of bugs, and nobody wants to rewrite their games.
0

#20 User is offline   Frobozz 

  • Ryiah
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,808
  • Joined: 07-March 01
  • Gender:Not Telling

Posted 11 May 2008 - 12:01 AM

Nightwatch said:

Of course, there's also inertia and people's (totally understandable) uncertainty about having to learn something new. This is especially true in the ZZT/MZX community, which has gone through ZIG, Plastic, a delicious mint julep! and who knows what else without any of them catching on. This is where, I hope, (a) backwards compatibility and (b) stability can come in. Nobody wants to use something that's full of bugs, and nobody wants to rewrite their games.

I agree partially with learning something new. People do tend to stick to something they are used to and feel comfortable with. However its also partially due to there being no decent replacements for what MegaZeux offers both in terms of ease of use and capabilities. Plastic I believe was meant as a clone of ZZT so I don't think that counts and ZIG was really meant as a replacement to ZZT and not MegaZeux. The only of those three touted as a replacement to MegaZeux was BANG - however it lacks both ease of use and capabilities (in fact falling short in pretty much all of its capabilities compared to the state MegaZeux was in when BANG came out). MegaZeux is also superior in terms of platform support and stability whereas BANG is Windows-only and ran for only about 2/3rds of the people that bothered to try it.

My only true complaint for MegaZeux though isn't in terms of graphics or sound (I'm plenty happy with the visual capabilities) but in the language. Robotic is a total nuisance to work with the few times I tried it. If Lua were to be included at some point in the main distribution, I'd probably try working with it again and maybe make something.

This is probably a pointless question, but I seem to recall Lua defaults to double-precision floats. Is there a possibility that could reduce portability? I don't really think so - I'd imagine any platform it'd be useful on would have support for floating-point data types - but it wouldn't hurt to determine that before people start using it and you try to change to an integer type. :D
0

#21 User is offline   Exophase 

  • Laughing on the inside.
  • Group: DigiStaff
  • Posts: 7,155
  • Joined: 23-October 00
  • Gender:Male
  • Location:Cleveland, OH

Posted 11 May 2008 - 12:52 AM

Lua is pretty popular on PSP which doesn't have doubles so I doubt it matters that much (then again, Lua isn't used for anything very interesting so maybe the apps are all just that much slower than they could be)
~ ex0 has a kickass battle engine, without it you sux0rz! without it you sux0rz! ~

"The fact that I say I've one of the best, is called honesty." -Akwende
"Megazeux is not ment to be just ASCII, it is ANSI!" - T-bone6
"I hate it when you get all exo on me." - emalkay

Exophase can what Rubi-cant.
exoware is ware ur ware is exoware
ps. not loking 4 new membrs kthx
0

#22 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 11 May 2008 - 01:12 AM

View PostFrobozz, on May 11 2008, 12:01 AM, said:

This is probably a pointless question, but I seem to recall Lua defaults to double-precision floats. Is there a possibility that could reduce portability? I don't really think so - I'd imagine any platform it'd be useful on would have support for floating-point data types - but it wouldn't hurt to determine that before people start using it and you try to change to an integer type. :D

Lua is actually number type agnostic. By default on PC platforms a lua_Number is a double, but on embedded systems it may be an int. On the PSP I suspect it's actually an int. Defensive Lua code won't rely on numbers having any particular precision (although this is of course unavoidable in some cases).

There is LuaJIT if speed is a concern, although well written Lua should be much, much faster than Robotic, so I doubt it matters much. Also, in MZX my rule of thumb is that commands in the MZX Basis Library that loop should be written in C, where possible. For instance, mzx.change('boulder', 'explosion') is written in C, but mzx.slash('nneessww') is written in Lua because it needs to do a wait(), and a wait() in the middle of C code is impossible given the persistence requirements of MegaZeux.
0

#23 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 11 May 2008 - 01:29 AM

EDIT: When I first wrote this, I only included a) and b), which are of greatest technical relevance. I've added some more, but please help build the list.

I think everything you've said here makes sense. My concerns boil down to:

a) Clear and consistent presentation to the user:
- This means not mixing robotic and Lua in the same robot
- This means having the editor validate the Lua program in no way worse than Robotic would be validated

b) Footprint
- Lua can't be considerably slower, and this should be benchmarked. At least, it should not be cripplingly slower
- The minimum "MZX Lua" memory footprint must be obtained wrt current handheld portability (obviously legacy games remain unaffected)

c) Documentation
- Lua commands/philosophies are not documented in the help file

d) Community acceptance
- Competitions like the DoZ: would they allow one language or both? Is it important?

If b) can't be ascertained satisfactorily, Lua support would have to be a config.sh-time option (limiting the build to Robotic and playing classic worlds) which is really not ideal. If a) isn't implemented, I don't think it's mergeable, frankly. I feel I must also stress that I don't expect answers to all of these questions in your (hopefully upcoming) release, I'm just figuring out where this could next be taken.

--ajs.
0

#24 User is offline   weasel 

  • bleh
  • PipPipPipPipPipPip
  • Group: Members
  • Posts: 7,915
  • Joined: 23-December 00
  • Gender:Male
  • Location:Hillsboro, Oregon

Posted 11 May 2008 - 03:58 AM

View Postajs, on May 10 2008, 06:29 PM, said:

d) Community acceptance
- Competitions like the DoZ: would they allow one language or both? Is it important?

If Lua were implemented in MZX, I see no reason why it should be disallowed for the DoZ, same as SMZX mode and all the other things - Lua does not inherently improve a game, and would take skill to use, so it should be allowed.
Blaugh!
Serious damage to important body parts pretty much ruins any plans you had for living. Bummer.
0

#25 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 11 May 2008 - 08:51 AM

Ok, I ported what I have over to Windows and made a binary. Here it is:

mzx281h+lua2-win32.zip

Let me know if it doesn't work for you.

The source is here: mzx281h+lua2-src.tar.bz2

I added a Lua error screen, so you don't have to check stderr anymore.

Here's a quick rundown of MZX Basis Library at present. It is highly subject to change. Don't expect your robots to be forward compatible at this stage.

To enter Lua mode, press Alt+L when editing a robot.

<direction> = 'n', 'east', 'EAST', 'seek', etc.
<color/thing/param> = 'boulder', { color = 0x0f, param = 'p45', thing = 'explosion' }, etc.
<point> = a specific point like { 2, 4 }, or 'player' (position of player), 'self' (position of self), or 'counters' (position relative to mzx.counter.xpos, mzx.counter.ypos)
<layer> = 'board', 'overlay', or 'vlayer'

mzx._go(<direction>) - goes one space in the specified direction, and DOES NOT wait
mzx.aligned() - true if aligned with player, false otherwise
mzx.avalanche() - scatters boulders across the board
mzx.become(<color/thing/param>) - destroys the robot and replaces it with the given program
mzx.change(<color/thing/param>, <color/thing/param>) - changes all instances of the first CTP to the second CTP
mzx.char(<integer>) - changes the robot's character
mzx.copyblock(<layer>, <point>, width, height, <layer>, <point>) - copies a region of the given size from the first point on the first layer to the second point on the second layer
mzx.cycle(<integer>) - makes the robot execute commands slower
mzx.die() - destroys the robot
mzx.endgame() - causes game over
mzx.endmod() - stops the music
mzx.endlife() - sets health to 0
mzx.explode(<integer>) - causes a robot to explode with the given radius from 0 to 15, destroying it
mzx.fetch(<point>) - returns a color/thing/param in { color = ..., param = ..., thing = ... } form representing the object at the given point on the board
mzx.get(<string>) - returns the value of a counter - note that you can use mzx.counter.X to get counter X as well
mzx.go(<direction>, <integer>) - causes the robot to move the given number of steps in the given direction, waiting for 1 cycle in between each
mzx.goto(<string>) - sends a message to the robot itself
mzx.lockself() - prevents the robot from receiving messages
mzx.match(<color/thing/param>, <color/thing/param>) - returns true if the first color/thing/param matches the pattern specified by the second; thus mzx.match({ color = 0x80, param= 0x00, thing = 'boulder' }, 'boulder') returns true
mzx.mod(<string>) - plays the music with the given filename
mzx.rel(<point>, <direction>) - returns the point that is one step away from the given point in the given direction
mzx.send(<string robot>, <string label>) - delivers the "label" message to the given robot
mzx.set(<string>, <value>) - sets the given counter to the given value - note that you can use e.g. "mzx.counter.X = 3" as well
mzx.sfx(<number>) - plays the given built-in sound effect
mzx.shoot(<direction>) - shoots a bullet in the given direction
mzx.shootseeker(<direction>) - induces the player to delete your game
mzx.slash(<string>) - makes the robot follow the given path, waiting for a cycle in between each step; e.g. "nneessww" will make the robot go in a circle
mzx.spitfire(<direction>) - shoots shooting fire in the given direction
mzx.star(<string>) - displays a message at the bottom, just like the Robotic "*" (star) command; color codes are supported
mzx.unlockself() - allows a robot to receive messages again
mzx.wait(<integer>) - causes the robot to wait the given number of cycles (which defaults to 1 if omitted)

MegaZeux checks the Robot's private "when" table to deliver messages. So you define a "touch" message with something like:
function when.touch()
mzx.star('Ouch!');
end


Known bugs:
- Copying robots that are running Lua via copyblock() can crash MZX.
- Alt+L only affects new lines. You have to press alt+L before adding a new line to your robot program to make it register as Lua.

Not fixable:
- You can't use commands that wait inside certain constructs, such as Lua's pcall(), the __index metamethod, or iterators. This is a well-known Lua issue and is fixable with Coco, but not in a portable or serializable way.

TODO:
- Do syntax checks on the Lua in and when leaving the Robotic editor.
- Add more Robotic commands to the MZX Basis Library.

This post has been edited by Nightwatch: 11 May 2008 - 08:51 AM

0

#26 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 11 May 2008 - 01:46 PM

I ported the changes to SVN. A patch is available (mzx-lua-svn-565.diff). This patch might be replaced as I merge the runrobo changes and bugfixes. I've also provided a tarball (mzxsvnsrc-lua-565.tar.bz2) which rolls up the patch and the current SVN trunk. You can check out MegaZeux's SVN with:

svn co https://megazeux.svn.sourceforge.net/svnroot/megazeux megazeux

Which will grab the last two stable versions and the current trunk.

Initial comments:

  • Moving World * to the beginning of the function prototypes still needs doing;
  • Whitespace is generally inconsistent with the rest of the MZX source (4 space indents in several places instead of 2);
  • Numerous coding style issues remain unfixed;
  • pkg-config shouldn't really be used in Makefile, if anywhere this logic should go in config.sh (also on MINGW /dev/null doesn't exist, so you can't use this trick);
  • In a lot of places, stack variables are declared in the middle of a block -- this is a GNUC/C99 extension and not supported by some of the compilers MZX supports (e.g. MSVC, most recently) -- in all of the cases it should be trivial to move the declarations before the code;

In SVN I also get the following warnings (due to SVN enabling more compiler checks):

src/mzxlua.c: In function ‘mzxlua_file_writer’:
src/mzxlua.c:51: warning: pointer of type ‘void *’ used in arithmetic
src/mzxlua.c: In function ‘mzxlua_push_protection_table’:
src/mzxlua.c:189: warning: passing argument 5 of ‘mzxlua_add_to_protection_table’ discards qualifiers from pointer target type
src/mzxlua.c: In function ‘mzxlua_report_error’:
src/mzxlua.c:490: warning: initialization discards qualifiers from pointer target type
src/robot.c: In function ‘send_robot_direct’:
src/robot.c:1194: warning: passing argument 3 of ‘mzxlua_robot_send_message’ discards qualifiers from pointer target type
src/robo_ops.c: In function ‘robo_op_avalanche’:
src/robo_ops.c:160: warning: implicit declaration of function ‘Random’


They're mostly harmless so I haven't bothered to fix them in the forward-port.

Now, when I mentioned some of this stuff before, you seemed quite closed minded to fixing them. If you want this code to eventually be merged (and that's looking like an increasing possibility), these things would have to be fixed. If it's just a matter of effort, I would gladly do the work for you and send you a patch, but this might interfere with your workflow. Do you have any suggestions?

--ajs.
0

#27 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 11 May 2008 - 03:45 PM

View Postajs, on May 11 2008, 01:46 PM, said:

Now, when I mentioned some of this stuff before, you seemed quite closed minded to fixing them. If you want this code to eventually be merged (and that's looking like an increasing possibility), these things would have to be fixed. If it's just a matter of effort, I would gladly do the work for you and send you a patch, but this might interfere with your workflow. Do you have any suggestions?

--ajs.

I'll take a crack at it. Running mzxlua.c, mzxlua.h, robo_ops.c, and robo_ops.h through indent shouldn't be much of a problem. Thanks for rebasing to the svn head.

Oh, and as you can see there are some memory access changes (memsets and the like). This is a result of running MZX through Valgrind on Linux. I did this to fix a nasty bug that was crashing the Windows version in which runrobo2.c was trying to modify robots that deleted themselves in Lua code, and it picked up a couple of things in other places (like the file browser) that I went ahead and fixed.

This post has been edited by Nightwatch: 11 May 2008 - 03:49 PM

0

#28 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 11 May 2008 - 04:07 PM

Actually we'd already fixed those in SVN, using the same method. My fix was cleaner (using the BSS with static initialization rather than a memset), so I removed your fix from the forward-port. You have made other subtle changes too (the graphics.c one was a good catch) but I've not had a chance to merge them back yet.

I really appreciate somebody else glancing over the code, these days it's just me doing code review.

--ajs.
0

#29 User is offline   ajs 

  • carpe diem
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,614
  • Joined: 21-October 00
  • Gender:Male
  • Location:United Kingdom

Posted 11 May 2008 - 06:05 PM

Reviewing it some more, I think it would be a worthy goal to try to reduce the impact the changes have on the MZX core. For example, World *mzx_world is added to ~10 functions just so that the mzxlua code can access the global lua_State. Perhaps the worst example of this is the invalidate_robot code which involves passing mzx_world through a ~5 deep call stack.

It might be better (especially in the interests of inventing some kind of CONFIG_LUA in the short term) if the lua_State was a global, maybe in a struct with a pointer to the current mzx_world, so that sanity checks could be done. This could be fleshed out on save/load. It's pretty bad practice, and not ordinarily the sort of design I'd recommend, but in this case it might be advantageous to reduce noise in the diff.

I had a further look at the Lua security stuff you've done and it all looks very sensible.

--ajs.
0

#30 User is offline   Nightwatch 

  • Member
  • PipPip
  • Group: Members
  • Posts: 58
  • Joined: 01-September 06

Posted 12 May 2008 - 06:23 AM

Ok, I made a lot of changes today. Fixed up coding style a lot and split up mzxlua.c into mzxllib.c (the Basis Library) and mzxlua.c (the interpreter, robot, and saving logic). Also I added a lot of functionality to the Basis Library that was previously only available in Robotic. I made myself a little spreadsheet of the Robotic commands and classified them into YES (supported in Lua), PARTIAL (can be done in Lua but not in the most user-friendly way), and NO (cannot be done properly in Lua at all). My top priority is to get rid of the NOs, obviously.

Here's a copy and paste to show what functions I have implemented at the moment:
{ "_charid",        mzxlua_lib__charid        },
	{ "_go",            mzxlua_lib__go            },
	{ "aligned",        mzxlua_lib_aligned        },
	{ "avalanche",      mzxlua_lib_avalanche      },
	{ "become",         mzxlua_lib_become         },
  { "box",            mzxlua_lib_box            },
	{ "change",         mzxlua_lib_change         },
	{ "char",           mzxlua_lib_char           },
	{ "color",          mzxlua_lib_color          },
	{ "copyblock",      mzxlua_lib_copyblock      },
	{ "cycle",          mzxlua_lib_cycle          },
	{ "die",            mzxlua_lib_die            },
	{ "endmod",         mzxlua_lib_endmod         },
	{ "fetch",          mzxlua_lib_fetch          },
	{ "fsafetest",      mzxlua_lib_fsafetest      },
	{ "get",            mzxlua_lib_get            },
	{ "goto",           mzxlua_lib_goto           },
  { "gotoxy",         mzxlua_lib_gotoxy         },
  { "loadcharset",    mzxlua_lib_loadcharset    },
  { "loadpalette",    mzxlua_lib_loadpalette    },
  { "lockplayer",     mzxlua_lib_lockplayer     },
	{ "lockself",       mzxlua_lib_lockself       },
	{ "match",          mzxlua_lib_match          },
	{ "mod",            mzxlua_lib_mod            },
	{ "protect",        mzxlua_lib_protect        },
  { "put",            mzxlua_lib_put            },
  { "putrel",         mzxlua_lib_putrel         },
	{ "rel",            mzxlua_lib_rel            },
  { "sam",            mzxlua_lib_sam            },
  { "scrollview",     mzxlua_lib_scrollview     },
	{ "send",           mzxlua_lib_send           },
	{ "set",            mzxlua_lib_set            },
	{ "sfx",            mzxlua_lib_sfx            },
	{ "shoot",          mzxlua_lib_shoot          },
	{ "shootseeker",    mzxlua_lib_shootseeker    },
	{ "spitfire",       mzxlua_lib_spitfire       },
	{ "star",           mzxlua_lib_star           },
	{ "teleportplayer", mzxlua_lib_teleportplayer },
  { "unlockplayer",   mzxlua_lib_unlockplayer   },
	{ "unlockself",     mzxlua_lib_unlockself     },
  { "viewport",       mzxlua_lib_viewport       },
  { "volume",         mzxlua_lib_volume         }


In general, the Lua equivalents of Robotic commands are stricter about their input than the equivalent Robotic commands. They will throw exceptions when input is obviously malformed, except when it would be annoying to do so. (For instance, match() will simply return false if the X/Y coordinates are outside the range of the board, so that robots at the edge of the board don't have to do a lot of exception handling.) Lua has an exception handling mechanism, so in general if you want to catch errors you can.

Also, most Lua commands that alter internal variables (for instance, mzx.volume, mzx._charid, or mzx.viewport) can be called with no arguments and will return the value of that variable. This means that you can use Lua commands where you previously had to use counters.

This post has been edited by Nightwatch: 12 May 2008 - 06:31 AM

0

Share this topic:


  • (5 Pages)
  • +
  • 1
  • 2
  • 3
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

5 User(s) are reading this topic
0 members, 5 guests, 0 anonymous users