Supplementary Scripts

I’ve ran into a lot of cases where RPG Maker was missing some functionality which I found to be almost mandatory. Because of that, I kept adding functionality I’d want to have to make my life easier, and am now sharing some of it with you.

Setup

Read Script Layer, as this requires a very simple script change.

Save this image, name it debug_collision.png and move it into Graphics/Tilesets: supplimentary_scripts_1.png

Create a new script, and have it load second to last, before Main. Should look like this if you’re doing it through the editor: supplimentary_scripts_2.png

Then copy the contents of this code block into it:

# This script monkeypatches some things in the vanilla scripts to enable some quality of life functionality.
# Read the documentation where you got the script from for additional information.

class Game_Event
    # Check if the event is not earased, and has any commands to execute in the current list.
    def execution_valid?()
        return !(@erased || @list.nil? || (@list.count { |c| c.code != 108 && c.code != 408 && c.code != 0 } == 0))
    end
end

class Game_Map
    # Returns an event by ID
    def event_by_id(event_id)
        return @events[event_id]
    end

    # Find the first event with the given name, returning a pair of its ID and its instance
    def event_by_name(event_name)
        return *@events.find { |id, e| e.name == event_name }
    end
end

class Interpreter
    # Trigger an event on the map by name
    # Does nothing if there's no event with that name
    # Having multiple events with the same name will trigger the one with the lowest ID
    # Unlike `start`, it follows common event behavior, where the event starts executing when it's called, and not when the current event is done
    # And unlike common events, the commands are executed within the context of the called event, instead of the context of the calling event
    def call_event(event_name)
        ev_id, ev = $game_map.event_by_name(event_name)
        return _call_event(ev)
    end


    # Same as `call_event`, but by ID instead
    def call_event_by_id(event_id)
        ev = $game_map.event_by_id(event_id)
        return _call_event(ev)
    end


    # Returns whether calling the given on map event is valid
    # Checks if the event with the name exists, and that it has commands to execute
    # While `call_event` already does that, this method allows for setup before actually calling the event
    def can_call_event(event_name)
        _, ev = $game_map.event_by_name(event_name)
        return ev && ev.execution_valid?
    end

    # Same as `can_call_event`, but by ID instead
    def can_call_event_by_id(event_id)
        ev = $game_map.event_by_id(event_id)
        return ev && ev.execution_valid?
    end

    # Get another event's self switch
    def get_others_self_switch(event_name, switch)
        id, _ = $game_map.event_by_name(event_name)
        $game_self_switches[[$game_map.map_id, id, switch]]
    end

    # Set another event's self switch
    def set_others_self_switch(event_name, switch, state)
        id, = $game_map.event_by_name(event_name)
        $game_self_switches[[$game_map.map_id, id, switch]] = !!state
        $game_map.need_refresh = true
    end

    private
    def _call_event(ev)
        # Return false if the event wasn't found, was erased, or has no commands in the list
        return false if !ev || !ev.execution_valid?

        # Copied off command_117
        @child_interpreter = Interpreter.new(@depth + 1)
        @child_interpreter.setup(ev.list, ev.id, ev.name)

        # Found and called event, return true so the caller knows it actually executed
        # Very useful in script condition commands
        return true
    end
end

module RPG
    module Cache
        class << self
            alias :_og_load_bitmap :load_bitmap

            EMPTY_BITMAP = Bitmap.new(1, 1)

            def load_bitmap(path, filename, hue = 0)
                # Return an empty bitmap for files starting with a `!` to allow placeholders that shouldn't appear in game
                return EMPTY_BITMAP if filename.start_with?("!")

                # Wrap and keep hue positive to prevent memory leaks and redundant memory usage
                hue %= 360
                hue += 360 if hue < 0
                return _og_load_bitmap(path, filename, hue)
            end
        end
    end
end

# Simplify entering proper debug mdoe
$debug = File.exists?("debug_tester.dat") unless $debug

# Debug specific stuff past this point
return unless $debug

class Collision_View
    TILE_OFFSET = 384
    GAME_WIDTH = 640
    GAME_HEIGHT = 480

    attr_reader :enabled

    def initialize()
        @enabled = false
    end
    
    def update()
        if Input.trigger?(Input::R)
            @enabled ? hide() : show()
        end

        if @enabled
            @map.ox = $game_map.display_x / 4
            @map.oy = $game_map.display_y / 4
        end
    end

    def show()
        dispose()
        @enabled = true
        
        @vieport = Viewport.new(0, 0, GAME_WIDTH, GAME_HEIGHT)
        @vieport.z = 999

        @map = Tilemap.new(@vieport, GAME_WIDTH / 32 + 2, GAME_HEIGHT / 32 + 2)
        @map.tileset = RPG::Cache.tileset('debug_collision')
        @map.map_data = buildMapData()
        Graphics.frame_reset()
    end
    
    def hide()
        dispose()
        @enabled = false
    end

    def dispose()
        @vieport.dispose() if @vieport
        @vieport = nil
        @map = nil
    end
    
    def isEventPassable(ev)
        return true if ev.through
        return false if ev.tile_id == 0 && ev.character_name.empty?
        return false if $game_map.passages[ev.tile_id] & 0x0f == 0x0f
        return false unless ev.character_name.empty?

        return true
    end
    
    DIRS = [
        2, # DOWN
        4, # Left
        6, # Right
        8  # UP
    ]

    def buildMapData()
        # Temporarily move the player out of the way as to not interfere with collision checks
        px, py = $game_player.x, $game_player.y
        $game_player.x = -1
        $game_player.y = -1

        map_data = Table.new($game_map.width, $game_map.height)

        $game_map.width.times do |x|
            $game_map.height.times do |y|
                flag = 0
                DIRS.each do |dir|
                    flag += 1 << (dir / 2 - 1) unless $game_player.passable?(x, y, dir)
                end
                
                map_data[x, y] = TILE_OFFSET + flag if flag > 0
            end
        end

        # Put the player back
        $game_player.x = px
        $game_player.y = py

        return map_data
    end
end

$collision_view = Collision_View.new()

class Scene_Map
    alias :_og_transfer_player :transfer_player
    def transfer_player()
        $collision_view.hide()
        _og_transfer_player()
    end
end

class Spriteset_Map
    alias :_og_update :update
    def update()
        _og_update()
        $collision_view.update()
    end
end

class Game_Player
    alias :_og_passable? :passable?

    def passable?(x, y, d)
        return true if Input.press?(Input::CANCEL)
        return _og_passable?(x, y, d)
    end
end

module Graphics
    @@game_modification_time = File.stat("Data/Actors.rxdata").mtime

    class << self
        alias :_og_update :update

        def update()
            if File.stat("Data/Actors.rxdata").mtime != @@game_modification_time && $scene.class == Scene_Map
                sleep(0.1) # to give rmxp a chance to save the file properly
                
                $data_actors = load_data("Data/Actors.rxdata")
                $data_items = load_data("Data/Items.rxdata")
                $data_armors = load_data("Data/Armors.rxdata")
                $data_animations = load_data("Data/Animations.rxdata")
                $data_tilesets = load_data("Data/Tilesets.rxdata")
                $data_common_events = load_data("Data/CommonEvents.rxdata")
                
                RPG::Cache.clear()
                $game_map.setup($game_map.map_id)
                $scene = Scene_Map.new()
                $game_player.center($game_player.x, $game_player.y)
                @@game_modification_time = File.stat("Data/Actors.rxdata").mtime
            end

            _og_update()
        end
    end
end