New GUI scripting concepts and foundation

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: New GUI scripting concepts and foundation

Post by Xanathar »

I totally agree with all your points, just highlight that the "encounter" abstraction should probably be put on top of everything else and not as the main way to do the gui as it is very easy and fast to use, but not versatile enough.

Should we include some way to "freeze" the world during special tagged guis ? (like avoiding damage or blocking monsters) ?

Also:
I didn't have the chance to check how they work, but has anyone done some experiment on onDrawInventory, onDrawStats and onDrawSkills ?

From the docs it says: "a hook which is called after a champion’s XXX screen has been rendered. The function gets one parameter, a gui context object (see below)."

Which would mean:
  • The gui should make sure to cover the existing gui all the time if overriding
  • We don't know which champion to render the data of :(
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org
User avatar
thomson
Posts: 337
Joined: Thu Sep 13, 2012 9:55 pm
Location: R'lyeh
Contact:

Re: New GUI scripting concepts and foundation

Post by thomson »

Xanathar wrote:Should we include some way to "freeze" the world during special tagged guis ? (like avoiding damage or blocking monsters) ?
That would be great if we can manage that. I know that there is experimental turn mode combat for JKos's framework. I haven't looked at the code at all, but it seems there's some way to "freeze" the world. We must keep in mind that the onDrawGui() call is called every frame (possibly 60 times/sec).
I didn't have the chance to check how they work, but has anyone done some experiment on onDrawInventory, onDrawStats and onDrawSkills ?
No, I haven't, but that's a different problem. I'm focusing now on having the ability to encounter NPCs and interact with them.
[MOD] Eye of the Beholder: Waterdeep sewers forum sources; Grimtools (LoG1 -> LoG2 converter) sources
User avatar
petri
Posts: 1917
Joined: Thu Mar 01, 2012 4:58 pm
Location: Finland

Re: New GUI scripting concepts and foundation

Post by petri »

I think you get the champion as second argument (the docs seem to be wrong).
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: New GUI scripting concepts and foundation

Post by JKos »

I have tested onDrawSkills etc. and they do get champion as a second argument. Other difference is that they are called after the "champion gui" is drawn, so you can draw over of the original gui with them. onDrawGui is called before the champion gui is drawn .

Freezing the world is pretty easy with my framework:

Code: Select all

function freezeWorld()
	fw.addHooks('monsters','freeze',{
			onMove = function() return false end,
			onTurn = function() return false end,		
			onAttack = function() return false end,
			onRangedAttack = function() return false end,
		}
	)
	fw.addHooks('party','freeze',{
			onMove = function() return false end,
			onTurn = function() return false end,		
			onAttack = function() return false end,
			onCastSpell = function() return false end,
		)
	)
end
function unfreezeWorld()
	fw.removeHooks('monsters','freeze')
	fw.removeHooks('party','freeze')
end
But if we want to implement it as a core feature of the grimwidgets, then we have to include my framework to grimwidgets or grimwidgets to my framework. But I would like to keep these projects separated.
Maybe we can keep it as an optional feature, which will work only if my hook framework is installed. Or should we combine grimq, LoG Framework and grimwidgets to one larger project? I think it's possible too.
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: New GUI scripting concepts and foundation

Post by JKos »

Here is my suggestions for the api and general design:

I suggest that the main script entity is named to gw (I would hate to type grimwidgets all the time :) )

gw script entity
  • gw.addElement(gwElement,[hookname]) -- hook name can be gui,inventory,skills or stats
  • gw.removeElement(gwElementId)
  • gw.createElement(elementName,elementId,[x],[y],[width],[height]) -- element factory
  • gw.setKeyHook(key,toggle,callback) -- generic key hook
  • gw.draw(g) -- draws all elements

gwElement api
properties
  • id
  • x
  • y
  • width
  • height
  • parent --parent element, if the element is attached to some other element
  • elements -- table of sub elements
functions
  • gwElement:addElement(gwElement) -- adds a sub element which position is relative to parent
  • gwelement:removeElement(gwElementId)
  • gwElement:close() -- removes the element and all possible sub elements from the gui
  • gwElement:draw(self,g,[champion]) -- draws the element and its subelements recursively

Functional design
I like to design by making imaginary examples, so for example I would like to be able to do things like this.
(This is the low level api suggestion)

Code: Select all

-- create 200x200 rectangle to postion 20,20  
local dialog = gw.createElement('rect','my_dialog',20,20,200,200)
-- create button
local button = gw.createElement('button','my_button',20,150,20,100)
dialog.addElement(button) --  is positioned relatively to dialog element. x=20+20,y=20+150

button.text = 'Bye!'
button.onPress = function(self,g,parent,[champion])
	parent:close() -- remove the parent dialog and its all sub elements from the gui
end

local text = gw.createElement('text','my_text')
text.x = 30
text.y = 20
text.text = 'Hello.'
dialog.addElement(text)
gw.addElement(dialog) -- position: x=20+30,y=20+20

High level api should be something more like this

Code: Select all

local dialog = gw.createDialog('my_dialog',{'top','left'})
dialog.addText('Hello!') -- adds a new text line to the dialog (default posiotion is top left)
dialog.addText('This is my shop.') -- add another line

local button = dialog:addButton('my_button','Bye',{'bottom','left'})
button.setAction('close')
local button_2 = dialog:addButton('my_other_button','Buy items',{'after','my_button'})
button_2.setAction('my_shop_script','drawShopGui') -- similar to addConnector

For this we need to implement some kind of oop like functionality so the widgets could inherit functions and properties from other elements.
Something like this:
gwElement
rect extends gwElement
dialog extends rect
button extends rect
closeButton extends button
..etc..

Unfortunately we don't have access to lua meta methods, but we should be able to do this with the factory pattern.

Example: dialog extends the basic rect element

Code: Select all

createDialog(id,x,y,w,h)
	local dialog = gw.createElement('rect',id,x,y,w,h)
	-- Extend the rect element by defining a new function addButton
	dialog.class = 'dialog'
	dialog.addButton = function(id,x,y,w,h)
		...
		local button = gw.createElement('button',id,x,y,w,h)
		...
		return self.addElement(button)
	end
	...
end
But these are only my suggestions and they should be critisized (I don't feel bad if someone says that it's crap :) ). For example if you think it's too complicated then maybe we shouldn't implement it this way. I know it's doable, but it could be too much work.
Everybody (not just me, Xanathar or thomson) should express they opinions soon as possible because it's much harder to change the api and general design principles after we have actually started coding it.
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: New GUI scripting concepts and foundation

Post by Xanathar »

I suggest that the main script entity is named to gw (I would hate to type grimwidgets all the time :) )
Agree! With grimq I made a 3-letter-too-many mistake.
For this we need to implement some kind of oop like functionality so the widgets could inherit functions and properties from other elements.
I think we can get away using just tables and constructor functions and some convention:
  • An object can be defined by its constructor method (like in Javascript), without, of course, the new operator
  • Inheritance can be obtained simply by calling the parent object as the first line
  • Protected methods to be prefixed with _ . Private methods are simply not exposed at all.
  • Overrides can be done by the constructor overwriting the definition and saving the old one (if needed) as _base_<methodname>
Example:

Code: Select all


function MyObject()
	return {
		aPublicMethod = function(self) 
		
		end,
		
		_aProtectedMethod = function(self)
		
		end,
	}
end


function MyChildObject()
	local self = MyObject()
	
	-- override of aPublicMethod
	self._base_aPublicMethod = self.aPublicMethod
	
	self.aPublicMethod = function(self) 
		
	end
end

The biggest defect I see is that the syntax of extending objects is weird. We can think of a inheritObject method which takes two tables and merges them in the right way:

Code: Select all

function inheritObject(baseObject, this)
	for k, v in pairs(baseObject) do
		if (this[k] == nil) then 
			this[k] = v
		else
			this["_base_" .. k] = v
		end
	end
	return this
end

function MyChildObjectObject()
	return inheritObject(MyObject(), {
		aPublicMethod = function(self) 
			
		end
	})
end
Not yet tested if this could work :), but I think so.

More specific to the gui: can you upload a version of your base module so that we can start from there ? (when you have time of course, no need to hurry!).
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: New GUI scripting concepts and foundation

Post by JKos »

Ok, I pushed the scripts. Actually I made empty dungeon for development purposes and added my scripts in it. I think that the dungeon will be useful for sharing our test/demo scripts (see debug script entity for example). Maybe we could also create a simple functional dungeon for demo purposes in the process, IMO it's more fun to make something cool which can be shown to others than just scripts.

Edit: Your inheritance method should work fine, thanks.

Edit2: I just wanted to add that we can still change everything, so if anyone has better ideas please speak out :)
Also anyone can download the scripts as a zip package from here: https://github.com/xanathar/grimwidgets ... master.zip
or if you know how to use git you can clone the repo in read only mode. And if you wan't to contribute to this project just ask and I'm sure that Xanathar will give you write permission.
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: New GUI scripting concepts and foundation

Post by Xanathar »

And if you wan't to contribute to this project just ask and I'm sure that Xanathar will give you write permission.
Of course! Anyone wanting to join is welcome.

I pulled and tested and it's working. I committed a minor change: a wall button showing the grid you put as debug (which is cool btw).
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: New GUI scripting concepts and foundation

Post by JKos »

That's cool, I pulled your changes and it seems to work fine. But you can of course enable the grid by calling debug.grid(100) (or debug.grid(50) or what ever size you want) from the console, and disable it by calling debug.grid(false) from the console in run time.
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
thomson
Posts: 337
Joined: Thu Sep 13, 2012 9:55 pm
Location: R'lyeh
Contact:

Re: New GUI scripting concepts and foundation

Post by thomson »

JKos wrote:[*]gw.addElement(gwElement,[hookname]) -- hook name can be gui,inventory,skills or stats
I was thinking about fifth type of hooks - events, i.e. dialog boxes that will appear only in specific locations in the dungeon. On the other hand, they should probably be implemented as a special case of gui hook.
JKos wrote:
  • gw.removeElement(gwElementId)
  • gw.createElement(elementName,elementId,[x],[y],[width],[height]) -- element factory
  • gw.setKeyHook(key,toggle,callback) -- generic key hook
  • gw.draw(g) -- draws all elements
We can possibly implement createElement() with one extra optional parameter "parent". It would simplify creating events (no need to call addElement, no need to store leaf buttons)

This code

Code: Select all

-- create 200x200 rectangle to postion 20,20  
local dialog = gw.createElement('rect','my_dialog',20,20,200,200)
-- create button
local button = gw.createElement('button','my_button',20,150,20,100)
dialog.addElement(button) --  is positioned relatively to dialog element. x=20+20,y=20+150
would be simplified to:

Code: Select all

-- create 200x200 rectangle to postion 20,20  
local dialog = gw.createElement('rect','my_dialog',20,20,200,200)
-- create button as a child of dialog. All coordinates become relative to parent
gw.createElement('button','my_button',20,150,20,100, dialog)
Your proposal for gwElement api seems more or less complete. I assume that by functions you meant functions that will be called if element is clicked, right? (and possibly some other functions)
JKos wrote: I like to design by making imaginary examples, so for example I would like to be able to do things like this.
That's very convenient approach. It helps to visualize if the concept is reasonably usable or not.
JKos wrote: High level api should be something more like this

Code: Select all

local dialog = gw.createDialog('my_dialog',{'top','left'})
dialog.addText('Hello!') -- adds a new text line to the dialog (default posiotion is top left)
dialog.addText('This is my shop.') -- add another line

local button = dialog:addButton('my_button','Bye',{'bottom','left'})
button.setAction('close')
local button_2 = dialog:addButton('my_other_button','Buy items',{'after','my_button'})
button_2.setAction('my_shop_script','drawShopGui') -- similar to addConnector
We need more than a dialog. Let me give an example. In EOB1, you meet a wounded dwarf. First dialog is to heal, talk or leave. This step can be implemented using your proposal. But once you choose one option, e.g. heal, you get completely different dialog. How do you propose to model that? Call dialog.removeElement() for all old options and dialog.addElement() for new options?
JKos wrote: For this we need to implement some kind of oop like functionality so the widgets could inherit functions and properties from other elements.
Something like this:
gwElement
rect extends gwElement
dialog extends rect
button extends rect
closeButton extends button
..etc..

Unfortunately we don't have access to lua meta methods, but we should be able to do this with the factory pattern.
That makes sense, given that Lua doesn't support inheritance. I was a bit puzzled when I first read that. BTW we also need to support: image extends rect.
JKos wrote: Example: dialog extends the basic rect element

Code: Select all

createDialog(id,x,y,w,h)
	local dialog = gw.createElement('rect',id,x,y,w,h)
	-- Extend the rect element by defining a new function addButton
	dialog.class = 'dialog'
	dialog.addButton = function(id,x,y,w,h)
		...
		local button = gw.createElement('button',id,x,y,w,h)
		...
		return self.addElement(button)
	end
	...
end
Why do we need to set dialog.class to any specific value? In what context that would be useful? That's an honest question.
I'm total noob regarding Lua. I wrote a small gui for my OpenWRT package, but that was ages ago and I forgot everything.
JKos wrote: Everybody (not just me, Xanathar or thomson) should express they opinions soon as possible because it's much harder to change the api and general design principles after we have actually started coding it.
The design sounds reasonable.

Is there a way to calculate width of a text? Calculating absolute positions if the user specifies 'center' or 'right' will be tricky.
[MOD] Eye of the Beholder: Waterdeep sewers forum sources; Grimtools (LoG1 -> LoG2 converter) sources
Post Reply