Needin' some table education & 2 questions

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
Post Reply
Ryeath_Greystalk
Posts: 366
Joined: Tue Jan 15, 2013 3:26 am
Location: Oregon

Needin' some table education & 2 questions

Post by Ryeath_Greystalk »

First generic #1
Following script

Code: Select all

function mainScript()
    local dragon = 0
          if leverone:getLeverState() == "deactivated" then
                 dragon = 1
          else
                 dragon = 2
          end
end
functions fine, but

Code: Select all

function mainScript()
          if leverone:getLeverState() == "deactivated" then
                 local dragon = 1
          else
                 local dragon = 2
          end
get me a global value = nil error. Why?

generic question #2,
I have 4 buttons. I want each one to assign a value to a counter then call the main script.

Code: Select all

function setCounterValue()
         RG_Counter:setValue(1)
         mainScript()
end
doesn't call the main script. What is the proper way to call another script?

Now to the main point of the post. (I can easily work around the other items, just trying to learn a little more stuff)

I have for example a 6x7 area (x grid #'s 12,13,14,15,16,17) and (y grid #'s 16,17,18,19,20,21,22)
and I wan the player to maneuver items around in that space. Now it would be simple if all the squares in that area were available, i.e if x>11 and x<18 and y>15 and y<23 then check if space is empty, move item. But not every space is going to be available because of a few fixed items, and a couple of wall spaces (also in the real situation it's not a perfect rectangle either). So my thought is to have a double layered(?) table with all valid x,y coordinates in it, which I think is possible using the dot, but not quite sure. Something like

PosistionTable = {12.16, 12.17, 13.18} etc, etc.

then I could do something like
(push button to move item north)
local TempPosistionX = itemOne.x
local TempPosistionY = itemOne.y - 1

for i in #PosistionTable

(and at this point I am unsure of the proper way to call the table values)

the compare if there is a match,
if yes check if entitiesAt() shows an empty square,
and if empty move item.

Anyone wish to enlighten me on double layered table use?

And as always I appreciate the time taken to respond.
User avatar
msyblade
Posts: 792
Joined: Fri Oct 12, 2012 4:40 am
Location: New Mexico, USA
Contact:

Re: Needin' some table education & 2 questions

Post by msyblade »

I THINK i can help with the calling a script from a script entity. Try using a period and refer to a defined function that u want to use in the main script. For example

Code: Select all

function setCounterValue()
         RG_Counter:setValue(1)
         mainScript.champTalk --(Assuming i have defined a function for "champTalk" in the mainScript.)
end
Hope it helps!
Currently conspiring with many modders on the "Legends of the Northern Realms"project.

"You have been captured by a psychopathic diety who needs a new plaything to torture."
Hotel Hades
Marble Mouth
Posts: 52
Joined: Sun Feb 10, 2013 12:46 am
Location: Dorchester, MA, USA

Re: Needin' some table education & 2 questions

Post by Marble Mouth »

Hi Ryeath_Greystalk. Your first example is kind of hard to analyze, since it's out-of-context. I see you setting up the variable dragon, but how do you use it? In particular, this snippet:
Ryeath_Greystalk wrote:

Code: Select all

function mainScript()
          if leverone:getLeverState() == "deactivated" then
                 local dragon = 1
          else
                 local dragon = 2
          end
is missing an "end" to conclude the function mainScript . I can say that wherever you declare a local variable, you can use that local variable at the same level or any "deeper" level, but not at any "shallower" level. For example:

Code: Select all

local a = 1
--declaring a variable as "local" outside of any block
--is not much different than just using a non-local variable

print(a) --> 1

do --this is an "artifical" block, betweeen the "do" and
   --the "end", but honestly, all blocks are artificial
	local b = 2
	print(a,b) --> 1   2
end

--b has "gone out of scope" because we are no longer in the
--block where it was declared, so when we print its value,
--we print nil
print(a,b) --> 1   nil

--i doesn't have any value, yet
print(i) --> nil
for i = 1,4 do
	print(a,b,i) --> 1   nil   1
				 --> 1   nil   2
				 --> 2   nil   3
				 --> 3   nil   4
	a = i --this affects variable a, the same variable a
		  --that we declared at the very beginning
end
--i has "gone out of scope" because we are no longer in the
--block where it was declared, so when we print its value,
--we print nil, but a has retained its value from within the loop
print(a,b,i) --> 4   nil   nil

do
	local c = 3
	do
		local d = 4
		do
			local e = 5
			print(c,d,e) --> 3   4   5
		end
		print(c,d,e) --> 3   4   nil
	end
	print(c,d,e) --> 3   nil   nil
end
print(c,d,e) --> nil   nil   nil
There is much more to discuss on the topic of variable scope, but I hope this is a good start.
Ryeath_Greystalk wrote: function setCounterValue()
RG_Counter:setValue(1)
mainScript()
end


doesn't call the main script. What is the proper way to call another script?
That looks correct to me, but as msyblade pointed out, if you're trying to call a function within a different script_entity, then you'll have to qualify it like this:

Code: Select all

scriptEntityId.functionName()
Last edited by Marble Mouth on Fri Mar 15, 2013 2:29 am, edited 1 time in total.
Marble Mouth
Posts: 52
Joined: Sun Feb 10, 2013 12:46 am
Location: Dorchester, MA, USA

Re: Needin' some table education & 2 questions

Post by Marble Mouth »

Ryeath_Greystalk wrote: PosistionTable = {12.16, 12.17, 13.18} etc, etc.
Unfortunately, these will be interpreted as decimal numbers e.g. 12.16 == ( 1216 / 100 ) . As usual, there are many ways to write the code to implement this functionality. Here's one way:

Code: Select all

PositionTable = { { x = 12 , y = 16 } , 
	{ x = 12 , y = 17 } , { x = 13 , y = 18 } , }
	
function NorthButton()

	local x = itemOne.x
	local y = itemOne.y - 1
	
	local PositionValid
	for _ , v in ipairs( PositionTable ) do
		if v.x == x and v.y == y then
			PositionValid = true
			break --this ends the loop, since we already found our match
		end
	end
	if not PositionValid then
		return
	end
	
	for _ in entitiesAt( itemOne.level , x , y ) do
		--if there are any entities at all, then stop here
		return
	end
	
	--Put code here to move the item.
	--This will probably involve destroying the old
	--item and spawning a new one.
end
There's two bits of this code that probably bear more explanation:

Code: Select all

PositionTable = { { x = 12 , y = 16 } , 
	{ x = 12 , y = 17 } , { x = 13 , y = 18 } , }
This creates a table, and makes the variable PositionTable refer to that table. Since we didn't specify any keys for this table, lua just starts counting upwards from 1 to assign the keys. So this is equivalent to:

Code: Select all

PositionTable = { [1] = { x = 12 , y = 16 } , 
	[2] = { x = 12 , y = 17 } , [3] = { x = 13 , y = 18 } , }
The values within PositionTable are themselves tables. We did specify keys ( "x" and "y" ) for these inner tables, so lua uses the keys we specified. The values within these inner tables are individual coordinates. So we could now write for example:

Code: Select all

if PositionTable[2].y == 17 then
	--lua will reach this line, based on the
	--value of PositionTable given above.
end
Note that square brackets ( [] ) and the dot ( . ) are not equivalent. The relationship between them is:

Code: Select all

table.whatever == table["whatever"]
In other words, the syntax table.whatever will always treat "whatever" as a string type key. If you want to use a value of any other type (e.g. number) as a key, then you must use the square bracket syntax. If you want to use the value of a variable as a key, then you must use the square bracket syntax. If you use the dot syntax:

Code: Select all

local i = 10
t.i = 11
then you are actually using the string "i" as the key, which has no relationship at all to the variable i .[/digression]


The other especially notable line is:

Code: Select all

for _ , v in ipairs( PositionTable ) do
ipairs is a native lua function which takes a table as its argument. Using ipairs allows you to loop on all of the key/value pairs in the table, assuming that all of the keys are consecutive positive integers starting with 1 . A table that conforms to these requirements ( such as PositionTable ) is often called an "array". There is a similar function, pairs , which allows you to loop on all of the key/value pairs in a table, regardless of the nature of the keys.

Why did I use the variable names "_" and "v" ? "v" stands for "value". I typically prefer verbose variable names, but when the lifetime of the variable is so short ( just this loop ) I'm a little more relaxed about it. "_" as a variable name is a standard lua convention for a variable that we don't actually care about. I know that the keys will be consecutive positive integers starting with 1, but I'm really only concerned with the values. But ipairs always gives me a key and a value so I have to do something with the key before I can get to the value. So I just assign the key to the "dummy" variable _ . The same convention is also in use in the next loop:

Code: Select all

	for _ in entitiesAt( itemOne.level , x , y ) do
		--if there are any entities at all, then stop here
		return
	end
Here, we don't even care what entity is at the specified location, we just care about the presence or absence of such an entity.


I hope any of this helps :) . And don't forget there is also an official lua guide. It's written with the stand-alone lua interpreter in mind, and talks about quite a few topics that aren't supported in Grimrock, but it's still immensely helpful.
Ryeath_Greystalk
Posts: 366
Joined: Tue Jan 15, 2013 3:26 am
Location: Oregon

Re: Needin' some table education & 2 questions

Post by Ryeath_Greystalk »

Thanks for the replies msyblade & Marble Mouth.

I have browsed the lua guide but I found it more confusing than helpful as it assumes a certain level of previous knowledge. I've been having more success with the wiki lua tutorial.

As for the dragon variable your reply answered a lot. I thought a local variable was good for the entire function no matter where it was originated, but as you point out if it is in a block it is nil after, which is what I was doing.

Your table explanation is one of the clearest I think I've seen so far. I believe you have provided me with enough studying matter to last a few days. I especially appreciate the way you explained the other parts of the scripting such as the difference with the . and [ ], and the '_' variable. That's what the other tutorials are missing, explaining details. Without that I would have spent untold hours trying to figure out the function of the underscore.

Again, many thanks to all.
User avatar
Diarmuid
Posts: 807
Joined: Thu Nov 22, 2012 6:59 am
Location: Montreal, Canada
Contact:

Re: Needin' some table education & 2 questions

Post by Diarmuid »

Hey, thanks too for the explanations! I thought the _ was a indicator to not query that part of the pairs/ipairs function, it didn't occur to me it was an actual variable, albeit a dummy. Great vulgarisation, this will probably help quite a few people here.
Ryeath_Greystalk
Posts: 366
Joined: Tue Jan 15, 2013 3:26 am
Location: Oregon

Re: Needin' some table education & 2 questions

Post by Ryeath_Greystalk »

If anyone is interested here is the final script for my dragon statue sliding puzzle.

Big thanks to Marble Mouth for the lua table instruction. The entitiesAt() didn't work due to detecting pillars and wall grates so I had to manually check the position of each statue.

Here is the code, you'll have to play my mod to see it in action though (if I ever manage to finish it).

Code: Select all


-- Main script

function mainScript()

-- set local variables

	local count = RG_Counter:getValue()
	local dragon = 0
	local current_X = 0
	local current_Y = 0
	local target_X = 0
	local target_Y = 0
	local position_valid = false
	local position_empty = true
	
	if RG_Lever_Two:getLeverState() == "deactivated" then
		if RG_Lever_One:getLeverState() == "deactivated" then
			dragon = 0
		else
			dragon = 1
		end
	elseif RG_Lever_Two:getLeverState() == "activated" then
		if RG_Lever_One:getLeverState() == "deactivated" then
			dragon = 2
		else
			dragon = 3
		end
	end
	
-- set possible locations tables

	PositionTable = {{x=2,y=4},{x=4,y=4},{x=5,y=4},{x=1,y=5},{x=2,y=5},{x=3,y=5},{x=5,y=5},{x=6,y=5},
				  {x=3,y=6},{x=4,y=6},{x=6,y=6},{x=4,y=7},{x=5,y=7},{x=6,y=7},{x=4,y=8}}

	
-- get current location
	
	if dragon == 0 then
		current_X = Dragon_North.x
		current_Y = Dragon_North.y
	elseif dragon == 1 then
		current_X = Dragon_East.x
		current_Y = Dragon_East.y
	elseif dragon == 2 then
		current_X = Dragon_South.x
		current_Y = Dragon_South.y
	elseif dragon == 3 then
		current_X = Dragon_West.x
		current_Y = Dragon_West.y
	end
	
-- set target location

	if count == 0 then
		target_X = current_X
		target_Y = current_Y - 1
	elseif count == 1 then
		target_X = current_X + 1
		target_Y = current_Y
	elseif count == 2 then
		target_X = current_X
		target_Y = current_Y + 1
	elseif count == 3 then
		target_X = current_X - 1
		target_Y = current_Y
	end
	
-- check for valid target cords

	for _,v in ipairs(PositionTable) do
		if v.x == target_X and v.y == target_Y then
			position_valid = true
			break
		else
			position_valid = false
		end
	end
	
-- ceck if target cords are empty

	if Dragon_North.x == target_X and Dragon_North.y == target_Y then
		position_empty = false
	elseif Dragon_East.x == target_X and Dragon_East.y == target_Y then
		position_empty = false
	elseif Dragon_South.x == target_X and Dragon_South.y == target_Y then
		position_empty = false
	elseif Dragon_West.x == target_X and Dragon_West.y == target_Y then
		position_empty = false
	end
	
-- move dragons
	
	if position_valid then
		if position_empty then
			if dragon == 0 then
				Dragon_North:destroy()
				spawn("dragon_statue", 5, target_X, target_Y, 2, "Dragon_North")
			end
			if dragon == 1 then
				Dragon_East:destroy()
				spawn("dragon_statue", 5, target_X, target_Y, 3, "Dragon_East")
			end
			if dragon == 2 then
				Dragon_South:destroy()
				spawn("dragon_statue", 5, target_X, target_Y, 0, "Dragon_South")
			end
			if dragon == 3 then
				Dragon_West:destroy()
				spawn("dragon_statue", 5, target_X, target_Y, 1, "Dragon_West")
			end
		end
	end
	
-- check for success

	if Dragon_North.x == 4 and Dragon_North.y == 6 then
		if Dragon_East.x == 3 and Dragon_East.y == 5 then
			if Dragon_South.x == 4 and Dragon_South.y == 4 then
				if Dragon_West.x == 5 and Dragon_West.y == 5 then
					spawn("temple_ceiling_lamp", 5, 4, 5, 0)
					spawn("temple_ceiling_lamp", 5, 3, 5, 0)
					spawn("temple_ceiling_lamp", 5, 2, 5, 0)
					spawn("temple_ceiling_lamp", 5, 1, 5, 0)
					prison_ceiling_lamp_9:deactivate()
					prison_ceiling_lamp_10:deactivate()
					prison_ceiling_lamp_11:deactivate()
					prison_ceiling_lamp_12:deactivate()
					playSound("secret")
					temple_wall_grating_80:open()
				end
			end
		end
	end
			
	
	
-- test variables
	hudPrint("Counter = "..count..", Dragon is "..dragon)
	hudPrint("Dragon x cord is "..current_X..", Y cord is "..current_Y)
	hudPrint("Target cord is "..target_X..", "..target_Y)
	if position_valid == true then
		hudPrint("Valid move")
	else hudPrint("Invalid move")
	end
	if position_empty == true then
		hudPrint("Space clear")
	else hudPrint("Space full")
	end

end
Post Reply