[Script] Generic Plate Path Sequence Puzzle

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
Post Reply
User avatar
Grimwold
Posts: 511
Joined: Thu Sep 13, 2012 11:45 pm
Location: A Dungeon somewhere in the UK

[Script] Generic Plate Path Sequence Puzzle

Post by Grimwold »

Third in my series of Generic puzzle frameworks is this one for a path puzzle using hidden plates. This could be used to create a puzzle similar to the Medusa hall from the original game.

The idea is that the party has to walk over a series of pressure plates in the correct order. If they go out of sequence, they are teleported back to the start. I managed to script it to allow duplicates, so you can require the party to walk over the same square more than once in a sequence and you can also include 'fake' squares that will always reset the puzzle.

This script can be used any number of times in a dungeon by pasting it into different script entities, (provided the counter and reset_plate variables in each version are changed to unique values). Unfortunately it will not be possible to re-use plates from one puzzle in another, as the script for the 1 variation will end up teleporting you out even if you are correct for a 2nd variation.

You will need to place a door and a number of pressure plates (hidden or otherwise) in your dungeon, noting their ID values, and decide on co-ordinates for a destination for the teleport/reset. Then paste the following script into a script entity in your dungeon and edit the settings in the first few lines to customise it.

Code: Select all

counter_name = "hidden_path_counter_1"
reset_plate_name = "path_reset_1"
door_name = "hidden_path_door_1"
destination = {level=2, x=17, y=18, facing=2}
silent = 1
punish = 1

function checkPath(trigger)
  local plate_check = {"pressure_plate_hidden_2","pressure_plate_hidden_3","pressure_plate_hidden_4","pressure_plate_hidden_5","pressure_plate_hidden_6","pressure_plate_hidden_7","pressure_plate_hidden_8","pressure_plate_hidden_12","pressure_plate_hidden_11","pressure_plate_hidden_10","pressure_plate_hidden_8","pressure_plate_hidden_9"}
  local plate_fake = {"pressure_plate_hidden_13"}
  if initialisation == 1 then
    local unique_plate_check = removeDuplicates(plate_check)
    local unique_plate_fake = removeDuplicates(plate_fake)
    for i=1,#unique_plate_check do
      local plate_entity = findEntity(unique_plate_check[i])
      if plate_entity == nil then
        print(unique_plate_check[i] .. " is missing")
        break
      else
        plate_entity:setTriggeredByParty(true)
        plate_entity:setTriggeredByMonster(false)
        plate_entity:setTriggeredByItem(false)
        plate_entity:setActivateOnce(false)
        plate_entity:addConnector("activate",self.id,"checkPath")
        local tele_name = plate_entity.id .. "_tele"
        local tele_entity = findEntity(tele_name)
        if tele_entity ~= nil then
          print(tele_name .. " already exists")
        else
          spawn("teleporter",plate_entity.level,plate_entity.x,plate_entity.y,plate_entity.facing,tele_name)
            :setTeleportTarget(destination.x,destination.y,destination.facing,destination.level)
            :deactivate()
          if silent == 1 then
            findEntity(tele_name):setInvisible(true):setHideLight(true):setSilent(true):setScreenFlash(false)
          end
        end
      end
    end
    for i=1,#unique_plate_fake do
      local fake_entity = findEntity(unique_plate_fake[i])
      if fake_entity == nil then
        print(unique_plate_fake[i] .. " is missing")
        break
      else
        fake_entity:setTriggeredByParty(true)
        fake_entity:setTriggeredByMonster(false)
        fake_entity:setTriggeredByItem(false)
        fake_entity:setActivateOnce(false)
        fake_entity:addConnector("activate",self.id,"checkPath")
        local tele_name = fake_entity.id .. "_tele"
        local tele_entity = findEntity(tele_name)
        if tele_entity ~= nil then
          print(tele_name .. " already exists")
        else
          spawn("teleporter",fake_entity.level,fake_entity.x,fake_entity.y,fake_entity.facing,tele_name)
            :setTeleportTarget(destination.x,destination.y,destination.facing,destination.level)
            :deactivate()
          if silent == 1 then
            findEntity(tele_name):setInvisible(true):setHideLight(true):setSilent(true):setScreenFlash(false)
          end
        end
      end
    end
    if findEntity(reset_plate_name) ~= nil then
      print(reset_plate_name .. " already exists in dungeon")
    else
      spawn("pressure_plate_hidden",destination.level,destination.x,destination.y,destination.facing,reset_plate_name)
        :setTriggeredByParty(true)
        :setTriggeredByMonster(false)
        :setTriggeredByItem(false)
        :setActivateOnce(false)
        :addConnector("activate",self.id,"destroyTele")
    end
  end
  local range = #plate_check
  local counter_entity = findEntity(counter_name)
  local door_entity = findEntity(door_name)
  if counter_entity == nil then
    spawn("counter",self.level,self.x,self.y,self.facing,counter_name)
      :setInitialValue(range)
      :setValue(range)
      :addConnector("activate",door_entity.id,"open")
      :addConnector("deactivate",door_entity.id,"close")
  end
  local counter_entity = findEntity(counter_name)
  local plate_index = range + 1 - counter_entity:getValue()
  if trigger ~= nil then 
    if trigger.id == plate_check[plate_index] then
      counter_entity:decrement()
    else
      counter_entity:reset()
      if punish == 1 then 
        spawn("poison_cloud",destination.level,destination.x,destination.y,destination.facing)
      end
      activated_tele = trigger.id .. "_tele"
      local tele_entity = findEntity(activated_tele)
      if tele_entity ~= nil then
        tele_entity:activate()
      else
        print(activated_tele .. " is missing. Internal script error")
      end
   --   spawn("teleporter",party.level,party.x,party.y,party.facing,"temp_teleport")
   --     :setTeleportTarget(destination.x,destination.y,destination.facing,destination.level)
    end
  end
  if initialisation == 1 then
    initialisation = 0
  end
end

function destroyTele()
  local tele_entity = findEntity(activated_tele)
  if tele_entity ~= nil then
    tele_entity:deactivate()
  end
end

function isInTable(table, element)
  for _,value in pairs(table) do
    if value == element then
      return true
    end
  end
  return false
end

function removeDuplicates(t)
  local t2 = {}
  local idx = 1
  for k,v in pairs(t) do
    if not isInTable(t2,v) then
      t2[idx] = v
      idx = idx+1
    end
  end
  return t2
end

initialisation = 1
activated_tele = ""

checkPath()

These are the settings you will need to customise from the first few lines of the script:
counter_name = "hidden_path_counter_1"
replace the text hidden_path_counter_1 with a unique name for a counter. You do not need to place the counter in the dungeon, but you need to make sure that this ID is not used for anything else in your dungeon (and does not duplicate the ID of a counter in a subsequent copy of this script)

reset_plate_name = "path_reset_1"
replace the text path_reset_1 with a unique name for a hidden pressure plate. You do not need to place the plate in the dungeon, but you need to make sure that this ID is not used for anything else in your dungeon (and does not duplicate the ID of a plate in a subsequent copy of this script)

door_name = "hidden_path_door_1"
replace the text hidden_path_door_1 with the ID of the door you want to open with this puzzle. You must place this door somewhere in your dungeon.

destination = {level=3, x=17, y=18, facing=2}
replace the numerical values in this list with the co-ordinates of the destination/reset square. Ideally this will be at the entrance to your puzzle.

silent = 1
if set to 1, then the teleporters will activate silently and with no visual effects.. this could be handy for disguising the fact that the party has been teleported.. a bit like the medusa room puzzle in the Original. If you want the bells and whistles, set this to 0

punish = 1
if set to 1, then a poison cloud will be spawned at the reset destination to "punish" the party for an incorrect attempt. If you are a more lenient dungeon master and do not wish to punish the party, set this value to 0

local plate_check = {"pressure_plate_hidden_2","pressure_plate_hidden_3","pressure_plate_hidden_4","pressure_plate_hidden_5","pressure_plate_hidden_6","pressure_plate_hidden_7","pressure_plate_hidden_8","pressure_plate_hidden_12","pressure_plate_hidden_11","pressure_plate_hidden_10","pressure_plate_hidden_8","pressure_plate_hidden_9"}
replace the entries in this list with the IDs of the plates that map out the path you wish the party to take. You can add as many entries here as you want and can include duplicates. If you are placing plates next to each other in room you must be aware that the party can only move in the 4 orthogonal directions (i.e. not diagonal) so plan your list of plates accordingly.

local plate_fake = {"pressure_plate_hidden_13"}
replace the entries in this list with the IDs of the fake plates... these are the ones over which the party should never travel to solve the puzzle. They will automatically reset the puzzle. You can put as many entries as you like in this list (separating each entry with a comma), duplicates are allowed but will be ignored.
Last edited by Grimwold on Thu Dec 20, 2012 3:23 pm, edited 3 times in total.
User avatar
Redweaver
Posts: 68
Joined: Tue Dec 04, 2012 1:13 am
Location: Illinois, USA

Re: [Script] Generic Plate Path Sequence Puzzle

Post by Redweaver »

Awesome script! Thanks, Grimwold. Minor issue with it (very minor): You can't go backwards. Step onto a correct plate from a correct plate and try to step back, you get TP'd as though you'd stepped on a fake plate. Not puzzle breaking, just minorly annoying.
My finished mods:
A Curious Conundrum http://grimrock.nexusmods.com/mods/135 also on Steam Workshop

Current WIP:
Lair of Unarak http://grimrock.nexusmods.com/mods/137
User avatar
Grimwold
Posts: 511
Joined: Thu Sep 13, 2012 11:45 pm
Location: A Dungeon somewhere in the UK

Re: [Script] Generic Plate Path Sequence Puzzle

Post by Grimwold »

Redweaver wrote:Awesome script! Thanks, Grimwold. Minor issue with it (very minor): You can't go backwards. Step onto a correct plate from a correct plate and try to step back, you get TP'd as though you'd stepped on a fake plate. Not puzzle breaking, just minorly annoying.
Yeah, that was a bit of a design decision (so a feature, as opposed to a bug).. it means you have to get the sequence correct in one go. You could have the plates spread out i.e. not next to each other but at key places in the room, to allow for "breaks" in the sequence.

I will have a think as to how I could allow for travel backwards through the sequence... best bet would be to setTriggeredByParty(false) on a plate after each successful step.. but that would screw up with the possibility of allowing duplicates, because a plate that appeared later in the sequence may already have been 'deactivated'
User avatar
LordYig
Posts: 175
Joined: Wed Jul 04, 2012 5:45 pm

Re: [Script] Generic Plate Path Sequence Puzzle

Post by LordYig »

Grimwold wrote:Third in my series of Generic puzzle frameworks is this one for a path puzzle using hidden plates. This could be used to create a puzzle similar to the Medusa hall from the original game.
That's great !
You never stop don't you ? :lol:
User avatar
Grimwold
Posts: 511
Joined: Thu Sep 13, 2012 11:45 pm
Location: A Dungeon somewhere in the UK

Re: [Script] Generic Plate Path Sequence Puzzle

Post by Grimwold »

I had a small issue with this script very occasionally and completely randomly crashing the game.. It was not possible to duplicate with any certainty, though it happen to me twice of the course of a lot of testing and I have had reports that it has happened to others as well.
The most likely explanation is to do with the use of teleporter:destroy() which I used so I could dynamically create and destroy a single teleporter.

However, a script that crashes, even just sometimes, is not acceptable, so for this I have re-written the script using fixed teleporters... which are of course created by the script at game start and are all named after the pressure plate on which they are spawned, with the added "_tele" on the end. You do not need to add the teleporters yourself, but the script will warn you in the console if it finds something with the same id as a teleporter it needs to create.

I will replace the code in the Original Post to avoid confusion... and anyone who has used this script so far should replace theirs with this version.
User avatar
Grimwold
Posts: 511
Joined: Thu Sep 13, 2012 11:45 pm
Location: A Dungeon somewhere in the UK

Re: [Script] Generic Plate Path Sequence Puzzle

Post by Grimwold »

Also added two new settings

Code: Select all

silent = 1
punish = 1
The first allows for you to set whether all the teleporters activate silently and without effects or not. useful for disguising the fact that the party has been teleported.

The second allows you to damage the party each time they get the sequence wrong by spawning a poison cloud at the reset destination.
User avatar
Komag
Posts: 3658
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: [Script] Generic Plate Path Sequence Puzzle

Post by Komag »

nice, anytime extra redundancy and protection are built in is a good thing

(and I love the holiday avatar!)
Finished Dungeons - complete mods to play
Post Reply