Anonymissimus' lua thread

Discussion of Lua and LuaWML support, development, and ideas.

Moderator: Forum Moderators

Post Reply
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

Anonymissimus wrote:

Code: Select all

local args = args.__parsed
...
wesnoth.fire("store_locations", args)
While it will work most of the time, it is not quite correct (and possibly slower than needed). You are parsing formulas one time too many, as the [store_locations] tag does it too. The code should look like the following instead:

Code: Select all

-- args has a metatable, so accessing args.x_source executes any formula it contains
local l_iX = tonumber(args.x_source) or ...
-- but args is also read-only, so convert it to a plain table without executing formulas
args = args.__literal
args.x_source = nil
-- formulas will be executed here
wesnoth.fire("store_locations", args)
Anonymissimus wrote:What is MAX_INT in lua really, does it exist, and how to access ? (What is it in wml ? I've never needed it so far...) math.pi is there, nut not max_int.
There are no integers in Lua, only double-precision floating-point numbers. So a real big value would be 1e308. But in fact, there are also infinities. For instance, "1/0" (or "tonumber 'inf'") will be bigger than any finite values.

As for WML, the maximal integer is architecture-dependent. Most of the time it will be 2147483647, but I wouldn't bet on it.
Anonymissimus wrote:Is it good to export and import functionalities like this (to load them when needed)
local locations = functions.require("locations")
?
Yes, it is fine, "require" was added precisely for this purpose. But if you know that you will use "locations" anyway, you can move the loading line before the function, so that it is executed only once rather than each time the function is called.

And if it is in a place which you know is executed only once (e.g. inside a "require"d file), you can replace "require" file by "dofile". In your situation:
Anonymissimus wrote: I need to make new files when the base file becomes too large.
you could just have one base file that will be "require"d, and this base file will perform "dofile" on all the other files to include them.
Anonymissimus wrote:How can functions within a table
local functions = {}
function functions...
...
return functions
be "private" (while some others are "public" ?)
You can't have private functions inside a table. But you can just put them outside the table if you want them to be private.

Code: Select all

local function private_fun() ... end
function functions.public_fun() ... private_fun() ... end
Anonymissimus wrote:Does wesnoth.find_vacant_tile check whether the tile currently being considered has suitable terrain for the unit ?
That's what the documentation says actually. (In fact, it says something even stronger: all the tiles between from the input location to the resulting one are suitable.) Any suggestion on how to improve the wording?
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: some first steps in lua, questions and discussion

Post by Anonymissimus »

silene wrote:

Code: Select all

function get_locations(filter)
  local cfg = filter.__literal
  local var_name = "LUA_store_location"
  cfg.variable = var_name
  wesnoth.fire("store_locations", cfg)
  local t = helper.get_variable_array var_name
  wesnoth.clear_variable var_name
  return t
end
Why filter.__literal ?

Code: Select all

local function get_locations(wtSLF)
	--TODO: __parsed ?, __literal ?
	local sVariable = "LUA_store_locations"
	wtSLF.variable = sVariable
	wesnoth.fire("store_locations", wtSLF)
	local stored_locations = helper.get_variable_array(sVariable)
	wesnoth.fire("clear_variable", {name = sVariable})
	return stored_locations
end
works
(wt = wml table, my variable prefixes are very inconsistent due to the lack of explicit type modifiers...)
Anonymissimus wrote:Does wesnoth.find_vacant_tile check whether the tile currently being considered has suitable terrain for the unit ?
That's what the documentation says actually. (In fact, it says something even stronger: all the tiles between from the input location to the resulting one are suitable.) Any suggestion on how to improve the wording?
You're partly wrong/looks like a bug:

Code: Select all

		wesnoth.set_terrain(2,4,"Ql")--lava chasm
		wesnoth.set_terrain(3,4,"Ql")
		wesnoth.set_terrain(4,4,"Ql")
		wesnoth.set_terrain(4,5,"Ql")
		wesnoth.set_terrain(3,6,"Ql")
		wesnoth.put_unit(3, 5, {type = "Troll", id = "Test"})
		wesnoth.put_unit(2, 5, {type = "Troll"})
		local Test = wesnoth.get_units({id = "Test"})[1]
		wesnoth.message(Test.x .. "," .. Test.y)
		Test = Test.__cfg--without this line, output is 1,5
		local x, y = wesnoth.find_vacant_tile(Test.x, Test.y, Test)
		wesnoth.message(x .. "," .. y)--2,4 - on lava chasm
As I said, [unstore_unit] doesn't check for terrain so I'd be surprised if wesnoth.find_path did. However, the "userdata unitvar" is only in lua accessible.
If that's the intended behavior, it should be mentioned in the documentation.
silene wrote:
Anonymissimus wrote:

Code: Select all

local args = args.__parsed
...
wesnoth.fire("store_locations", args)
While it will work most of the time, it is not quite correct (and possibly slower than needed). You are parsing formulas one time too many, as the [store_locations] tag does it too. The code should look like the following instead:

Code: Select all

-- args has a metatable, so accessing args.x_source executes any formula it contains
local l_iX = tonumber(args.x_source) or ...
-- but args is also read-only, so convert it to a plain table without executing formulas
args = args.__literal
args.x_source = nil
-- formulas will be executed here
wesnoth.fire("store_locations", args)
I found that I need to use __parsed at that point; __literal: x_source = $x1, without:
attempt to index local 'args' (a userdata value)
when args.x_source
In a similar case the substitution works - it might be a bug but I'm having a hard time to track it down...
--------------------------
This code

Code: Select all

		wesnoth.put_unit(3, 5, {type = "Troll", id = "Test"})
		local Test = wesnoth.get_units({id = "Test"})[1]
		Test = Test.__cfg
		wesnoth.put_unit(3, 5)
		wesnoth.put_unit(3, 5, Test)
produces these warning messages:
20100223 23:21:46 warning unit: Unknown attribute 'x' discarded.
20100223 23:21:46 warning unit: Unknown attribute 'y' discarded.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

Anonymissimus wrote:
silene wrote:

Code: Select all

function get_locations(filter)
  local cfg = filter.__literal
Why filter.__literal ?
It's just to confuse you even further...
Disregard that line; not sure what I had in mind at the time, but it doesn't make sense in that context. Indeed, the input is already a WML table, so no need to create one.
Anonymissimus wrote: You're partly wrong/looks like a bug:

Code: Select all

		Test = Test.__cfg--without this line, output is 1,5
		local x, y = wesnoth.find_vacant_tile(Test.x, Test.y, Test)
		wesnoth.message(x .. "," .. y)--2,4 - on lava chasm
As I said, [unstore_unit] doesn't check for terrain so I'd be surprised if wesnoth.find_path did.
Not sure how you can draw such a conclusion; [unstore_unit] and wesnoth.find_vacant_tile don't exercise the same paths of the engine, so there is no reason for them to have the same behavior.

Unless I'm mistaken, (1,5) was the correct answer. But when you replace Test (a unit) by Test.__cfg (a WML table), you are causing wesnoth.find_vacant_tile to just ignore its third argument since it's no longer a unit. Ideally, there should have been an error message stating that the third argument had an incorrect type. But I guess that it would be even more useful if the function allowed a WML table as an argument. Indeed, I wouldn't be surprised if people wanted to find a suitable location before actually putting the unit on the map.
Anonymissimus wrote: I found that I need to use __parsed at that point; __literal: x_source = $x1, without:
attempt to index local 'args' (a userdata value)
when args.x_source
In a similar case the substitution works - it might be a bug but I'm having a hard time to track it down...
I don't understand how you could get such an error message. (In fact I don't understand your paragraph; it seems like some words got lost.) Are you trying to overwrite args.x_source while args is not a __literal value?

Let me detail it one more time. The initial argument is a read-only value; you are not allowed to add/modify/remove attributes, and whenever you read an attribute, the embedded formulas are evaluated on the fly. In order to get a writable WML table, you have to query for __literal or __parsed. The first one returns a complete copy of the object without evaluating any formula, while the second one does evaluate all of them.
Anonymissimus wrote: This code

Code: Select all

		wesnoth.put_unit(3, 5, {type = "Troll", id = "Test"})
		local Test = wesnoth.get_units({id = "Test"})[1]
		Test = Test.__cfg
		wesnoth.put_unit(3, 5)
		wesnoth.put_unit(3, 5, Test)
produces these warning messages:
20100223 23:21:46 warning unit: Unknown attribute 'x' discarded.
20100223 23:21:46 warning unit: Unknown attribute 'y' discarded.
This behavior is expected: your Test table contains some x and y attributes, which the code for creating units does not know what to do with. You could remove them beforehand to avoid the warning. But I guess I will make wesnoth.put_unit a bit friendlier and do the removal itself, since it will probably be common for the argument to contain these two attributes.
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: some first steps in lua, questions and discussion

Post by Anonymissimus »

silene wrote:
Anonymissimus wrote: Why filter.__literal ?
It's just to confuse you even further...
Disregard that line; not sure what I had in mind at the time, but it doesn't make sense in that context. Indeed, the input is already a WML table, so no need to create one.
I thought that I had just understood what your intention was. In the lua reference is said that all tables (and more) are pointer types, which means that doing filter.something=... modifies the variable that the function is called with (in some other function) (tested).
This raises 2 important questions:
-How to make a true (content) copy of a table ? I'm looking for e.g. table.copy but it isn't there.
-How to prevent accidental changing from "parent" function ? E.g.
...
const filter=...
get_locations(filter)
...
silene wrote: Not sure how you can draw such a conclusion; [unstore_unit] and wesnoth.find_vacant_tile don't exercise the same paths of the engine, so there is no reason for them to have the same behavior.

Unless I'm mistaken, (1,5) was the correct answer. But when you replace Test (a unit) by Test.__cfg (a WML table), you are causing wesnoth.find_vacant_tile to just ignore its third argument since it's no longer a unit. Ideally, there should have been an error message stating that the third argument had an incorrect type. But I guess that it would be even more useful if the function allowed a WML table as an argument. Indeed, I wouldn't be surprised if people wanted to find a suitable location before actually putting the unit on the map.
I was assuming that wesnoth.find_vacant_tile called the same function like [unstore_unit]find_vacant=yes.
Consider the use cases for this functionality: It is needed when unstoring units, in which case there's not yet something to get with get_units and possibly when creating units (same) (depending on the placement=... stuff that has just been worked on...)
This terminology "a wml table describing a unit isn't a unit" will be hard to understand/accept for wml authors...

args.__parsed thing: solved

I'm wondering how much lua does make sense in add-ons; tsr was trying to put as much as possible on the lua side, but is probably easier to keep the [event][filter] base on the wml side. [side], [story] etc can't be lua-made; dialogs are probably also better done in wml (lua+wmllint (spellcheck)?!). Whereas whenever some looping/storing/unstoring is done, lua seems handy.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

Anonymissimus wrote:In the lua reference is said that all tables (and more) are pointer types, which means that doing filter.something=... modifies the variable that the function is called with (in some other function) (tested).
This raises 2 important questions:
-How to make a true (content) copy of a table ? I'm looking for e.g. table.copy but it isn't there.
-How to prevent accidental changing from "parent" function ? E.g.
You can't really prevent accidental changes except by passing a "copy" of your table. Note that there are two kinds of copy, a shallow one and a deep one. The shallow copy creates a copy of the table but only at the top level. In other words, shareable values (e.g. tables) of attributes are shared between the two tables. It can be implemented as follow in Lua:

Code: Select all

function shallow_copy(t)
  local u = {}
  for k,v in pairs(t) do
    u[k] = v
  end
  setmetatable(u, getmetatable(t)) -- not useful if t is a plain lua table
  return u
end
The issue with a shallow copy is that you are protecting only the toplevel attributes:

Code: Select all

local t = { a = 1, b = { c = 2 } }
local u = shallow_copy(t)
u.a = 3   -- doesn't modify t.a
u.b.c = 4 -- does modify t.b.c
A deep copy would protect the nested tables too. But the issue is that deep copying is usually impossible. By the way, this is not a restriction of Lua, but a "feature" of almost all the programming languages. To convince yourself, try to imagine what would be the deep copy of the following table:

Code: Select all

local t = { a = 1 }
t.b = t
print(t.b.b.b.b.b.b.b.b.b.b.b.b.a)
To summarize, sorry, there is no proper way of preventing changes. So it's just a matter of trust. Either a function documents that it modifies some of its arguments, or it should perform a shallow copy of all the tables (possibly nested ones) it modifies.
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: some first steps in lua, questions and discussion

Post by Anonymissimus »

Is the cost as returned by wesnoth.find_path supposed to cover the starting hex ? E.g. a path (2,4) (3,5) (4,5) (show_coordinates), movement_cost=1 on all that tiles, is supposed to have cost = 3 ?
I'm pretty sure I've found a situation where the calculated costs are inconsistent. Looks like a bug in the pathfinder (it'll be complicated to build a minimal example...).
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

Anonymissimus wrote:Is the cost as returned by wesnoth.find_path supposed to cover the starting hex ?
No. Note that the cost value takes into account wasted points at end of turns and zones of control. For instance, if, arrived on a given tile, a unit would have only one mp left yet the next tile has a cost of 2, then the actual cost for the next tile will be 3, since the unit will have to wait the next turn to proceed with the move. This may explain the discrepancies you are experiencing.
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: some first steps in lua, questions and discussion

Post by Anonymissimus »

This explained it partly. I'm setting the remaining moves to max_moves for the time of calculation now. But I still have one test case where the max_cost parameter passed to find_path needs to be higher than it should need to be to find a path; if I increase it by one to 7, a path of cost 6 is found which is correct, but if it is only 6, no path is found - everything else is the same. In another test case, the same issue doesn't happen...(It had a mistake, now it happens for both of them. Though still wrong, it's at least consistent...;) )

EDIT
What's the meaning of the parameter side_id (in the description of get_units) ? The lua page is the only place where it appears in the wiki.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

Anonymissimus wrote:What's the meaning of the parameter side_id (in the description of get_units) ? The lua page is the only place where it appears in the wiki.
No idea. (The lua interface just reflects what is available from the C++ side of the engine, so it just happens to be here.) A quick glance at the code tells me that it actually is the "save_id" of the side of the unit. I doubt it is worth keeping that field, so I may remove it from lua. (In fact, I may even remove it from C++, since it is hardly used.)

As for the max_cost behavior, it just reflects the one from the C++ pathfinder. I agree that it doesn't make much sense from a user point of view. I will modify the Lua interface so that it implicitly adds 1 to max_cost before passing it to the engine.
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: some first steps in lua, questions and discussion

Post by Anonymissimus »

Some lua editors feature a remote-debugger, is it or do you think that it may be possible to use it in combination with wesnoth, silene ? If so I'll try to figure it out.
E.g. when passing some arguments from wml code to lua code, the cursor jumps to the start position of the function and mouse-moveover shows the values of the arguments ? Whishful thinking I'm guessing, though...
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

Anonymissimus wrote:Some lua editors feature a remote-debugger, is it or do you think that it may be possible to use it in combination with wesnoth, silene ? If so I'll try to figure it out.
I don't envision any particular difficulty (but I have never tried it). Let's suppose your debugger is remdebug (again, never used it).

You first have to recompile wesnoth after modifying src/scripting/lua.cpp. Add "package" to the allowed modules between lines 2244 and 2247:

Code: Select all

{ "package", luaopen_package },
It should be sufficient for allowing remdebug. But in case it is not, you may also need to prevent the disabling of module "debug" by removing lines 2429 and 2430.

Code: Select all

lua_pushnil(L);
lua_setglobal(L, "debug");
Now, just add at the beginning of one of your first loaded file (or in data/core/_main.cfg) the two following Lua lines for running remdebug.

Code: Select all

require "remdebug.engine"
remdebug.engine.start()
That's it. You should now have remote debugging of your LuaWML scripts from inside Eclipse.

I guess it won't work that well the first time. So I suggest to try with remdebug's text interface first before even trying with eclipse. In other words, before launching Wesnoth, open a console and type

Code: Select all

$ lua controller.lua
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: some first steps in lua, questions and discussion

Post by Anonymissimus »

Thanks.
Although the first step, compiling wesnoth, will already be very frustrating I fear.
Is it correct that add-ons developed with such a modified wesnoth engine are compatible to the "mainline" wesnoth engine ?
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

Anonymissimus wrote:Is it correct that add-ons developed with such a modified wesnoth engine are compatible to the "mainline" wesnoth engine ?
Yes, they will work just the same. Just don't forget to remove the two remdebug lines from your addon before shipping it, otherwise plain engines will choke on them.
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: some first steps in lua, questions and discussion

Post by silene »

I did give it a try and just adding the "package" line to the engine is sufficient to make remdebug work. Patch below.

Code: Select all

--- a/src/scripting/lua.cpp
+++ b/src/scripting/lua.cpp
@@ -2244,6 +2244,7 @@ LuaKernel::LuaKernel()
                { "table",  luaopen_table  },
                { "string", luaopen_string },
                { "math",   luaopen_math   },
+               { "package", luaopen_package },
                { "debug",  luaopen_debug  },
                { NULL, NULL }
        };
I added the three lines below to one of the loaded LuaWML files.

Code: Select all

package.path = package.path .. ";/home/guest/src/scite_debug/remDebug/?.lua"
require "remdebug.engine"
remdebug.engine.start()
There is a small bug in Wesnoth that causes it not to canonicalize filenames, hence preventing breakpoints in other files from working. So I had to modify remdebug a bit first by applying the following patch:

Code: Select all

--- remDebug/remdebug/engine.lua.old	2010-05-19 18:07:29.000000000 +0200
+++ remDebug/remdebug/engine.lua	2010-05-19 18:07:37.000000000 +0200
@@ -154,7 +154,7 @@
   -- check if path is already absolute
   if UNIX then
     if string.sub(path2,1,1) == '/' then
-      return path2
+      return merge_paths('/', string.sub(path2, 2))
     end
   else
     if string.sub(path2,2,2) == ':' then
I couldn't get Eclipse to work, so not sure if it would have gone well. So I tried with another editor that supports remote debugging, namely Scite. It was kind of awful, but I finally was able to get it work.

First I had to modify the debian/rules file of the Scite package and recompile it in order to enable Lua. Indeed it is disabled for all 64-bit architectures on Ubuntu. (No idea why, maybe a leftover from Itanium time.) For 32-bit architectures, there shouldn't be anything to do. For Windows either. Anyway, I did the following

Code: Select all

$ apt-get source scite
$ cd scite-2.03
$ edit debian/rules
# remove the three lines: ifneq (,$(findstring 64,$(DEB_HOST_ARCH))) BUILD_LUA=NO_LUA=1 endif
$ fakeroot debian/rules binary
$ dpkg -i ../scite_2.03-1_amd64.deb
Then I installed scite_debug. The one shipped on their website is for 32-bit architectures. So I had to recompile a small library by typing "make" in the scite_debug directory. Unfortunately, their makefile didn't work, so I had to replace it first by the following one. Again, it would have probably worked out of the box on a 32-bit architecture or on Windows.

Code: Select all

CONFIGFLAGS=$(shell pkg-config --cflags gtk+-2.0 lua5.1)

unix-spawner-ex.so: unix-spawner-ex.c
	gcc -g $(CONFIGFLAGS) -fPIC -shared -o unix-spawner-ex.so unix-spawner-ex.c -lutil
Finally, I added the following lines to the config file named ".SciTEUser.properties" in my home directory. (All hail random caps in filenames.)

Code: Select all

ext.lua.startup.script=/home/guest/src/scite-debug/extman.lua
ext.lua.directory=/home/guest/src/scite-debug/scite_lua
debug.target=:remote.lua
That's it, folks. I then opened a LuaWML file, set a breakpoint (F9), run the debugger (alt+r), run wesnoth. See the screenshot for an example of real-time debugging and mouseover display of variables.

Image

So it was a bit painful, but it worked. I will fix the filename bug and I may add a "--unsafe-lua" option that loads the "package" package. So compiling things won't be needed anymore (at least for a 32-bit architecture).
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: some first steps in lua, questions and discussion

Post by Anonymissimus »

:D So you think that it's worth trying, too. Thanks. This would've been too tough for me to work out, probably...
Will these modifications to the engine be backported to 1.8 ?

Regarding eclipse (without knowing what your problems were), it had worked for me on Debian following a guide from ubuntu forums
http://ubuntuforums.org/showthread.php?t=332674
I had to use the -vm option to lead it to the correct java virtual machine (sun jdk) and there was a bug regarding mouse cursor clicking, IIRC using
export GDK_NATIVE_WINDOWS=true
in the shell before executing eclipse from the same one (or a shell script) solved it. (On windows this problem doesn't exist.)
Still not sure whether eclipse+lua is good idea, though. One of the lua plugins (luaeclipse) is outdated, didn't yet try lunareclipse.
I've seen SciTE mentioned as the "best" lua editor several times (but only the windows version, the linux one seems less good). For windows, it ships already with the "official" lua installer (and with remdebug, I think.)
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
Post Reply