Script: Make Multiple Buttons/Levers

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Script: Make Multiple Buttons/Levers

Post by Lark »

Script: Make Multiple Buttons/Levers

I hate minutiae... and I'll go to any lengths (of minutiae) to automate things I want to do multiple times. Here is a base script that can be used to place multiple buttons/levers on a wall in rows and columns optionally specifying the wall space to contain them and optionally omitting specific cells. It creates the controls (only tested with wall_button and lever objects) and connects them to a sample routine that can process the input. You'll probably not want to use the defaults, but it's a starting point. It defines the controls in the same square and facing as the LUA script - this can be a little tricky manually to get the rotations and adjustments right especially given setWorldPosition() can cause buttons to recede behind a wall even if the coordinates aren't altered! (Thanks Blichew!)

Format: makeControls(caller, control, base, size, space, omit)
  • caller - a required object; indicates the base for the controls; results in coordinates for the controls.
  • control - a required string; defines what is to be placed; "wall_button" and "lever" have been tested.
  • base - a required string; the base string for all objects created. i.e. self.go.id.."_ctrl1_"
  • size - an optional vector; the size of the matrix to be produced. i.e. {3, 3}
  • space - an optional vector; the offset wall space the controls will occupy. Buttons work in {1.1, -.7, 1, -1}, levers in {.9, -.6, 1, -1} although you can push them into adjacent squares with higher values. If nil or partial vectors are specified, missing elements will default to these values.
  • omit - an optional vector of combined row..col numbers to omit. i.e. {11,22} omits cells 1, 1 and 2, 2.
Example 1: a default matrix {6, 6} of buttons:
SpoilerShow

Code: Select all

makeControls(self, "wall_button", self.go.id .."_button_")
Image
Example 2: a default matrix {3, 6} of levers:
SpoilerShow

Code: Select all

makeControls(self, "lever", self.go.id .."_lever_")
Image
Example 3: 3 rows, 5 columns of buttons with 2 levers replacing 4 buttons:
SpoilerShow

Code: Select all

makeControls(self, "wall_button", self.go.id .. "_button_", {3, 5}, {1, .2, 1, -1},  {14, 15, 24, 25})
makeControls(self, "lever", self.go.id .. "_lever_", {1, 2}, {.86, .6, -.5})
The first call defines a {3, 5} matrix of buttons on the upper part of the wall as defined by offsets {1, .2, 1, -1} from the normal position of a single button. It also omits buttons on row 1, columns 4 and 5 and it omits buttons on row 2, also columns 4 and 5 using the specification {14, 15, 24, 25} - this reserves space for our levers, but still allows the 3rd row of buttons to easily line up right.

The second call defines 1 row of 2 levers {1, 2} in the offsets {.86, .6, -.5} but note that one offset, the last, is missing. It defaulted to -1 for levers - see space above.
Image
Example 4: a more sedate 5-buttons in a dice pattern:
SpoilerShow

Code: Select all

makeControls(self, "wall_button", self.go.id .."_button_", {3, 3}, {.4, -.4, .4, -.4},  {21, 23, 12, 32})
Image
The script itself: Version 1.1 - expect revisions here.

Code: Select all

function makeControls(my, what, base, size, range, omit)
  local isLever = what == "lever"
  if size == nil then 
    if what == "lever" then
      size = {3, 6}
    else 
      size = {6, 6} 
      end
    end
  local area
  local rows, cols = size[1], size[2]
  if isLever then area = {.9, -.6, 1, -1}
    else area = {1.1, -.7, 1, -1} 
    end
  if range ~= nil then
    for i = 1, 4 do
      if range[i] ~= nil then
        area[i] = range[i]
        end
      end
    end
  
  local drop = {}
  if omit ~= nil then
    for i = 1, # omit do
      drop[tostring(omit[i])] = true
      end
    end

  local r1, r2, c1, c2 = area[1], area[2], area[3], area[4]
  if rows == 1 then rdiv = 1 else rdiv = rows - 1 end
  if cols == 1 then cdiv = 1 else cdiv = cols - 1 end
  local dr, dc = (r1 - r2) / rdiv, (c1 - c2) / cdiv
  local i, k = self.go.facing % 2 * 2 + 1, (self.go.facing + 1) % 2 * 2 + 1
  local ar, ac = getForward(self.go.facing)
  if self.go.facing == 1 or self.go.facing == 2 then sign = 1
    else sign = -1
    end
  local adj = 0.0001 * (ar - ac)

  for row = 1, rows do
    local r = r1 - (row - 1) * dr
    for col = 1, cols do
      if not drop[row..col] then
        local c = c1 - (col - 1) * dc
        local ctl = spawn(what, my.go.level, my.go.x, my.go.y, my.go.facing, my.go.elevation, base..row..col)
        local vec = ctl:getWorldPosition()
        vec[2] = vec[2] + r
        vec[i] = vec[i] + c * sign
        vec[k] = vec[k] - adj
        ctl:setWorldPosition(vec)
        if isLever then ctl.lever:addConnector("onToggle", my.go.id, "leverFlip")       
          else ctl.button:addConnector("onActivate", my.go.id, "buttonPress") 
          end
        end
      end
    end
  end

function buttonPress(my)
  local pushed = tonumber(string.sub(my.go.id, string.len(my.go.id) - 1))
  hudPrint("button pressed:  "..pushed)
  --Replace the above with your own logic
  end

function leverFlip(my)
  local lev = findEntity(my.go.id)
  local flipped = tonumber(string.sub(my.go.id, string.len(my.go.id) - 1))
  hudPrint("lever flipped:  "..flipped.." has been " .. (lev.lever:isActivated() and "activated" or "deactivated"))
  --Replace the above with your own logic
  end

makeControls(self, "wall_button", self.go.id .."_button_", {3, 3}, {.4, -.4, .4, -.4},  {21, 23, 12, 32})
Enjoy! Comments and suggestions always welcome. -Lark

[EDIT] Updated buttonPress() and leverFlip() examples and added a missing local statement.
Last edited by Lark on Mon Nov 24, 2014 5:39 pm, edited 3 times in total.
User avatar
petri
Posts: 1917
Joined: Thu Mar 01, 2012 4:58 pm
Location: Finland

Re: Script: Make Multiple Buttons/Levers

Post by petri »

Awesome!
swampie
Posts: 9
Joined: Sun Nov 02, 2014 1:12 am

Re: Script: Make Multiple Buttons/Levers

Post by swampie »

Sweet! :shock:
kelly11
Posts: 17
Joined: Mon Oct 08, 2012 12:30 am

Re: Script: Make Multiple Buttons/Levers

Post by kelly11 »

Question: I am very new to all off this, Lua stuff. But eager to learn. My qestion is, is this script save to use, it doenst mess up save games when exported from the editor. I don't really understand the whole local variable thing and the bug it causes mentioned in this post: viewtopic.php?f=22&t=8128 (the one about save games blowing up)

So, one of the biggest things you need to be really careful about in your scripts is making sure that you do not have any references to entities stored in variables that are not local. So, for instance the following script entity will cause a crash (when you try to save from an exported dungeon)...

And am now wondering if I didnt put any of those scripts in my dungeon. I have only tested it in the editor, not yet exported it.

can anyone try to explain to me when a code is local ...and when it is not?
sorry if my question is a bit of the mark, don't know how else to ask it
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Re: Script: Make Multiple Buttons/Levers

Post by Lark »

Thank you Petri - that's the highest compliment I could have received. It encourages me to do more! Thank you!! :D :oops:

[The below was edited for better clarity and accuracy]
Kelly11, I believe the code to be safe. The warning in the thread you indicated was against using a non-local variable (i.e. global to a specific script) to hold an entity. Basically, Grimrock 2 would need to save the entity in the variable across saves, and it crashes when it tries to do so. Think of it this way (and anyone correct me if I get something wrong):

A script is an entity that runs code but it is also a container. By default, variables used anywhere in the container have values everywhere in the container. So a script contains functions which, with the use of local, can themselves become isolated containers as well. The scope limiting can continue into specific clauses (see http://www.lua.org/pil/4.2.html for examples). This limiting of scope is good programming practice because it keeps variables in one function from inadvertently affecting variables in another. The restriction in Grimrock 2 discussed simply says, "don't pass entities between functions using non-local variables" which the makeControls code above does not do, but the example below does for a scalar (ordinary number). The example below won't present any problems, but if you had b referencing a door entity instead, for example, it would cause the game to crash on saving.

Code: Select all

function one()
  b = 12
  print (b)
  end

function two()
  local b = 11
  print(b)
  end

function three()
  print(b)
  end

one()
two()
three()
When you run this, the results printed out are 12, 11, and 12. The function one references a non-local variable b which is set to 12, then printed. The two function sets a local b (local to the function) to a value of 11 and prints it. Function three simply prints b without setting it. But because one() was called first, it set the value of b to 12 . Function two ran before three also, but it used a local b which couldn't affect the non-local b - they are actually two different variables! So function three prints the non-local b and displays 12. The net results is that function one passed the value of variable b to function three using non-local variables.

The prohibition simply says - hey, don't do that if the non-local variable contains an entity! So if a non-local variable contains an entity at save time, the game will crash. I verified this, but a key here is at save time. I actually have a puzzle I'm working on that uses a non-local variable to pass an entity (object) from to a function that I know is going to get executed 1 second later. If I pass the entity and continue, the game can no longer be saved. However, if I pass the entity, use it 1 second later, then set the non-local variable to nil, the game can be saved again. So... while I will probably find a way to not pass an entity this way, there is only one second in the game where it cannot be saved safely. I can live with that for now.

I hope this helps, -Lark
kelly11
Posts: 17
Joined: Mon Oct 08, 2012 12:30 am

Re: Script: Make Multiple Buttons/Levers

Post by kelly11 »

Thanks alot for the time you took to give me an indepth answer. I am beginning to understand it.
User avatar
Drakkan
Posts: 1318
Joined: Mon Dec 31, 2012 12:25 am

Re: Script: Make Multiple Buttons/Levers

Post by Drakkan »

this is so sick :d
Breath from the unpromising waters.
Eye of the Atlantis
NutJob
Posts: 426
Joined: Sun Oct 19, 2014 6:35 pm

Re: Script: Make Multiple Buttons/Levers

Post by NutJob »

Lark, you are one of the top assets to these boards and your scripts are like a well laid out novel. I look forward to seeing where this story goes.

Does this ensure the object has interactivity?

Code: Select all

local adj = 0.0001 * (ar - ac)
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Re: Script: Make Multiple Buttons/Levers

Post by Lark »

NutJob wrote:Does this ensure the object has interactivity?

Code: Select all

local adj = 0.0001 * (ar - ac)
Awe shucks, NutJob, thanks! I just wish I had more time! I'm having to learn LUA all over again, but it's coming back.

And yes, you are correct. The above code ensures that the buttons remain clickable. I would assume it's a minor bug that may be fixed. Thanks, -Lark
NutJob
Posts: 426
Joined: Sun Oct 19, 2014 6:35 pm

Re: Script: Make Multiple Buttons/Levers

Post by NutJob »

Lark wrote:The above code ensures that the buttons remain clickable. I would assume it's a minor bug that may be fixed. Thanks, -Lark
Nice, you just helped me solve an problem with the catacomb_ceiling entity clipping with a tiled dungeon_floor (ceiling).
Post Reply