Automatic Elevator Script

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Automatic Elevator Script

Post by Lark »

EDIT:
Version 2 is now available with several enchancements including only one script to move around.
viewtopic.php?f=14&t=3723#p38402

ORIGINAL:
I hesitated to post this because it's already obsolete in my book because I thought of a much better way to implement this. However, since it works and I spent so much time on it, I'll post it and hopefully add version 2 soon.

This is an automatic elevator script that builds a one by one elevator (sans good graphics so far, sorry). Download and define the custom sounds, take the script, edit its operational parameters, create a stacked one by one alcove on every floor the elevator is to service, and paste the script on each floor (read the usage section for more information). That's it. You'll have a working elevator connecting all the floors. Note my LED Display: 0-99 script is embedded and can be removed if the floor display is not wanted. I defined a few objects to place in the elevator to make it look a little better, but it really needs custom graphics, I think. I wanted to get the mechanics working and hope for better graphics later.

Please post a link here to your dugneon if you used this code anywhere. I'd really like to see your work! I'd also like to know about any enhancement requests you might have or any assistence you could lend with custom graphics.

Thank you, -Lark

IMAGE:
SpoilerShow
Image
DOWNLOADS: (save target as...)
https://ntg.missouristate.edu/images/lark/elevator.zip

VERSION 1:

Code: Select all

---------------------------------------------------------------------------------------
---  Automatic Elevator:  Version:  1.0.1        Written by Lark, 10-1-2012         ---
---                       Updated:  10-11-2012   Mark.Harsen@missouriState.edu      ---
--                        Special Thanks to:     Lmaoboat and BeNNyBiLL             ---
---------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------- 
---  Parameters:  Set carefully for proper operation!                               ---
---------------------------------------------------------------------------------------
car_tracking = true             --true to set realistic car arrival times and sounds; false for instantly available
anchor_script = "script_ele_1"  --if car tracking, elevator anchor script name where car starts; must exist once; referenced by all elevators in the stack
floor_display = self.level      --nil or the numeric value to display on the back wall of the elevator
upper_limit = 1                 --the highest level serviced; don't look for an elevator above this
lower_limit = 25                --the lowest level serviced; don't look for an elevator below this (tried to make this automatic)
auto_close = true               --true if the door closes automatically after the elevator arrives or after it is triggered open
initially_opened = false        --specifies if the door is initially opened or not
floor_delay = 3                 --delay in seconds the car must travel per floor; final floor is always 2 seconds or more to allow for arrival sound
call_by_button = {1,0,0}        --nil for call by plate or {left-offset, depth-offset, rotation-offset} from in front of door; example placements:
                                --to left {1,0,0}; to right {-1,0,0}, right facing {0,0,1}; opposite door {0,0,2}; left facing {0,0,3}; behind {0,-3,2}
--------------------------------------------------------------------------------------- 
---  Usage Section:                                                                 ---
---------------------------------------------------------------------------------------
--
-- 1.  Download and define custom sounds
-- 2.  Set parameters as desired for a given stack of an elevator.  You may skip step 3 if elevator car tracking is not to occur
-- 3.  Set the upper_limit and lower_limit to the top floor and bottom floor serviced by the elevator (optional but recommended)  
-- 4.  Pick the name of one of the scripts (i.e. the first) and set "anchor_script" to this name - the elevator car will start here
-- 5.  Create a one by one square "alcove" with only one side open, the same side, on every floor to serviced by the elevator
-- 6.  Manually set graphics for elevator walls, ceiling, and floor - the script can't do this and replace the existing graphics.  
-- 7   Place the modifiec version of this script in every alcove on every floor facing the opening - (version 2 will be a single script!)
-- 8.  All should face the same direction for a given elevator.  To create multiple elevators, use a different "anchor_script" name.
-- 9.  Change the "call_by_button" field in each script to place the call button on each floor as desired.  nil = call by pressure plate.
-- 10. That should be it.  The outside button calls the elevator, the inside button moves the car in the direction of the lever.

--------------------------------------------------------------------------------------- 
---  Function Section:                                                              ---
---------------------------------------------------------------------------------------

---
---  round to nearest integer
---
function round(num)
  local floor = math.floor(num)
  local ceiling = math.ceil(num)
  if (num - floor) >= 0.5 then
    return ceiling
    end
  return floor
  end

---
---  Remember what floor our elevator is on - used to delay calls if appropriate (called remotely)
---
function carLocation(n)
  if n ~= nil then carFloor = n end
  return carFloor
  end

---
---  Call the elevator
---
function elevatorCall()
  if callIsActive then return end
  if anchor.carLocation() == self.level or not car_tracking then
    remoteOpen()
    return
    end
  callIsActive = true
  wait = math.max(0, math.abs(anchor.carLocation() - self.level) * floor_delay - 2)
  timerGone:setTimerInterval(wait)
  timerGone:activate()
  end

---
---  Elevator Just arrived on our floor:  play bell and open door
---
function justArrived()
  timerGone:deactivate()
  playSound("elevatorRun2")
  timerNear:activate()
  end

---
---  Our inside button closes the door, waits, then starts elevatorMove()
---
function doorClose()
  if elevatorIsActive then
    playSound("elevatorBuzz")
    return
    end

  elevatorIsActive = true
  if lever:getLeverState() == "activated"
    then dir = 1
    else dir = -1
    end

  if self.level + dir < upper_limit or self.level + dir > lower_limit then
    playSound("elevatorBuzz")
    elevatorIsActive = false
    return
    end

  --Find out if an elevator (any script for now) exists in the desired direction of travel
  IsPartnerOkay = false
  for entity in entitiesAt(self.level + dir, self.x, self.y) do
    if entity.name == "script_entity" then
      if door:isClosed() then
        --door:setDoorState("closed")      --may not really be closed yet, so force it.  [doesn't work]
        elevatorMove()
        return
        end
      door:close()
      timerClose:activate()
      IsPartnerOkay = true
      break
      end
    end
  if IsPartnerOkay == false then
    playSound("elevatorBuzz")
    elevatorIsActive = false
    end
  end

---
---  Go up or down based on the lever, play elevator sound, shake car, and schedule elevatorBell()
---
function elevatorMove()
  timerClose:deactivate()
  mover:setTeleportTarget(self.x, self.y, 0, self.level + dir)
  playSound("elevatorRun4")
  party:shakeCamera(.02,4.3)
  timerRide:activate()
  end

---
---  Deactivate timerRide timer, duplicate lever state, signal arrival, teleport the party, and activate open timer
---
function elevatorArrive()
  timerRide:deactivate()

  for entity in entitiesAt(party.level + dir, party.x, party.y) do
    if entity.name == "script_entity" then
      entity.remoteLever(lever:getLeverState())
      break
      end
    end

  playSound("elevatorBell")
  mover:activate()
  timerOpen:activate()
  end

---
---  Open the door above or below our original location, deactivate the teleporter, and stop last timer
---
function elevatorOpen()
  timerOpen:deactivate()
  mover:deactivate()
  for entity in entitiesAt(party.level, party.x, party.y) do
    if entity.name == "script_entity" then
      entity:remoteOpen()
      break
      end
    end
  elevatorIsActive = false
  end

---
---  Open the door where the party was teleported (called locally and remotely)
---
function remoteOpen()
  timerNear:deactivate()
  anchor.carLocation(self.level)
  if callIsActive and wait ~= 0 then
    playSound("elevatorBell")
    callIsActive = false
    end
  door:open()
  if auto_close then timerHere:activate() end
  end

---
---  Close the door again
---
function localClose()
  timerHere:deactivate()
  callIsActive = false
  door:close()
  end

---
---  Position the level where the party will be teleported to the same as our starting location
---
function remoteLever(state)
  lever:setLeverState(state)
  end

--------------------------------------------------------------------------------------- 
---  Global Code Section:                                                           ---
---------------------------------------------------------------------------------------
 
---
---  Set up car tracking; find our anchor script
---
anchor = findEntity(anchor_script)
if anchor == nil then
  if car_tracking then hudPrint(self.id.." says:  anchor not found; car tracking force disabled.") end
  car_tracking = false
  anchor = findEntity(self.id)
  end
if anchor.id == self.id then carFloor = self.level end

---
---  Set up standard door, inside button, and directional lever
---
os = {{0,-1}, {1,0}, {0,1}, {-1,0}}    --[NOTE:  change offset method to getForward...]
door = spawn("dungeon_door_iron", self.level, self.x + os[self.facing + 1][1], self.y + os[self.facing + 1][2], (self.facing+2)%4, "ele_door"..self.id)
if initially_opened then door:setDoorState("open") end
button = spawn("wall_button", self.level, self.x, self.y, (self.facing+1)%4, "ele_button"..self.id)
button:addConnector("toggle", self.id, "doorClose")
lever = spawn("lever", self.level, self.x, self.y, (self.facing+3)%4, "ele_lever"..self.id)

---
---  Set up call button or call pressure plate
---
if call_by_button == nil then
  plate = spawn("pressure_plate_hidden", self.level, self.x + os[self.facing + 1][1], self.y + os[self.facing + 1][2], 0, "ele_plate"..self.id)
  plate:setTriggeredByParty(true)
  plate:addConnector("activate", self.id, "elevatorCall")
else
  local cos2 = round(math.cos(1.5707963267949 * self.facing))
  local sin2 = round(math.sin(1.5707963267949 * self.facing))
  local r1 = self.x + os[self.facing + 1][1] +(call_by_button[1] * cos2 - call_by_button[2] * sin2 * (1 - self.facing % 2 * 2))
  local r2 = self.y + os[self.facing + 1][2] + (call_by_button[1] * sin2 - call_by_button[2] * cos2)
  callButton = spawn("wall_button", self.level, r1, r2, (call_by_button[3] + self.facing + 2) % 4)
  callButton:addConnector("toggle", self.id, "elevatorCall")
  end

--Can't be spawned else it doesn't replace existing walls
--spawn("elevator_plating", self.level, self.x, self.y, (self.facing+1)%4)
--spawn("elevator_plating", self.level, self.x, self.y, (self.facing+2)%4)
--spawn("elevator_plating", self.level, self.x, self.y, (self.facing+3)%4)
--spawn("elevator_floor", self.level, self.x, self.y, self.facing)

---
---  Set up elevator timers to delay code execution between various events
---

--  Delay while closing the elevator door, then start the ride
timerClose = spawn("timer", self.level, self.x, self.y, 0, "doorWait"..self.id)
timerClose:setTimerInterval(2)
timerClose:addConnector("activate", self.id, "elevatorMove")

--  Delay while riding between floors, then start arrival sequence
timerRide = spawn("timer", self.level, self.x, self.y, 0, "timerRide"..self.id)
timerRide:setTimerInterval(4)
timerRide:addConnector("activate", self.id, "elevatorArrive")

--  Delay just enough for the teleport to complete, then trigger door opening and reset
timerOpen = spawn("timer", self.level, self.x, self.y, 0, "timerOpen"..self.id)
timerOpen:setTimerInterval(0.1)
timerOpen:addConnector("activate", self.id, "elevatorOpen")

--  Elevator is here, delay with door open for awhile, then close
timerHere = spawn("timer", self.level, self.x, self.y, 0, "timerHere"..self.id)
timerHere:setTimerInterval(11)
timerHere:addConnector("activate", self.id, "localClose")

--  Elevator is elsewhere, delay with door closed for awhile, then open
timerGone = spawn("timer", self.level, self.x, self.y, 0, "timerGone"..self.id)
timerGone:addConnector("activate", self.id, "justArrived")

--  Elevator is arriving on our floor, delay while sounds play
timerNear = spawn("timer", self.level, self.x, self.y, 0, "timerNear"..self.id)
timerNear:setTimerInterval(2)
timerNear:addConnector("activate", self.id, "remoteOpen")

---
---  Define the teleporter that will move us between floors and initialize variables
---
mover = spawn("teleporter", self.level, self.x, self.y, 0, "mover"..self.id)
mover:setChangeFacing(false)
mover:setInvisible(true)
mover:setHideLight(true)
mover:setSilent(true)
mover:setScreenFlash(false)
elevatorIsActive = false
callIsActive = false
wait = 0

---------------------------------------------------------------------------------------
---  LED display:  version 1.4.1  10-10-2012     Written by Lark                    ---
---                                              Much thanks to:  Lmaoboat          ---
---------------------------------------------------------------------------------------

---
---  Light Parameters
---
displayed = floor_display       --the initial number displayed by the script
wall = (self.facing + 2) % 4    --adjust wall used for display from script's facing; i.e. opposite = (self.facing + 2) % 4
initallyOn = true               --specify if the light is initially on

vertical_spacing = .17          --vertical LED spacing
horizontal_spacing = .15        --horizontal LED spacing; center, left, & right values are based on this number being .15
height = 3                      --height above the floor for the top row of LEDs
center = -.3                    --start of row offset for single digit displays
left = -.85                     --start of row offset for left digit
right = .25                     --start of row offset for right digit

---  Usage:
---
---  1.  drop this script into a square facing the wall where the display is desired (or adjust "wall" above)
---  2.  set parameters to suit:  displayed, wall, and initiallyOn for basic parameters
---  3.  on() to display the initial value
---  4.  off() to turn off lights
---  5.  add() to increment by one
---  6.  sub() to decrement by one
---  7.  display(number) to set the display to a new number
---  8.  do not call digit() directly

---
---  Create a LED panel for numbers 0 - 99.  LED grid is:
---

--   00 01 02 03 04
--   05 06 07 08 09
--   10 11 12 13 14
--   15 16 17 18 19
--   20 21 22 23 24
--   25 26 27 28 29
--   30 31 32 33 34

digits = {
  {1,2,3,5,9,10,14,15,19,20,24,25,29,31,32,33},
  {6,2,7,12,17,22,27,31,32,33},
  {5,1,2,3,9,14,18,22,26,30,31,32,33,34},
  {5,1,2,3,9,14,18,17,16,24,29,33,32,31,25},
  {0,5,10,15,16,17,18,19,3,8,13,23,28,33},
  {4,3,2,1,0,5,10,15,16,17,18,24,29,33,32,31,30},
  {9,3,2,1,5,10,15,20,25,31,32,33,29,24,18,17,16},
  {5,0,1,2,3,4,9,14,18,22,27,32},
  {10,5,1,2,3,9,14,18,17,16,20,25,31,32,33,29,24},
  {25,31,32,33,29,24,19,14,9,3,2,1,5,10,16,17,18}}

led = {nil}
cos = math.cos(1.5707963267949 * wall)
sin = math.sin(1.5707963267949 * wall)
tz = 1.3

---
---  divide into digits and display in correct sector
---
function display(number)
  if number == nil then return end
  local b = number % 10
  local a = (number - b) / 10
  off()

  if a > 0 then
    digit(a, left)
    digit(b, right)
  else
    digit(b, center)
    end
  displayed = number
  end

---
---  display a digit of the number at the given alignment and light set
---
function digit(number, align)
  local dix = number + 1
  for ix = 1, 35 do
    local pix = digits[dix][ix]
    if pix == nil then return end
    local tx = align + pix % 5 * horizontal_spacing
    lix = lix + 1
    led[lix] = spawn("fx", self.level, self.x, self.y, 3, self.id..lix)
    led[lix]:setLight(1, 0, 0, 1500, 0.24, 360000, false)
    led[lix]:translate(tx * cos + tz * sin, height - math.floor(pix / 5) * vertical_spacing - 1, tz * cos + tx * sin * (1 - wall%2 * 2))
    end
  end

---
---  add one to the current display w/ wrap
---
function add()
  display((displayed + 1)%100)
  end

---
---  subtract one from the current display w/ wrap
---
function sub()
  display((displayed + 99)%100)
  end

---
---  turn lights off
---
function off()
  if lix ~= null then
    for dix = 1, lix do
      led[dix]:destroy()
      end
    end
  lix = 0
  led = {nil}
  end

---
---  turn lights on
---
function on()
  display(displayed)
  end

---
---  turn lights on if specified in parameters
---
if initallyOn then on() end
Last edited by Lark on Tue Oct 16, 2012 5:15 am, edited 1 time in total.
User avatar
Kuningas
Posts: 268
Joined: Wed Apr 11, 2012 10:29 pm
Location: Northern Finland

Re: Automatic Elevator Script

Post by Kuningas »

This is rather cool!

I currently don't have any use for it, but if I will at some point, I will certainly chime in.
BASILEUS
User avatar
crisman
Posts: 305
Joined: Sat Sep 22, 2012 9:23 pm
Location: Italy

Re: Automatic Elevator Script

Post by crisman »

Oh man this elevator is cool!! Totally awesome!
Two things:
1)I also made myself a small elevator - nothing serious, just to move down for one floor - but what I'd like to see in yours, and I added to mine, are the 'infrared sensor' - well actually a hidden pressure plate - to keep the doors open if you keep coming in-and out the elevator ^^
2) the buzz sound when you try to move the elevator where you can't --- it nearly made me deaf D: --- could you lower the volume a bit?
Anyway good - great! - job!!
Oh, also a small button for open the doors while inside is appreciated! ;)
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Re: Automatic Elevator Script

Post by Lark »

Thank you both for your kind words.

Yes, I believe I can do all of those things. I'll make the electric_eye and open_button options. I too wanted a door open button, but I didn't really want to put it on the back wall with the display, but I don't know where else to put it. I wish we could change the vertical and horizontal positions of buttons. On locks, we can at least specify the height. I think the volume issue can be addressed in the definition, but here’s some half, quarter, and tenth-volume versions of the original: https://ntg.missouristate.edu/images/La ... Buzzes.zip

Thank you, -Lark
User avatar
crisman
Posts: 305
Joined: Sat Sep 22, 2012 9:23 pm
Location: Italy

Re: Automatic Elevator Script

Post by crisman »

I'm pretty sure you can adjust the vertical and horizontal position of a button, but you'll need a new model for each change.
I think for vertical it is set via script, instead for the horizontal axis you need to physically move the model inside blender or similar.

maybe the small prison button above the main one could be an idea?
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Re: Automatic Elevator Script

Post by Lark »

crisman wrote:I'm pretty sure you can adjust the vertical and horizontal position of a button, but you'll need a new model for each change.
I think for vertical it is set via script, instead for the horizontal axis you need to physically move the model inside blender or similar.
I'm afraid that this is beyond me at the moment. Version 2 of the Automatic Elevator script is ready, however, and it's available below.

ENHANCEMENTS:
  • Place only one script at the top of the elevator stack
  • Button location definitions are now in a table which also define all floors below
  • Optional electric eye resets door close timer by walking in or out of the elevator
  • Optional door open button is placed on the back wall of the elevator
  • An optional elevator light can be included
  • The default buzz sound was reduced to about 10% of the original volume (others included)
  • Script reset included for level exits before sequences finish
  • See comments in the code for parameters and usage guidelines
If anyone can help with multiple buttons on a given wall or help with custom graphics, I'd appreciate it. I also love script writing and I'm looking for another project if anyone would like assistance.

Thank you, -Lark

DOWNLOADS:
https://ntg.missouristate.edu/images/lark/elevator2.zip

VERSION 2:

Code: Select all

---------------------------------------------------------------------------------------
---  Automatic Elevator:  Version:  2.0          Written by Lark, 10-1-2012         ---
---                       Updated:  10-15-2012   Mark.Harsen@missouriState.edu      ---
--                        Special Thanks to:     Lmaoboat and BeNNyBiLL             ---
---------------------------------------------------------------------------------------
---
---  Elevator Parameters:
---
car_level = self.level          --the initial level of the elevator car
car_light = true                --true for light in the elevator car else nil
car_opener = true               --true if elevator car has a door open button
car_display = true              --nil to disable or true to display the level number on the back wall of the elevator
car_tracking = true             --true to set realistic car arrival times and sounds; false for instantly available
auto_close = true               --true if the door closes automatically after the elevator arrives or after it is triggered open
electric_eye = true             --true if crosessing the elevator threshold resets the door close timer (requires auto_close = true)
initially_opened = false        --specifies if the door on the initial floor is initially opened or not
delay_levels = 3                --delay in seconds the car must travel per floor; final floor is always 2 seconds or more to allow for arrival sound
delay_close = 11                --delay in seconds before the car door will close again
call_buttons = {                --One set per floor starting with the top floor:  false for call by pressure plate or {left-offset, depth-offset, rotation-offset}
  {1,0,0},                        --from in front of door; example placements:  to left {1,0,0}; to right {-1,0,0}, right facing {0,0,1}; 
  {0,0,3},                        --opposite door {0,0,2}; left facing {0,0,3}; behind elevator {0,-3,2};
  {1,0,0},
  false}                          --end of our four sample floors, the being false sets a hidden pressure plate to call the elevator

--------------------------------------------------------------------------------------- 
---  Usage Section:                                                                 ---
---------------------------------------------------------------------------------------
--
-- 1.  Download and define custom sounds and elevator_light
-- 2.  Create a one by one square "alcove" with only the same side open on every level to be serviced by the elevator
-- 3.  Place the modified version of this script in only the uppermost alcove in the stack facing the opening
-- 4.  Set parameters as desired; define call button placements to define levels under the script - each one extends the elevator
-- 5.  Manually set graphics for elevator walls, ceiling, and floor - the script can't do this and replace the existing graphics.  
-- 6.  See if the party can exit the level within "delay_close" seconds of the elevator door; if not, you may skip the the rest of this step.  Place a
--     hidden pressure plate on every stairway and below every pit within "delay_close" seconds of the elevator; tie each to function "reset" on "activate".
-- 7.  That should be it.  The outside button (or pressure plate) calls the elevator, the inside left button moves the car in the direction of the lever,
--     the inside back button opens the door again.

--------------------------------------------------------------------------------------- 
---  Function Section:                                                              ---
---------------------------------------------------------------------------------------

---
---  reset the elevators if party travels to a different floor via another method
---
function reset()
  if findEntity(self.id.."_timer") ~= nil then timer:destroy() end
  callIsActive = false
  elevatorIsActive = false
  local ix  
  for ix = self.level, # call_buttons + self.level - 1 do
    doors[ix]:setDoorState("closed")
    end
  end  

---
---  round to nearest integer
---
function round(num)
  local floor = math.floor(num)
  local ceiling = math.ceil(num) 
  if (num - floor) >= 0.5 then return ceiling end
  return floor
  end

---
---  Wait for the specified time, then invoke the next function
---
function sleep(secs, next)
  if findEntity(self.id.."_timer") ~= nil then timer:destroy() end
  lastTimer = next
  timer = spawn("timer", toLevel, self.x, self.y, 0, self.id.."_timer")
  timer:setTimerInterval(secs)
  timer:addConnector("activate", self.id, next)
  timer:activate()
  end

---
---  Call the elevator
---
function elevatorCall(object)
  toLevel = tonumber(string.sub(object.id, idLen))
  if callIsActive then return end
  if carLevel == toLevel or not car_tracking then
    doorOpen()
    return
    end
  callIsActive = true
  wait = math.max(0, math.abs(carLevel - toLevel) * delay_levels - 2)
  sleep(wait, "justArrived")
  end

---
---  Elevator Just arrived on our floor:  play bell and open door
---
function justArrived()
  timer:destroy()
  playSound("elevatorRun2")
  carLevel = toLevel
  sleep(2, "doorOpen")
  display(toLevel, toLevel)
  end

---
---  Open the target level's door
---
function doorOpen()
  if findEntity(self.id.."_timer") ~= nil then timer:destroy() end
  carLevel = toLevel
  if callIsActive and wait ~= 0 then
    playSound("elevatorBell")
    callIsActive = false
    end
  doors[carLevel]:open()
  if auto_close then sleep(delay_close, "doorClose") end
  end

---
---  Close the door again
---
function doorClose()
  if findEntity(self.id.."_timer") ~= nil then timer:destroy() end
  callIsActive = false
  doors[carLevel]:close()
  end

---
---  Our inside button closes the door, waits, then starts elevatorMove()
---
function elevatorRun()
  if elevatorIsActive then
    playSound("elevatorBuzz")
    return
    end
  elevatorIsActive = true
  if levers[carLevel]:getLeverState() == "activated"
    then dir = 1
    else dir = -1
    end
  if carLevel + dir < self.level or carLevel + dir > lower_limit then
    playSound("elevatorBuzz")
    elevatorIsActive = false
    return
    end
  toLevel = toLevel + dir
  if doors[carLevel]:isClosed() then
    elevatorMove()
    return
    end
  doors[carLevel]:close()
  sleep(1, "elevatorMove")
  end

---
---  Go up or down based on the lever, play elevator sound, shake car, and schedule elevatorBell()
---
function elevatorMove()
  if findEntity(self.id.."_timer") ~= nil then timer:destroy() end
  playSound("elevatorRun4")
  party:shakeCamera(.02,4.3)
  sleep(2, "elevatorArrive")
  end

---
---  Deactivate timerRide timer, duplicate lever state, signal arrival, teleport the party, and activate open timer
---
function elevatorArrive()
  if findEntity(self.id.."_timer") ~= nil then timer:destroy() end
  mover = spawn("teleporter", party.level, self.x, self.y, 0, self.id.."mover")
  mover:setChangeFacing(false)
  mover:setInvisible(true)
  mover:setHideLight(true)
  mover:setSilent(true)
  mover:setScreenFlash(false)
  mover:setTeleportTarget(self.x, self.y, 0, toLevel)
  levers[toLevel]:setLeverState(levers[carLevel]:getLeverState())
  carLevel = toLevel
  playSound("elevatorBell")
  mover:activate()
  display(carLevel, carLevel)
  sleep(0.1, "elevatorOpen")
  end

---
---  Open the door above or below our original location, deactivate the teleporter, and stop last timer
---
function elevatorOpen()
  timer:destroy()
  mover:destroy()
  elevatorIsActive = false
  doorOpen()
  end

---
---  Electric eye and inside open button causes door close timer to be restarted
---
function eyeOpener()
  if not auto_close or elevatorIsActive or callIsActive then return end
  doors[carLevel]:open()
  sleep(delay_close, "doorClose")
  end
  
--------------------------------------------------------------------------------------- 
---  Code Section:                                                                  ---
---------------------------------------------------------------------------------------

---
---  Housekeeping
--- 
carLevel = car_level
toLevel = car_level
calls = {nil}
doors = {nil}
levers = {nil}
idLen = string.len(self.id) + 7
local dx,dy = getForward(self.facing)
local doorX, doorY = self.x + dx, self.y + dy
local cos2 = round(math.cos(1.5707963267949 * self.facing))
local sin2 = round(math.sin(1.5707963267949 * self.facing))
lower_limit = self.level + # call_buttons - 1
elevatorIsActive = false
callIsActive = false
wait = 0

---
---  Set up doors, levers, run buttons, call buttons and/or call pressure plates on each floor
---
local ix
for ix = 1, # call_buttons do
  local def = call_buttons[ix]
  local level = self.level + ix - 1

  doors[level] = spawn("dungeon_door_iron", level, doorX, doorY, (self.facing+2)%4, self.id.."_door_"..ix)
  local insideButton = spawn("wall_button", level, self.x, self.y, (self.facing + 1) % 4, self.id.."_move_"..ix)
  insideButton:addConnector("toggle", self.id, "elevatorRun")
  levers[level] = spawn("lever", level, self.x, self.y, (self.facing + 3) % 4, self.id.."lever_"..ix) 

  if def == false then 
    local plate = spawn("pressure_plate_hidden", level, doorX, doorY, 0, self.id.."_call_"..level)
    plate:setTriggeredByParty(true)
    plate:addConnector("activate", self.id, "elevatorCall")
  else
    local r1 = doorX + (def[1] * cos2 - def[2] * sin2 * (1 - self.facing % 2 * 2))
    local r2 = doorY + (def[1] * sin2 - def[2] * cos2)
    local callButton = spawn("wall_button", level, r1, r2, (def[3] + self.facing + 2) % 4, self.id.."_call_"..level)
    callButton:addConnector("toggle", self.id, "elevatorCall")
    end

  if car_opener == true then
    local openButton = spawn("wall_button", level, self.x, self.y, (self.facing + 2) % 4, self.id.."_open_"..ix)
    openButton:addConnector("toggle", self.id, "eyeOpener")
    end
  
  if electric_eye and auto_close then 
    local eye = spawn("pressure_plate_hidden", level, self.x, self.y, 0, self.id.."_eye_"..level)
    eye:setTriggeredByParty(true)
    eye:addConnector("any", self.id, "eyeOpener")
    end

  if car_light then spawn("elevator_light", level, self.x, self.y, self.facing, self.id.."_carLight_"..level) end
  end

if initially_opened then doors[carLevel]:setDoorState("open") end

---------------------------------------------------------------------------------------
---  LED Display:         Version:  1.4.2        Written by Lark, 10-1-2012         ---
---                       Updated:  10-15-2012   Mark.Harsen@missouriState.edu      ---
--                        Special Thanks to:     Lmaoboat                           ---
---------------------------------------------------------------------------------------

---
---  Light Parameters
---
displayed = toLevel             --the initial number displayed by the script
wall = (self.facing + 2) % 4    --adjust wall used for display from script's facing; i.e. opposite = (self.facing + 2) % 4
initallyOn = true               --specify if the light is initially on

vertical_spacing = .17          --vertical LED spacing
horizontal_spacing = .15        --horizontal LED spacing; center, left, & right values are based on this number being .15
height = 3                      --height above the floor for the top row of LEDs
center = -.3                    --start of row offset for single digit displays
left = -.85                     --start of row offset for left digit
right = .25                     --start of row offset for right digit
LEDlevel = toLevel              --the level where the light is to be displayed - required for elevator version 2

---  Usage:
---
---  1.  drop this script into a square facing the wall where the display is desired (or adjust "wall" above)
---  2.  set parameters to suit:  displayed, wall, and initiallyOn for basic parameters
---  3.  on() to display the initial value
---  4.  off() to turn off lights
---  5.  add() to increment by one
---  6.  sub() to decrement by one
---  7.  display(number) to set the display to a new number
---  8.  do not call digit() directly

---
---  Create a LED panel for numbers 0 - 99.  LED grid is:
---

--   00 01 02 03 04
--   05 06 07 08 09
--   10 11 12 13 14
--   15 16 17 18 19
--   20 21 22 23 24
--   25 26 27 28 29
--   30 31 32 33 34

digits = {
  {1,2,3,5,9,10,14,15,19,20,24,25,29,31,32,33},
  {6,2,7,12,17,22,27,31,32,33},
  {5,1,2,3,9,14,18,22,26,30,31,32,33,34},
  {5,1,2,3,9,14,18,17,16,24,29,33,32,31,25},
  {0,5,10,15,16,17,18,19,3,8,13,23,28,33},
  {4,3,2,1,0,5,10,15,16,17,18,24,29,33,32,31,30},
  {9,3,2,1,5,10,15,20,25,31,32,33,29,24,18,17,16},
  {5,0,1,2,3,4,9,14,18,22,27,32},
  {10,5,1,2,3,9,14,18,17,16,20,25,31,32,33,29,24},
  {25,31,32,33,29,24,19,14,9,3,2,1,5,10,16,17,18}}

led = {nil}
cos = math.cos(1.5707963267949 * wall)
sin = math.sin(1.5707963267949 * wall)
tz = 1.3

---
---  divide into digits and display in correct sector
---
function display(number, onLevel)
  if onLevel ~= nil then LEDlevel = onLevel end
  if number == nil then return end
  local b = number % 10
  local a = (number - b) / 10
  off()

  if a > 0 then
    digit(a, left)
    digit(b, right)
  else
    digit(b, center)
    end
  displayed = number
  end

---
---  display a digit of the number at the given alignment and light set
---
function digit(number, align)
  local dix = number + 1
  for ix = 1, # digits[dix] do
    local pix = digits[dix][ix]
    --if pix == nil then return end
    local tx = align + pix % 5 * horizontal_spacing
    lix = lix + 1
    led[lix] = spawn("fx", LEDlevel, self.x, self.y, 3, self.id..lix)
    led[lix]:setLight(1, 0, 0, 1500, 0.24, 360000, false)
    led[lix]:translate(tx * cos + tz * sin, height - math.floor(pix / 5) * vertical_spacing - 1, tz * cos + tx * sin * (1 - wall%2 * 2))
    end
  end

---
---  add one to the current display w/ wrap
---
function add()
  display((displayed + 1)%100)
  end

---
---  subtract one from the current display w/ wrap
---
function sub()
  display((displayed + 99)%100)
  end

---
---  turn lights off
---
function off()
  if lix ~= null then
    for dix = 1, lix do
      led[dix]:destroy()
      end
    end
  lix = 0
  led = {nil}
  end

---
---  turn lights on
---
function on()
  display(displayed)
  end

---
---  turn lights on if specified in parameters
---
if initallyOn then on() end
User avatar
crisman
Posts: 305
Joined: Sat Sep 22, 2012 9:23 pm
Location: Italy

Re: Automatic Elevator Script

Post by crisman »

I realize there is a small problem - not technical, but aesthetic: if you use the elevator on a level with a prison wall-set... well, the walls are higher then the dungeons one, so there will be an empty space from the iron door to the ceiling :(
Image
(for a reason I don't understand this reminds me the movie being john malkovich... I'll make a secret level called floor 7 1/2 >_<)
The button on the led wall, ad you predicted, is not so beautiful, but I think it's fine for now.
another thing is the floor of the elevator, which changes every floor. I personally placed a closed prison pit on every floor... but then I get a shaft above... If you spawn them via script I think it won't create the shafts, so you might consider this - closed prison pits look identically to a normal prison tile.
Everything else is working fine! I'll try to add a few script to change color to your led in base of the floor, and make your elevator not functional unless turning on an electrical generator, and try to move for 3 floors instead of one - wish I could be as tidy as you, it's a miracle that I'm able to indent a script :D
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Re: Automatic Elevator Script

Post by Lark »

Fantastic! I'm glad you're using it! I want to make it better for you. :D

DOOR PROBLEM:
crisman wrote:I realize there is a small problem - not technical, but aesthetic: if you use the elevator on a level with a prison wall-set... well, the walls are higher than the dungeons one, so there will be an empty space from the iron door to the ceiling :(
Oops, I should have thought of that. I can add a table to define the door type on each level - or maybe a default value and the ability to add the door type to the end of the button placement table. Actually, I need more items to address issues below, so I can restructure this to make it cleaner. This should fix it... (I hope). Maybe I can automatically determine the level type? :geek: Or perhaps I just need a custom door type that's used on all levels and works on all levels!
crisman wrote:The button on the led wall, as you predicted, is not so beautiful, but I think it's fine for now.
DOOR OPEN BUTTON OPTIONS:
I can add another parameter to make the inside button one of the "hidden" smaller ones. I'd prefer to have more regular button choices and put them on the same wall, but I don't know how to do that yet. Modders?

FLOOR ISSUES:
crisman wrote:...another thing is the floor of the elevator, which changes every floor. I personally placed a closed prison pit on every floor... but then I get a shaft above... If you spawn them via script I think it won't create the shafts, so you might consider this - closed prison pits look identically to a normal prison tile.
I'll have to check on this. If you spawn graphics via the script, they won't replace the default graphics so they either don't display right (see viewtopic.php?f=14&t=3476) or they clip a lot. To combat this,
I included an "elevator_floor" graphic definition to place manually which is really a prison floor tile if I remember correctly. I think we'll still have to define the walls and floor manually on each floor, but I still want better graphics. Hopefully later.

LED COLOR, DISABLE/ENABLE, and SKIP FLOORS:
crisman wrote:Everything else is working fine! I'll try to add a few script to change color to your led in base of the floor, and make your elevator not functional unless turning on an electrical generator, and try to move for 3 floors instead of one - wish I could be as tidy as you, it's a miracle that I'm able to indent a script :D
Don't sell yourself short! I'll bet you can do it. However, I'm happy to include these features in the next update. Like I said, I like writing scripts especially to create things others will use. Let me take a stab at improving the level definition options for each floor. Here's the complete list of mods, I think:

ENHANCEMENT REQUESTS:
  • Fix the door issues [Research]
    • Create a custom door type that works on all levels, or...
    • Fix the door type to automatically match the level, if possible, provided it doesn't create more problems, or...
    • Allow for manually setting the door type on each level. I'll just have to play with these.
  • Check elevator floor type - see if there are any useable options for setting it automatically - I don't think so, but I'll try. [Research]
  • Allow the LED color to be manually set differently on every floor [easy]
  • Allow floors to be skipped by the elevator (would this handle your 3 floor requirement?) Skipped floors won't be serviced by the elevator in any way, but would be counted in car arrival times. Example, an elevator servicing only two floors would have a call button definition of something like {{1,0,0}, “no”, “no”, {1, 0, 0}}. This would skip two floors traveling three floors every time. I'd also have to lengthen the ride time too I suppose which might need a new sound or at least one that will adjoin nicely. [moderate]
  • Define a function that can be called externally to enable or disable the operation of the elevator. This way you could disable the elevator by default, and then you could trigger the enabling only when your specific criteria were met. I assume if disabled (i.e. not powered), pressing any of the call buttons wouldn't have any effect. [easy]
  • Change “playSound” to “playSoundAt” to get real 3D sounds that diminish with distance. I just figured that one out. :) [easy]
Let me know if this would meet your needs, and I'll get started...

Thanks, -Lark
User avatar
Neikun
Posts: 2457
Joined: Thu Sep 13, 2012 1:06 pm
Location: New Brunswick, Canada
Contact:

Re: Automatic Elevator Script

Post by Neikun »

One solution to the door size issue, is to in the elevator room of every level, construct it out of the prison wallset by defining prison wall decorations with replaceWall = true, lines.
"I'm okay with being referred to as a goddess."
Community Model Request Thread
See what I'm working on right now: Neikun's Workshop
Lead Coordinator for Legends of the Northern Realms Project
  • Message me to join in!
User avatar
crisman
Posts: 305
Joined: Sat Sep 22, 2012 9:23 pm
Location: Italy

Re: Automatic Elevator Script

Post by crisman »

Those ideas are great!
For now I personally managed to get my 7 1/2 floor, in a stack of floor from 1 to 10, my 7 1/2 floor is the 10th, until you use a key on a special lock, the elevator will ignore the 10th. Then, the elevator will travel from 7th floor to 10th, to 10th floor to 8th. Originally when I said "skip 3 levels" I meant this, but now I realize it could be interesting if in a stack there are some floor not served by the elevator!
Still have to work on the button and the leds, in these days I don't have so much time :(
For the space I'll try to place an opened prison portcullis on prison-level I'm not sure if their height is enough to cover the hole But I'll give it a try later!
I'll wait for your next update!

EDIT: prison portcullis did the trick!
Image
inside is barely visible
Image
Because of the shaft I guess...
Post Reply