Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Lockpicking Minigame

Eilei

Sponsor

The old post with this script vanished into the ether sometime in the last two years, so here it is again.

Lockpicking Minigame Version: 1.0.0, for RMXP
By: Eilei

Introduction

The lockpicking minigame is a little script I slapped together a long time ago to teach myself Ruby. The game has a number of tumblers you need to get into place, and on the harder locks moving one tumbler can move another too. Successful completion of the minigame sets a switch of your choice to ON, so it's pretty easy to interact with from events.

Features

  • Varying difficulties for different levels of play
  • Completely random boards, so no two games are ever the same*
  • Can interact with either global switches or event self-switches

Screenshots

Minigame on "Average" difficulty:


Demo

Link Dead

Script

Code:
#==============================================================================

# ** Lockpick Minigame

#------------------------------------------------------------------------------

# Your Name   : Eilei

# Version     : 1.0.0

# Date        : September 9, 2007

# SDK Version : Version 2.3, Part I

#==============================================================================

 

=begin                    Installation Instructions

 

0.) Get SDK if you don't have it; Part I is required for this script.

 

1.) Copy this script and paste it into your script list, just above Main.

 

                          Editing Instructions

 

1.) Change the main background by swapping out

      Graphics/Pictures/lockpick_background.png

    for whatever you want.  Change the audio file at 

      Audio/BGM/lockpick_music.mp3

    Change the color of the tumblers by messing with

      Graphics/Pictures/lockpick_pins.png,

    but I don't recommend changing the sizes of the pins.  Finally, change the

    minigame lock background colors by editing both

      Graphics/Pictures/lockpick_cylinder.png

    and

      Graphics/Pictures/lockpick_pin_background.png

 

2.) This script isn't otherwise very customizeable at the moment.  Feel free

    to tinker with the number of tumblers per difficulty and such.

    

                          Usage

                          

1.) See the two chests for the two ways to use this script; one method puts

    the lockpicking result in a specified event's self switches, the other

    puts it in a specified global switch.

 

                          Legal Notices

                          

    This script is Copyright © 2007 C. Schrupp.

 

    This script is free software; you can redistribute it and/or modify

    it under the terms of the GNU General Public License as published by

    the Free Software Foundation; either version 3 of the License, or

    (at your option) any later version.

 

    This script is distributed in the hope that it will be useful,

    but WITHOUT ANY WARRANTY; without even the implied warranty of

    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

    GNU General Public License for more details.

 

    A copy of the GNU General Public License is in gpl.txt in the main

    directory.  If not, see <http://www.gnu.org/licenses/>.

=end

 

#--------------------------------------------------------------------------

# * Begin SDK Log

#--------------------------------------------------------------------------

SDK.log( 'Lockpicking Minigame', 'Eilei', '1.0', '2007-09-08')

 

#--------------------------------------------------------------------------

# * Begin Requirements Check

#--------------------------------------------------------------------------

SDK.check_requirements( 2.3, [1] )

 

#--------------------------------------------------------------------------

# * Begin SDK Enable Test

#--------------------------------------------------------------------------

if SDK.enabled?( 'Lockpicking Minigame' )

 

#----------------------------------------------------------------------------

# Begin Game_Temp Edit

#----------------------------------------------------------------------------

class Game_Temp

  attr_accessor :lock_difficulty   # difficulty for the next lock to be picked

end

#----------------------------------------------------------------------------

# End Game_Temp Edit

#----------------------------------------------------------------------------

 

#==============================================================================

# ** Scene_LockpickMinigame

#------------------------------------------------------------------------------

#  Scene_LockpickMinigame is the scene to call when you want to run the

#  lockpicking minigame

#

#  NOTE: Scene_LockpickMinigame.set_result MUST be called before $scene is

#  set, or the minigame will not work.

#==============================================================================

class Scene_LockpickMinigame < SDK::Scene_Base

 

  #---------------------------------------------------------------------------+

  # Scene Constants                                                           |

  #---------------------------------------------------------------------------+

  LOCK_VERY_EASY  = 0

  LOCK_EASY       = 1

  LOCK_AVERAGE    = 2

  LOCK_DIFFICULT  = 3

  LOCK_INSANE     = 4

 

  # The number of tumblers for each difficulty should be prime to maximize the

  # probability that the puzzle will be solveable.

  LOCK_TUMBLERS = {

    LOCK_VERY_EASY => 2,

    LOCK_EASY      => 3,

    LOCK_AVERAGE   => 5,

    LOCK_DIFFICULT => 7,

    LOCK_INSANE    => 11

  }

  

  LOCK_AUDIO_UNLOCK = RPG::AudioFile.new( 'lock_open' )

  

  # Internal constants

  CYLINDER_WIDTH  = 288

  

  #--------------------------------------------------------------------------

  # * Main Processing : Variable Initialization

  #--------------------------------------------------------------------------

  def main_variable

 

    # Check that the default value is ok

    if not LOCK_TUMBLERS.keys.include?( $game_temp.lock_difficulty )

      $game_temp.lock_difficulty = LOCK_VERY_EASY

    end

 

    # Initialize the tumbler pins

    @pins = Array.new( LOCK_TUMBLERS[ $game_temp.lock_difficulty ] )

    

    # Initialize the current lock modifier

    # (guarantees @lock_number will be prime)

    n = rand( 40 ) + 1

    @lock_number = n**2 + n + 41

 

  end

 

  #--------------------------------------------------------------------------

  # * Main Processing : Sprite Initialization

  #--------------------------------------------------------------------------

  def main_sprite

    # Initialize viewports

    @viewport_background = Viewport.new( 0, 0, 640, 480)

    @viewport_background.z = -500

    @viewport_lock = Viewport.new( ( 640 - CYLINDER_WIDTH ) / 2, 90,

      CYLINDER_WIDTH, 300 )

    @viewport_lock.z = -200

    

    # display background image

    @background_sprite = Sprite.new( @viewport_background )

    @background_sprite.bitmap = RPG::Cache.picture( 'lockpick_background' )

    

    # display lock cylinder

    @cylinder_sprite = Sprite.new( @viewport_lock )

    @cylinder_sprite.bitmap = RPG::Cache.picture( 'lockpick_cylinder' )

    

    # display tumbler springs and pins

    for i in [email=0...@pins.size]0...@pins.size[/email]

      @pins[ i ] = Tumbler.new( @viewport_lock )

      @pins[ i ].x = CYLINDER_WIDTH * ( i + 1 ) / ( @pins.size + 1 )

      @pins[ i ].y = 0

    end

    

    # display lockpick

    @lockpick_sprite = Lockpick.new

    @lockpick_sprite.tumblers = @pins.size

  end

  

  #--------------------------------------------------------------------------

  # * Main Processing : Window Initialization

  #--------------------------------------------------------------------------

  def main_window

    @help_window = Window_Help.new

    @help_window.set_text(

      'Line up the tumbler pins with the cylinder to pick the lock.' )

    @help_window.back_opacity = 160

  end

  

  #--------------------------------------------------------------------------

  # * Main Processing : Audio Initialization

  #--------------------------------------------------------------------------

  def main_audio

    $game_system.bgm_play( RPG::AudioFile.new( 'lockpick_music', 80 ))

  end

 

  #--------------------------------------------------------------------------

  # * Frame Update

  #--------------------------------------------------------------------------

  def update

    # game cancelled

    if Input.trigger?( Input::B )

      $scene = Scene_Map.new

    end

    

    # Don't allow other input if animating

    if @lockpick_sprite.move?

      return

    end

    for i in [email=0...@pins.size]0...@pins.size[/email]

      if @pins[ i ].move?

        return

      end

    end

    

    # move the lockpick left or right

    if Input.trigger?( Input::RIGHT )

      @lockpick_sprite.move_right

      if not @lockpick_sprite.move?

        $game_system.se_play( $data_system.buzzer_se )

      end

    end

    if Input.trigger?( Input::LEFT )

      @lockpick_sprite.move_left

      if not @lockpick_sprite.move?

        $game_system.se_play( $data_system.buzzer_se )

      end

    end

    

    # knock a tumbler up - cascades

    if Input.trigger?( Input::UP )

      @pins[ @lockpick_sprite.position ].raise

      if @pins[ @lockpick_sprite.position ].move?

        $game_system.se_play( Tumbler::TUMBLER_AUDIO_UP )

        @lockpick_sprite.tap_up

        if @lockpick_sprite.position > 0 # first pin guaranteed no cascades

          @pins[ ( @lock_number * @lockpick_sprite.position ) %

            @pins.size ].raise

        end

        if $game_temp.lock_difficulty == LOCK_INSANE

          @pins[ ( @lock_number + @lockpick_sprite.position ) %

            @pins.size ].lower

        end

      else

        $game_system.se_play($data_system.buzzer_se)

      end

    end

    

    # knock a tumbler down - cascades

    if Input.trigger?( Input::DOWN )

      @pins[ @lockpick_sprite.position ].lower

      if @pins[ @lockpick_sprite.position ].move?

        $game_system.se_play( Tumbler::TUMBLER_AUDIO_DOWN )

        @lockpick_sprite.tap_down

        if @lockpick_sprite.position > 0 # first pin guaranteed no cascades

          @pins[ ( @lock_number * @lockpick_sprite.position ) %

            @pins.size ].lower

        end

        if $game_temp.lock_difficulty == LOCK_INSANE

          @pins[ ( @lock_number + @lockpick_sprite.position ) %

            @pins.size ].raise

        end

      else

        $game_system.se_play( $data_system.buzzer_se )

      end

    end

 

    # game finished, maybe

    if Input.trigger?( Input::C )

      unlockable = true

      for i in [email=0...@pins.size]0...@pins.size[/email]

        if not @pins[ i ].open?

          unlockable = false

          break

        end

      end

      if unlockable

        

        $game_system.se_play( LOCK_AUDIO_UNLOCK )

        $scene = Scene_Map.new

        

        # change the specified result switch

        if @result_switch == 'X'

          # not a self-switch

          $game_switches[ @result_id ] = true

        else

          $game_self_switches[ [ $game_map.map_id, @result_id,

            @result_switch ] ] = true

        end

        $game_map.need_refresh = true

      else

        $game_system.se_play( $data_system.buzzer_se )

      end

    end

 

  end

 

  #--------------------------------------------------------------------------

  # * Main Processing : Ending

  #--------------------------------------------------------------------------

  def main_end

    Audio.bgm_fade( 2000 )

  end

 

  #--------------------------------------------------------------------------

  # * set_result specifies which game switch is to be changed to true on a

  # successful lockpick.

  # id = event id for a self-switch, or switch id for a global switch

  # self_switch = 'A', etc. for a self-switch.

  # LEAVE self_switch BLANK TO SPECIFY A GLOBAL SWITCH

  #--------------------------------------------------------------------------

  def set_result( id, self_switch = 'X' )

    @result_id = id

    @result_switch = self_switch

  end

  

end # class Scene_LockpickMinigame

 

#==============================================================================

# ** Tumbler

#------------------------------------------------------------------------------

#  Tumbler represents one of the lock's tumblers

#==============================================================================

class Tumbler < Sprite

  #---------------------------------------------------------------------------+

  # Tumbler Constants                                                         |

  #---------------------------------------------------------------------------+

  # Maximum vertical size (in potential openings) of a tumbler

  TUMBLER_POSITIONS = 6

  

  # Bitmap sources for the pictures

  TUMBLER_PIN_BITMAP    = RPG::Cache.picture( 'lockpick_pins' )

  TUMBLER_SPRING_BITMAP = [

    RPG::Cache.picture( 'lockpick_spring1' ),

    RPG::Cache.picture( 'lockpick_spring2' ),

    RPG::Cache.picture( 'lockpick_spring3' ),

    RPG::Cache.picture( 'lockpick_spring4' ),

    RPG::Cache.picture( 'lockpick_spring5' ),

    RPG::Cache.picture( 'lockpick_spring6' )

  ]

  TUMBLER_BACKGROUND_BITMAP = RPG::Cache.picture( 'lockpick_pin_background' )

  

  # Source rectangles inside the tumbler pin bitmap

  TUMBLER_DRIVER_TOP    = Rect.new(  0,  0, 20,  2 )

  TUMBLER_DRIVER_MIDDLE = Rect.new(  0,  2, 20, 27 )

  TUMBLER_DRIVER_BOTTOM = Rect.new(  0, 29, 20,  3 )

  TUMBLER_KEY_TOP       = Rect.new( 20,  0, 20,  3 )

  TUMBLER_KEY_MIDDLE    = Rect.new( 20,  3, 20, 17 )

  TUMBLER_KEY_BOTTOM    = Rect.new( 20, 20, 20, 12 )

  

  # Audio for ratcheting

  TUMBLER_AUDIO_UP   = RPG::AudioFile.new( 'tumbler', 100, 105 )

  TUMBLER_AUDIO_DOWN = RPG::AudioFile.new( 'tumbler', 100,  95 )

 

  # Rate at which the tumbler pins move up and down

  TUMBLER_MOVE_SPEED = 2  # this should be a factor of 20 (ie, 1, 2, 4, 5 )

 

  #---------------------------------------------------------------------------+

  # Tumbler Methods                                                           |

  #---------------------------------------------------------------------------+

  def initialize( *arg )

    super *arg

    @opening = rand( TUMBLER_POSITIONS )

    while @level.nil? or @level + @opening == TUMBLER_POSITIONS - 1

      @level = rand( TUMBLER_POSITIONS )

    end

    self.bitmap = Bitmap.new( 22, 262 )

    self.bitmap.blt( 0, 0, TUMBLER_BACKGROUND_BITMAP,

      TUMBLER_BACKGROUND_BITMAP.rect )

    @current = ( @level + 1 ) * 20 - TUMBLER_MOVE_SPEED

  end

  

  #--------------------------------------------------------------------------

  # * raise moves the tumbler up a number of clicks

  # distance = maximum distance raised

  #--------------------------------------------------------------------------

  def raise( distance = 1 )

    @level -= distance

    if @level >= TUMBLER_POSITIONS

      @level = TUMBLER_POSITIONS - 1

    elsif @level < 0

      @level = 0

    end

  end

 

  #--------------------------------------------------------------------------

  # * lower moves the tumbler down a number of clicks

  # distance = maximum distance lowered

  #--------------------------------------------------------------------------

  def lower( distance = 1 )

    @level += distance

    if @level >= TUMBLER_POSITIONS

      @level = TUMBLER_POSITIONS - 1

    elsif @level < 0

      @level = 0

    end

  end

 

  #--------------------------------------------------------------------------

  # * open? determines if the current level is the same as the opening

  # between the driver and key pins

  #--------------------------------------------------------------------------

  def open?

    return @level + @opening == TUMBLER_POSITIONS - 1

  end

 

  #--------------------------------------------------------------------------

  # * move? determines if the pin is currently moving

  #--------------------------------------------------------------------------

  def move?

    return @current != ( @level + 1 ) * 20

  end

  

  #--------------------------------------------------------------------------

  # * update updates the current display of the tumbler, ratcheting it up

  # or down depending on its current position vs. desired

  #--------------------------------------------------------------------------

  def update

    

    super

    

    # See if we need to update position

    if not move?

      return

    elsif @current - ( @level + 1 ) * 20 > 0

      @current -= TUMBLER_MOVE_SPEED # move up

    else

      @current += TUMBLER_MOVE_SPEED # move down

    end

    

    # Fill in the background

    self.bitmap.fill_rect( Rect.new( 1, 1, 20, 260 ),

      Color.new( 255, 255, 255 ) )

    

    # Draw the spring

    spring_level = ( ( @current + 10 ) / 20 ) - 1

    self.bitmap.stretch_blt( Rect.new( 1, 1, 20, @current + 1 ),

      TUMBLER_SPRING_BITMAP[ spring_level ],

      TUMBLER_SPRING_BITMAP[ spring_level ].rect )

      

    # Draw the driver pin

    self.bitmap.blt( 1, @current + 1, TUMBLER_PIN_BITMAP,

      TUMBLER_DRIVER_TOP )

    self.bitmap.stretch_blt( Rect.new( 1, @current + 3, 20,

      ( @opening + 1 ) * 20 - 5 ), TUMBLER_PIN_BITMAP,

      TUMBLER_DRIVER_MIDDLE )

    self.bitmap.blt( 1, ( @opening + 1 ) * 20 + @current - 2,

      TUMBLER_PIN_BITMAP, TUMBLER_DRIVER_BOTTOM )

    

    # Draw the key pin

    self.bitmap.blt( 1, ( @opening + 1 ) * 20 + @current + 1,

      TUMBLER_PIN_BITMAP, TUMBLER_KEY_TOP )

    self.bitmap.stretch_blt( Rect.new( 1, ( @opening + 1 ) * 20 + @current + 4,

      20, ( TUMBLER_POSITIONS - @opening ) * 20 - 15 ), TUMBLER_PIN_BITMAP,

      TUMBLER_KEY_MIDDLE )

    self.bitmap.blt( 1, ( TUMBLER_POSITIONS + 1 ) * 20 + @current - 11,

      TUMBLER_PIN_BITMAP, TUMBLER_KEY_BOTTOM )

    

  end

 

  

  def set_result_switch( event_id, switch_id )

  end

  

end # class Tumbler

 

#==============================================================================

# ** Lockpick

#------------------------------------------------------------------------------

#  Lockpick represents the game's lockpick

#==============================================================================

class Lockpick < Sprite

  

  attr_accessor :tumblers

  attr_accessor :position

  

  #---------------------------------------------------------------------------+

  # Lockpick Constants                                                         |

  #---------------------------------------------------------------------------+

  LOCKPICK_X_OFFSET = ( 640 - Scene_LockpickMinigame::CYLINDER_WIDTH ) / 2 - 482

  LOCKPICK_MOVEMENT_SPEED = 8  # This can be any integer; higher is faster

  

  #--------------------------------------------------------------------------

  # * initialize sets the starting lockpick x coordinate

  #--------------------------------------------------------------------------

  def initialize( *arg )

    super *arg

    

    self.bitmap = RPG::Cache.picture( 'lockpick_lockpick' )

    self.x = LOCKPICK_X_OFFSET

    self.y = 355

    

    @position = 0

    @current = 0

    @tumblers = 1

    @tap_count = 0

  end

  

  #--------------------------------------------------------------------------

  # * move_right moves the lockpick right one tumbler

  #--------------------------------------------------------------------------

  def move_right

    if @tap_count != 0

      return

    end

    if @position >= @tumblers - 1

      @position = @tumblers - 1

    else

      @position += 1

    end

  end

  

  #--------------------------------------------------------------------------

  # * move_left moves the lockpick left one tumbler

  #--------------------------------------------------------------------------

  def move_left

    if @tap_count != 0

      return

    end

    if @position <= 0

      @position = 0

    else

      @position -= 1

    end

  end

 

  #--------------------------------------------------------------------------

  # * tap_up moves the lockpick in an upwards tapping motion

  #--------------------------------------------------------------------------

  def tap_up

    @tap_count = 8

  end

 

  #--------------------------------------------------------------------------

  # * tap_down moves the lockpick in a downwards tapping motion

  #--------------------------------------------------------------------------

  def tap_down

    @tap_count = -6

  end

  

  #--------------------------------------------------------------------------

  # * move? determines if the lockpick needs to move

  #--------------------------------------------------------------------------

  def move?

    current_x = Scene_LockpickMinigame::CYLINDER_WIDTH * ( @position + 1 ) /

      ( @tumblers + 1 )

    return ( @current != current_x or @tap_count != 0 )

  end

 

  #--------------------------------------------------------------------------

  # * initialize sets the starting lockpick x coordinate

  #--------------------------------------------------------------------------

  def update

    super

    

    # See if we're animated up or down

    if @tap_count != 0 # down

      if @tap_count < 0

        self.x = @current + LOCKPICK_X_OFFSET

        self.y = 354 - @tap_count

        @tap_count += 1

      else # up

        self.x = @current + LOCKPICK_X_OFFSET - @tap_count

        self.y = 356 - @tap_count

        @tap_count -= 1

      end

      return

    end

 

    # See if we need to update position

    if not move?

      return

    elsif Scene_LockpickMinigame::CYLINDER_WIDTH * ( @position + 1 ) /

        ( @tumblers + 1 ) > @current

      @current += LOCKPICK_MOVEMENT_SPEED

    else

      @current -= LOCKPICK_MOVEMENT_SPEED

    end

    

    # if we're close, just set it to the right value

    if ( @current - Scene_LockpickMinigame::CYLINDER_WIDTH * ( position + 1 ) /

        ( @tumblers + 1 ) ).abs <=

        LOCKPICK_MOVEMENT_SPEED / 2

      @current = Scene_LockpickMinigame::CYLINDER_WIDTH * ( @position + 1 ) /

        ( @tumblers + 1 )

    end

 

    self.x = @current + LOCKPICK_X_OFFSET

    self.y = 355

  end

  

end # class Lockpick

  

end # if SDK.enabled?

Instructions (using the Demo as a base)

Editing
  1. Change the main background by swapping out Graphics/Pictures/lockpick_background.png for whatever you want.
  2. Change the audio file at Audio/BGM/lockpick_music.mp3
  3. Change the color of the tumblers by messing withGraphics/Pictures/lockpick_pins.png, but I don't recommend changing the sizes of the pins.
  4. Finally, change the minigame lock background colors by editing both Graphics/Pictures/lockpick_cylinder.png and Graphics/Pictures/lockpick_pin_background.png
  5. This script isn't otherwise very customizeable at the moment. Feel free to tinker with the number of tumblers per difficulty and such.

Usage

See the two chests for the two ways to use this script; one method puts the lockpicking result in a specified event's self switches, the other puts it in a specified global switch.

FAQ

None yet...

Compatibility

SDK 2.3 compatible

Author's Notes

I don't know what I have to do to make this SDK 2.4 compatible, but I will probably do so sometime in the near future.

Feel free to make any comments or suggestions.

Terms and Conditions

This script is released under GPL 3. Please list my name in the credits if you use this!

* There is a non-zero probability that two games may actually be the same.
 
I am surprised this hasn't got any feedback considering I've seen probably 100 topics requesting just this. I can say I cannot actually offer any advice on your coding at all. It's perfect! Formatting, style, structure are perfect. Ok, maybe the only thing I would do different is making a lock picking module, placing all constants within that module, and making your class classes of your module, but that is all a personal preference for organization and in no way improves your code.

Fantastic code all around and really fun to play. Great script. :thumb:

PS: This will be compatible with any SDK version as long as it includes the SDK::Scene_Base class, as it appears it is the only aspect of the SDK this script relies on.
 

Eilei

Sponsor

Thank you for your kind words. The script was pretty well-received the first time around; I think that there just isn't much call for RMXP scripts these days.
 
While I agree with Seph that it's definately one of the better scripts on .org, I wouldn't really say it's perfect... most is personal preference as well, but since that doesn't count, here's something I think would be generally seen as redundancy:
Code:
LOCK_VERY_EASY  = 0

LOCK_EASY       = 1

LOCK_AVERAGE    = 2

LOCK_DIFFICULT  = 3

LOCK_INSANE     = 4
Overall, you have a lot of constants in there that I don't think need to be necessarily defined... shouldn't be much of a performance issue though.
But yeah, that is some nice coding you have there - especially if you "slapped it together" ;)

As for the XP script needs... it seems to me that RMXP is still the wider-spread maker out there, so that might not be the issue. Personally though, if I'd be looking for a lockpicking script and couldn't script it myself, I wouldn't take this one... just too non-realistic (who sees the lock in a cut-open perspective when picking? ^^).

Keep up the good work!
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top