Mobius's Quest Journal

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Mobius's Quest Journal 2.3

by

MobiusXVI
Release Notes
Oct 2013 - v. 1.0 Initial Release
Oct 2013 - v. 1.1 Added Quest Maker functionality
Mar 2014 - v. 1.15 Improved Quest Maker to be more user friendly
Jun 2014 - v. 1.2 Minor script compatibility improvement
Jul 2014 - v. 2.0 Added optional switch/variable usage, improved debugging
Apr 2019 - v. 2.1 Added additional debugging for file loading
Oct 2019 - v. 2.2 Minor script compatibility improvement (shouldn't break if switches/variables are used in weird ways)
May 2020 - v. 2.3 Refactored external APIs, improved error messages


Introduction
I wanted to create a plain looking, but robust quest/journal system that was akin to the one found in Skyrim. I also wanted to make it as user friendly as possible while still giving you control over implementation. I hope you enjoy it!

Features
- Quests can have as many steps or phases as you'd like
- Each phase can have up to 20 lines of information displayed in the journal
- Can store virtually unlimited number of quests
- Sorts quests by current/completed; current quests show up in normal text while completed quests are grayed out
- Keeps completed quests around for player review
- Quest data created from .txt file; no need to put it all in the script window.
- Quest data can also be made using a custom windows program - Mobius's Quest Journal Maker!
- Quest data can be converted to .rxdata for security
- Journal is called using simple script. Access it from an event, item, or even a key press! (Custom menus can call it up as well!)
- Quests can be modified using standard switches/variables
- Can automatically update switch/variable names both in-game and in the editor
- Easily call the Quest Journal from the menu using my Menu Command Manager

Screenshots
The journal in game
The Windows Quest Maker program

How to Use
Script is standard plug and play. Just put it below everything else but above main. The demo contains an example on how to format your .txt file for the quest data, but it essentially follows this pattern:

Quest Name

Phase 0 info

Phase 1 info

mobius_quest_break

And that's all there is to it! You just repeat that pattern for each quest, and save it somewhere in the project folder. But if you don't feel like making in all in NotePad, then you can use my Quest Maker.

Download here: Mobius's Quest Journal Maker

If you're still not sure, then check out the video tutorial here: Video Tutorial
You can also check out a second video tutorial covering the v2.0 updates: Video Tutorial 2

Once you've got everything imported, you can use the following script calls from anywhere.

$scene = Scene_Quest.new
- Calls the quest journal scene

$game_quests.discover_quest("quest_name")
- Adds the named quest to the player's journal in the default state
- You can also use the quest's ID rather than its name if you prefer

$game_quests.dq("quest_name")
- Same as above - just a shorthand version
- Ditto about IDs

$game_quests.set_phase("quest_name", phase_number)
- Changes the named quest's phase to the specified phase number
- Again, you can also use the quest's ID rather than its name if you prefer

$game_quests.sp("quest_name", phase_number)
- Same as above - just a shorthand version
- Ditto about IDs

$game_quests.complete_quest("quest_name")
- Changes the named quest to completed status
- Once more, you can also use the quest's ID rather than its name if you prefer

$game_quests.cq("quest_name")
- Same as above - just a shorthand version
- Ditto about IDs

Demo
Download here: demo


Script

Code:
#===============================================================================
# Mobius' Quest Journal
# Author: Mobius XVI
# Version: 2.3
# Date: 28 MAY 2020
#===============================================================================
#
# Introduction:
#
#   I wanted to create a plain looking, but robust quest/journal system that 
#   was akin to the one found in Skyrim. I also wanted to make it as user 
#   friendly as possible while still giving you control over implementation. 
#   I hope you enjoy it!
#
# Instructions:
#
#  - Place this script below all the default scripts but above main.
#
#  - Visit the forums for detailed instructions as well as two video tutorials
#    https://forums.rpgmakerweb.com/index.php?threads/mobiuss-quest-journal.19144/
#
# Issues/Bugs/Possible Bugs:
#
#   - Q. Why don't the changes I made to my quests show up during playtesting?
#   - A. Simply put, the script is not save game compatible. This is - 
#     unfortunately - the biggest current limitation of the script. If you start 
#     a playtest, save your game, and exit, and then make changes to the quests 
#     when you load up the save game it will not have any of your changes to the 
#     quests. But any changes you make should display correctly as long as you 
#     start a new game. My recommendation for getting around this problem is 
#     utilizing the debug (F9) menu to set quests to the desired state for testing, 
#     and always starting a new game.
#
#   - Q. Why does the first quest always show in the journal even if it hasn't 
#     been discovered yet?
#   - A. The journal needs at least one quest to function, so the first quest 
#     gets automatically discovered upon starting a new game. You can work 
#     around this limitation by adding a "starting" phase to it that doesn't 
#     give anything away like "You're on a new adventure!".
#
#  Credits/Thanks:
#    - Mobius XVI, author
#    - Special thanks to Zeriab. I borrowed the name for two of my classes 
#       from the Quest Book script, and overall I'd say it influenced my design.
#    - KK20, for finding a fairly obscure bug in my code
#
#  License
#    
#    This script is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported license. 
#    A human readable summary is available here: http://creativecommons.org/licenses/by-sa/3.0/deed.en_US
#    The full license is availble here: http://creativecommons.org/licenses/by-sa/3.0/legalcode
#    In addition, this script is only authorized to be posted to the forums on RPGMakerWeb.com.
#    Further, if you do decide to use this script in a commercial product, 
#    I'd ask that you let me know via a post here or a PM. Thanks.
#
#==============================================================================
# CUSTOMIZATION START
#==============================================================================
module Mobius
  module Quests
    #--------------------------------------------------------------------------
    # * Module constants
    #--------------------------------------------------------------------------
      CREATE_ENCRYPTED = false              # Determines encrypted file creation
      USE_ENCRYPTED = false                 # Sets use of encrypted file
      QUEST_FILENAME = "Data/QuestData.txt" # Sets unencrypted filename
      USE_SWITCHES_VARIABLES = true         # Sets use of switches/variables
      FIRST_SWITCH_ID = 2                   # Sets the first switch ID
      FIRST_VARIABLE_ID = 2                 # Sets the first variable ID
      RENAME_SWITCHES_VARIABLES = true      # Determines renaming of switches/variables
      SHOW_ALL_QUESTS = false               # DEBUGGING FEATURE - Always shows all quests  
  end
end
#==============================================================================
# CUSTOMIZATION END -- DON'T EDIT BELOW THIS LINE!!!
#==============================================================================

#==============================================================================
# ** Game Quest -- Mobius
#------------------------------------------------------------------------------
#  The class that holds quests. Each instance of Game Quest is used to hold
#  one quest.
#==============================================================================

class Game_Quests
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
    attr_accessor :all_quests           # Array of all quest objects
    attr_accessor :current_quests       # Array of all current quest objects
    attr_accessor :completed_quests     # Array of all completed quest objects
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize
    @all_quests = []
    @current_quests = []
    @completed_quests = []
    setup
  end
  #--------------------------------------------------------------------------
  # * Add Quest - adds a quest object to the all_quests array
  #--------------------------------------------------------------------------
  def add_quest(quest)
    @all_quests.push(quest)
  end
  #--------------------------------------------------------------------------
  # * Sort Quests
  # Refreshes the current_quests and completed_quests arrays
  # Also sorts them as well as the all quests array by ID's
  #--------------------------------------------------------------------------
  def sort_quests
    # Sort the all_quests array by ID
    @all_quests.sort {|a,b| a.id <=> b.id }
    # Reset the current and completed quest arrays
    @current_quests = []
    @completed_quests = []
    # Push known and completed quests to their appropiate arrays
    for quest in @all_quests
      if quest.known and quest.completed
        @completed_quests.push(quest)
      elsif quest.known
        @current_quests.push(quest)
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Discover Quest - uses quest name or id to change state of quest to known
  #--------------------------------------------------------------------------
  def discover_quest(name_or_id)
    make_quest_change(name_or_id, :discover)
  end
  # Create shorthand name for eventing scripts
  alias dq discover_quest
  #--------------------------------------------------------------------------
  # * Set Phase - uses quest name or id to change phase
  #--------------------------------------------------------------------------
  def set_phase(name_or_id, phase)
    make_quest_change(name_or_id, :phase=, phase)
  end
  # Create shorthand name for eventing scripts
  alias sp set_phase
  #--------------------------------------------------------------------------
  # * Complete Quest
  # Uses quest name or id to change state of quest to complete
  #--------------------------------------------------------------------------
  def complete_quest(name_or_id)
    make_quest_change(name_or_id, :complete)
  end
  # Create shorthand name for eventing scripts
  alias cq complete_quest
  #--------------------------------------------------------------------------
  # * Try Lookup Quest
  # Uses quest name or id to get the quest object
  #--------------------------------------------------------------------------
  def try_lookup_quest(name_or_id)
    # Check if passed value is ID
    if name_or_id.is_a?(Integer)
      # Check if ID is valid
      if @all_quests[name_or_id].is_a?(Game_Quest)
        # Return found quest
        return true, @all_quests[name_or_id]
      # If ID is invalid
      else 
        # Return error message
        message = "The quest ID provided (#{name_or_id}) is not valid." +
                  "Check that the quest exists and that the id is correct."
        return false, message
      end
    # Else is it a string
    elsif name_or_id.is_a?(String)
      # Look up quest using name
      quest_to_change = @all_quests.find {|quest| quest.name == name_or_id}
      # Check if quest is valid
      if quest_to_change.is_a?(Game_Quest)
        # Return found quest
        return true, quest_to_change
      # If quest is invalid
      else     
        # Make newlines literal to call attention to them
        name_or_id = name_or_id.gsub("\n",'<line break>')
        # Return error message
        message = "The quest name '#{name_or_id}' was not found.\n" +
              "Check that the quest exists and that the spelling\n" +
              "is correct."
        return false, message
      end
    # If input is invalid
    else 
      # Return error message
      message = "Unrecognized input provided to method 'discover_quest'.\n" +
                "Input should be either an integer for the quest ID or\n" +
                "a string representing the quest name." 
      return false, message
    end
  end
  #--------------------------------------------------------------------------
  # * Make Quest Change
  # Uses quest name or id to change the state of a quest per the action
  #--------------------------------------------------------------------------
  def make_quest_change(name_or_id, action, *args)
    # Try to find a matching quest based on the name or ID
    found, quest_or_message = try_lookup_quest(name_or_id)
    # If we found a match...
    if found
      # ...Then this should be a quest
      quest = quest_or_message
      # Safety check that the method exists
      if quest.respond_to?(action)
        # Do the action on the quest (discover, complete, etc.)
        quest.send(action, *args)
      end
    # If we didn't find anything...
    else
      # ...Then this should be an error message
      message = quest_or_message
      # Display the message to the user
      print(message)
    end
    # Since things have changed, re-sort the quests
    sort_quests
  end
  #--------------------------------------------------------------------------
  # * Data Check
  # Performs a data check on the specified quest
  #--------------------------------------------------------------------------
  def data_check(id)
    @all_quests[id].data_check if @all_quests[id]
  end
  #--------------------------------------------------------------------------
  # * Data Check All
  # Performs a data check on all quests
  #--------------------------------------------------------------------------
  def data_check_all
    for quest in @all_quests
      quest.data_check
    end
  end
  #--------------------------------------------------------------------------
  # * Setup - Performs first time setup of quest data
  #--------------------------------------------------------------------------
  def setup
    # begin block for error handling
    begin
      # if true
      if Mobius::Quests::CREATE_ENCRYPTED
        # Load unencrypted data
        Game_Quests.normal_setup
        # Create encrypted .rxdata
        Game_Quests.create_encrypted
      # elsif true
      elsif Mobius::Quests::USE_ENCRYPTED
        # Load encrypted data
        Game_Quests.encrypted_setup
      else
        # Load unencrypted data
        Game_Quests.normal_setup
      end
      # initialize Game_Quest object data from $data_quests array
      for quest in $data_quests
        self.add_quest(quest)
      end
      # Set Main Quest to known
      discover_quest(0)
    # rescue when no file is found   
    rescue Errno::ENOENT => e 
      Game_Quests.on_no_file(e)
      raise SystemExit
    end
  end
  #--------------------------------------------------------------------------
  # * GQs - Normal Setup
  # Class method that intializes normal quest data 
  #--------------------------------------------------------------------------
  def Game_Quests.normal_setup
    # Create array of quest data from file
    quest_array = File.open(Mobius::Quests::QUEST_FILENAME) {|f| 
                f.readlines("mobius_quest_break\n\n")}
    # Remove empty last element if necessary
    if quest_array.last.rstrip == ""
      quest_array.pop
    end
    # Initialize $data_quests array
    $data_quests = Array.new
    # Create Game_Quest objects from data
    for quest_data in quest_array
      # Split quest data by paragraph
      quest_data_array = quest_data.split("\n\n")
      # Remove file delimiter "mobius_quest_break\n\n"
      quest_data_array.pop
      # Set and remove name
      name = quest_data_array.shift
      # Initialize info array
      info_array = []
      # Organize phase info into useable line lengths
      for quest_data_line in quest_data_array
        new_arr = []
        # Split phase info into words
        temp_arr = quest_data_line.split
        temp_str = ""
        for word in temp_arr
          # Rejoin words together
          temp_str.concat(word + " ")
          # When line length is useable, push to new_arr
          if temp_str.size >= 35
            new_arr.push(temp_str.strip)
            temp_str = ""
          end
        end
        # Push leftover string
        new_arr.push(temp_str.strip) unless temp_str == ""
        # Push phase info to info_array
        info_array.push(new_arr)
      end
      # Push new Game_Quest object to $data_quests array
      $data_quests.push(Game_Quest.new(name, info_array))
    end
  end
  #--------------------------------------------------------------------------
  # * GQs - Encrypted Setup
  # Class method that intializes encrypted quest data 
  #--------------------------------------------------------------------------
  def Game_Quests.encrypted_setup
    # load encrypted data
    $data_quests = load_data("Data/Quests.rxdata")
  end
  #--------------------------------------------------------------------------
  # * GQs - Create Setup
  # Class method that creates encrypted quest data 
  #--------------------------------------------------------------------------
  def Game_Quests.create_encrypted
    # save encrypted data
    save_data($data_quests, "Data/Quests.rxdata")
  end
  #--------------------------------------------------------------------------
  # * GQs - File Search
  # Class method that runs a search looking for similarly named files 
  #--------------------------------------------------------------------------
  def Game_Quests.file_search(dir, search_string)
    matches = []
    Dir.foreach(dir) do |entry|
      next if ((entry == ".") or (entry == ".."))
      if File.directory?(entry)
        # search sub directory
        sub_dir = File.expand_path(entry, dir)
        matches += Game_Quests.file_search(sub_dir, search_string)
      else
        # run comparison
        if entry.match(Regexp.new(search_string, true))
          full_path = File.expand_path(entry, dir)
          matches.push(full_path)
        end
      end
    end
    return matches
  end
  #--------------------------------------------------------------------------
  # * GQs - On No File
  # Class method that handles a no file error 
  #--------------------------------------------------------------------------
  def Game_Quests.on_no_file(error)
    # Construct filenames
    user_filename = error.message.sub("No such file or directory - ", "")
    user_basename = File.basename(user_filename, ".*")
    full_path = File.expand_path(user_filename)
    # Run search using given filename    
    matches = Game_Quests.file_search(Dir.pwd, user_basename)
    # Construct error message to user
    message = "##Mobius' Quest Journal Script Says##\n"
    message += "I was unable to find the file named: "
    message += "\n\"" + full_path + "\"\n\n"
    message += "I searched this folder: \n\"" + Dir.pwd + "\"\n"
    message += "and its subfolders for similar file names"
    unless matches.empty?
      message += " and I found these. Maybe they're what you want?\n\n\""
      message += matches.join("\"\n\n")
    else
      message += " but I couldn't find anything."
    end
    # display error message to user
    print(message)
  end
end

#==============================================================================
# ** Game_Quests
#------------------------------------------------------------------------------
#  This class handles the Game Quest arrays. Refer to "$game_quests" for the
#  instance of this class.
#==============================================================================

class Game_Quests
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
    attr_accessor :all_quests           # Array of all quest objects
    attr_accessor :current_quests       # Array of all current quest objects
    attr_accessor :completed_quests     # Array of all completed quest objects
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize
    @all_quests = []
    @current_quests = []
    @completed_quests = []
    setup
  end
  #--------------------------------------------------------------------------
  # * Add Quest - adds a quest object to the all_quests array
  #--------------------------------------------------------------------------
  def add_quest(quest)
    @all_quests.push(quest)
  end
  #--------------------------------------------------------------------------
  # * Sort Quests
  # Refreshes the current_quests and completed_quests arrays
  # Also sorts them as well as the all quests array by ID's
  #--------------------------------------------------------------------------
  def sort_quests
    # Sort the all_quests array by ID
    @all_quests.sort {|a,b| a.id <=> b.id }
    # Reset the current and completed quest arrays
    @current_quests = []
    @completed_quests = []
    # Push known and completed quests to their appropiate arrays
    for quest in @all_quests
      if quest.known and quest.completed
        @completed_quests.push(quest)
      elsif quest.known
        @current_quests.push(quest)
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Discover Quest - uses quest name or id to change state of quest to known
  #--------------------------------------------------------------------------
  def discover_quest(name_or_id)
    # Check if passed value is ID
    if name_or_id.is_a?(Integer)
      # Check if ID is valid
      if @all_quests[name_or_id].is_a?(Game_Quest)
        # Set quest to known
        @all_quests[name_or_id].discover
      else # If ID is invalid
        # Print debug message
        print("The quest ID provided (#{name_or_id}) is not valid.")
      end
    elsif name_or_id.is_a?(String)
      # Look up quest using name
      quest_to_change = @all_quests.find {|quest| quest.name == name_or_id}
      # Check if quest is valid
      if quest_to_change.is_a?(Game_Quest)
        # Set quest to known
        quest_to_change.discover
      else # If quest is invalid
        # Print debug message
        print("The quest name '#{name_or_id}' was not found.\n" +
              "Check that the quest exists and that the spelling\n" +
              "is correct." )
      end
    else # If input is invalid
      # Print debug message
      print("Unrecognized input provided to method 'discover_quest'.\n" +
            "Input should be either an integer for the quest ID or\n" +
            "a string representing the quest name." )
    end
    sort_quests
  end
  # Create shorthand name for eventing scripts
  alias dq discover_quest
  #--------------------------------------------------------------------------
  # * Set Phase - uses quest name or id to change phase
  #--------------------------------------------------------------------------
  def set_phase(name_or_id, phase)
    # Check if passed value is ID
    if name_or_id.is_a?(Integer)
      # Set quest to known
      @all_quests[name_or_id].phase = phase
    else
      # Look up quest using name
      quest_to_change = @all_quests.find {|quest| quest.name == name_or_id}
      # Set quest to known
      quest_to_change.phase = phase
    end
    sort_quests
  end
  # Create shorthand name for eventing scripts
  alias sp set_phase
  #--------------------------------------------------------------------------
  # * Complete Quest
  # Uses quest name or id to change state of quest to complete
  #--------------------------------------------------------------------------
  def complete_quest(name_or_id)
    # Check if passed value is ID
    if name_or_id.is_a?(Integer)
      # Check if ID is valid
      if @all_quests[name_or_id].is_a?(Game_Quest)
        # Set quest to known
        @all_quests[name_or_id].complete
      else # If ID is invalid
        # Print debug message
        print("The quest ID provided (#{name_or_id}) is not valid." +
              "Check that the quest exists and that the id is correct." )
      end
    elsif name_or_id.is_a?(String)
      # Look up quest using name
      quest_to_change = @all_quests.find {|quest| quest.name == name_or_id}
      # Check if quest is valid
      if quest_to_change.is_a?(Game_Quest)
        # Set quest to known
        quest_to_change.complete
      else # If quest is invalid
        # Print debug message
        print("The quest name '#{name_or_id}' was not found.\n" +
              "Check that the quest exists and that the spelling\n" +
              "is correct." )
      end
    else # If input is invalid
      # Print debug message
      print("Unrecognized input provided to method 'discover_quest'.\n" +
            "Input should be either an integer for the quest ID or\n" +
            "a string representing the quest name." )
    end
    sort_quests
  end
  # Create shorthand name for eventing scripts
  alias cq complete_quest
  #--------------------------------------------------------------------------
  # * Data Check
  # Performs a data check on the specified quest
  #--------------------------------------------------------------------------
  def data_check(id)
    @all_quests[id].data_check if @all_quests[id]
  end
  #--------------------------------------------------------------------------
  # * Data Check All
  # Performs a data check on all quests
  #--------------------------------------------------------------------------
  def data_check_all
    for quest in @all_quests
      quest.data_check
    end
  end
  #--------------------------------------------------------------------------
  # * Setup - Performs first time setup of quest data
  #--------------------------------------------------------------------------
  def setup
    # begin block for error handling
    begin
      # if true
      if Mobius::Quests::CREATE_ENCRYPTED
        # Load unencrypted data
        Game_Quests.normal_setup
        # Create encrypted .rxdata
        Game_Quests.create_encrypted
      # elsif true
      elsif Mobius::Quests::USE_ENCRYPTED
        # Load encrypted data
        Game_Quests.encrypted_setup
      else
        # Load unencrypted data
        Game_Quests.normal_setup
      end
      # initialize Game_Quest object data from $data_quests array
      for quest in $data_quests
        self.add_quest(quest)
      end
      # Set Main Quest to known
      discover_quest(0)
    # rescue when no file is found   
    rescue Errno::ENOENT => e 
      Game_Quests.on_no_file(e)
      raise SystemExit
    end
  end
  #--------------------------------------------------------------------------
  # * GQs - Normal Setup
  # Class method that intializes normal quest data 
  #--------------------------------------------------------------------------
  def Game_Quests.normal_setup
    # Create array of quest data from file
    quest_array = File.open(Mobius::Quests::QUEST_FILENAME) {|f| 
                f.readlines("mobius_quest_break\n\n")}
    # Remove empty last element if necessary
    if quest_array.last.rstrip == ""
      quest_array.pop
    end
    # Initialize $data_quests array
    $data_quests = Array.new
    # Create Game_Quest objects from data
    for quest_data in quest_array
      # Split quest data by paragraph
      quest_data_array = quest_data.split("\n\n")
      # Remove file delimiter "mobius_quest_break\n\n"
      quest_data_array.pop
      # Set and remove name
      name = quest_data_array.shift
      # Initialize info array
      info_array = []
      # Organize phase info into useable line lengths
      for quest_data_line in quest_data_array
        new_arr = []
        # Split phase info into words
        temp_arr = quest_data_line.split
        temp_str = ""
        for word in temp_arr
          # Rejoin words together
          temp_str.concat(word + " ")
          # When line length is useable, push to new_arr
          if temp_str.size >= 35
            new_arr.push(temp_str.strip)
            temp_str = ""
          end
        end
        # Push leftover string
        new_arr.push(temp_str.strip) unless temp_str == ""
        # Push phase info to info_array
        info_array.push(new_arr)
      end
      # Push new Game_Quest object to $data_quests array
      $data_quests.push(Game_Quest.new(name, info_array))
    end
  end
  #--------------------------------------------------------------------------
  # * GQs - Encrypted Setup
  # Class method that intializes encrypted quest data 
  #--------------------------------------------------------------------------
  def Game_Quests.encrypted_setup
    # load encrypted data
    $data_quests = load_data("Data/Quests.rxdata")
  end
  #--------------------------------------------------------------------------
  # * GQs - Create Setup
  # Class method that creates encrypted quest data 
  #--------------------------------------------------------------------------
  def Game_Quests.create_encrypted
    # save encrypted data
    save_data($data_quests, "Data/Quests.rxdata")
  end
  #--------------------------------------------------------------------------
  # * GQs - File Search
  # Class method that runs a search looking for similarly named files 
  #--------------------------------------------------------------------------
  def Game_Quests.file_search(dir, search_string)
    matches = []
    Dir.foreach(dir) do |entry|
      next if ((entry == ".") or (entry == ".."))
      if File.directory?(entry)
        # search sub directory
        sub_dir = File.expand_path(entry, dir)
        matches += Game_Quests.file_search(sub_dir, search_string)
      else
        # run comparison
        if entry.match(Regexp.new(search_string, true))
          full_path = File.expand_path(entry, dir)
          matches.push(full_path)
        end
      end
    end
    return matches
  end
  #--------------------------------------------------------------------------
  # * GQs - On No File
  # Class method that handles a no file error 
  #--------------------------------------------------------------------------
  def Game_Quests.on_no_file(error)
    # Construct filenames
    user_filename = error.message.sub("No such file or directory - ", "")
    user_basename = File.basename(user_filename, ".*")
    full_path = File.expand_path(user_filename)
    # Run search using given filename    
    matches = Game_Quests.file_search(Dir.pwd, user_basename)
    # Construct error message to user
    message = "##Mobius' Quest Journal Script Says##\n"
    message += "I was unable to find the file named: "
    message += "\n\"" + full_path + "\"\n\n"
    message += "I searched this folder: \n\"" + Dir.pwd + "\"\n"
    message += "and its subfolders for similar file names"
    unless matches.empty?
      message += " and I found these. Maybe they're what you want?\n\n\""
      message += matches.join("\"\n\n")
    else
      message += " but I couldn't find anything."
    end
    # display error message to user
    print(message)
  end
end

#==============================================================================
# ** Window Quest Info
#------------------------------------------------------------------------------
#  This window lists the info for the quests
#==============================================================================

class Window_QuestInfo < Window_Selectable
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize()
    super(200, 0, 440, 480)
    self.active = false
    self.contents = Bitmap.new(width - 32, height - 32)
    self.index = -1
    refresh([""])
  end
  #--------------------------------------------------------------------------
  # * Refresh
  #--------------------------------------------------------------------------
  def refresh(text_array)
    # Clear old contents
    if self.contents != nil
      self.contents.clear
    end
    # Set font color
    self.contents.font.color = normal_color
    # Break if text_array is nil
    return unless text_array
    # Draw info
    for i in 0...text_array.size
      line = text_array[i]
      self.contents.draw_text(0, i * 22, 408, 22, line)
    end
  end
end

#==============================================================================
# ** Window Quest List
#------------------------------------------------------------------------------
#  This window lists all currently active/completed quests
#==============================================================================

class Window_QuestList < Window_Selectable 
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize()
    super(0, 0, 200, 480)
    @current_quests = []        # Array of current quests
    @completed_quests = []      # Array of completed quests
    @top_half_size = 0          # Number of rows of current quests
    @bottom_half_size = 0       # Number of rows of completed quests
    self.active = true
    self.index = 0
    refresh
  end
  #--------------------------------------------------------------------------
  # * Refresh
  #--------------------------------------------------------------------------
  def refresh
    # Determine total number of rows
    @item_max = [@top_half_size + @bottom_half_size, 1].max
    if self.contents != nil
      self.contents.dispose
    end
    # Draw bitmap
    self.contents = Bitmap.new(200 - 32, row_max * 32)
    self.contents.font.color = normal_color
    # Draw current quests
    for i in 0...@top_half_size
      quest_name = @current_quests[i].name
      self.contents.draw_text(8, i * 32, 160, 32, quest_name)
    end
    self.contents.font.color = disabled_color
    # Draw completed quests
    for i in 0...@bottom_half_size
      quest_name = @completed_quests[i].name
      self.contents.draw_text(8, i * 32 + @top_half_size * 
      32, 160, 32, quest_name)
    end
  end
  #--------------------------------------------------------------------------
  # * Set Quests
  #--------------------------------------------------------------------------
  def set_quests(new_current_quests, new_completed_quests)
    if @current_quests != new_current_quests or
       @completed_quests != new_completed_quests
       #set new quests
       @current_quests = new_current_quests
       @completed_quests = new_completed_quests
       @top_half_size = @current_quests.size
       @bottom_half_size = @completed_quests.size
       #call update
       refresh
    end
  end
  #--------------------------------------------------------------------------
  # * Get Index Info
  #   Returns the text info from which ever quest is currently highlighted
  #--------------------------------------------------------------------------
  def get_index_info
    # Unless there are no quests
    unless @current_quests.empty? and @completed_quests.empty?
      # Determine cursor location
     if self.index < @top_half_size
        # Get selected quest info
        @current_quests[self.index].get_current_info 
      else
        # Get selected quest info
        @completed_quests[self.index - @top_half_size].get_current_info
      end
    end
  end
end

#==============================================================================
# ** Scene_Quest
#------------------------------------------------------------------------------
#  This class performs quest screen processing.
#==============================================================================

class Scene_Quest
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize(return_scene = $scene.type)
    @return_scene = return_scene
  end
  #--------------------------------------------------------------------------
  # * Main Processing
  #--------------------------------------------------------------------------
  def main
    # Make QuestList Window
    @quest_list_window = Window_QuestList.new
    # Make QuestInfo Window
    @quest_info_window = Window_QuestInfo.new
    # Create memory variable
    @list_index = @quest_list_window.index
    # Update Game Quests
    $game_quests.data_check_all if Mobius::Quests::USE_SWITCHES_VARIABLES
    $game_quests.sort_quests
    # Refresh QuestList
    unless Mobius::Quests::SHOW_ALL_QUESTS
      # Normal refresh
      @quest_list_window.set_quests($game_quests.current_quests, 
                                    $game_quests.completed_quests)
    else
      # DEBUG refresh
      @quest_list_window.set_quests($game_quests.all_quests, [] )
    end
    # Redraw info window
    new_text = @quest_list_window.get_index_info
    @quest_info_window.refresh(new_text)
    # Execute transition
    Graphics.transition
    # Main loop
    loop do
      # Update game screen
      Graphics.update
      # Update input information
      Input.update
      # Frame update
      update
      # Abort loop if screen is changed
      if $scene != self
        break
      end
    end
    # Prepare for transition
    Graphics.freeze
    # Dispose of windows
    @quest_list_window.dispose
    @quest_info_window.dispose
  
  end
  #--------------------------------------------------------------------------
  # * Frame Update
  #--------------------------------------------------------------------------
  def update
    # Update windows
    @quest_list_window.update
    # If index has changed
    if @list_index != @quest_list_window.index
      # Redraw info window
      new_text = @quest_list_window.get_index_info
      @quest_info_window.refresh(new_text)
      # Set index memory
      @list_index = @quest_list_window.index
    end
    # When cancel
    if Input.trigger?(Input::B)
      # Play cancel SE
      $game_system.se_play($data_system.cancel_se)
      # Return to menu
      $scene = @return_scene.new
    end
  end
end

# Changes to Scene_Title
class Scene_Title
  
  # Alias old method
  alias mobius_command_new_game command_new_game
  def command_new_game
    # Call old method
    mobius_command_new_game
    # Initialize Game_Quests object
    $game_quests = Game_Quests.new
  end
end

# Changes to Scene_Save
class Scene_Save
  
  # Alias old method
  alias mobius_write_save_data write_save_data
  def write_save_data(file)
    # Call old method
    mobius_write_save_data(file)
    # Dump Game_Quests object state to the save file
    Marshal.dump($game_quests, file)
  end
end

# Changes to Scene_Load
class Scene_Load
  
  # Alias old method
  alias mobius_read_save_data read_save_data
  def read_save_data(file)
    # Call old method
    mobius_read_save_data(file)
    # Load Game_Quests object state from the save file
    $game_quests = Marshal.load(file) 
  end
  
end

# Changes to Interpreter
class Interpreter
  # Create alias
  alias mobius_command_121 command_121
  #--------------------------------------------------------------------------
  # * Control Switches
  #--------------------------------------------------------------------------
  def command_121
    mobius_command_121
    # Only do this is using switches/variables
    if Mobius::Quests::USE_SWITCHES_VARIABLES and $game_quests
      # Get first quest switch id
      first = Mobius::Quests::FIRST_SWITCH_ID
      # Loop for group control
      for i in @parameters[0] .. @parameters[1]        
        # If first id and chosen id have same parity
        if (first % 2) == ( i % 2 )
          # Determine corresponding quest id
          id = ( i - first ) / 2
        # If first id and chosen id have different parity
        else
          # Determine corresponding quest id
          id = ( i - first - 1 ) / 2
        end
        $game_quests.data_check(id)      
      end
    end
    # Continue
    return true
  end
  # Create alias
  alias mobius_command_122 command_122
  #--------------------------------------------------------------------------
  # * Control Variables
  #--------------------------------------------------------------------------
  def command_122
    mobius_command_122
    # Only do this is using switches/variables
    if Mobius::Quests::USE_SWITCHES_VARIABLES and $game_quests
      # Get first quest switch id
      first = Mobius::Quests::FIRST_VARIABLE_ID
      # Loop for group control
      for i in @parameters[0] .. @parameters[1]        
        # Determine corresponding quest id
        id = ( i - first )
        $game_quests.data_check(id)
      end
    end
    # Continue
    return true
  end
  
end

FAQ
Q. Why don't the changes I made to my quests show up during playtesting?
A. Simply put, the script is not save game compatible. This is - unfortunately - the biggest current limitation of the script. If you start a playtest, save your game, and exit, and then make changes to the quests when you load up the save game it will not have
any of your changes to the quests. But any changes you make should display correctly as long as you start a new game. My
recommendation for getting around this problem is utilizing the debug (F9) menu to set quests to the desired state for testing, and always starting a new game.

Q. Why does the first quest always show in the journal even if it hasn't been discovered yet?
A. The journal needs at least one quest to function, so the first quest gets automatically discovered upon starting a new game. You can work around this limitation by adding a "starting" phase to it that doesn't give anything away like "You're on a new adventure!".

Credit and Thanks
- Mobius XVI, author
- Special thanks to Zeriab. I borrowed the name for two of my classes from the Quest Book script, and overall I'd say it influenced my design.
- KK20, for finding a fairly obscure bug in my code

Author's Notes
This script is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported license.
A human readable summary is available here: http://creativecommons.org/licenses/by-sa/3.0/deed.en_US
The full license is availble here: http://creativecommons.org/licenses/by-sa/3.0/legalcode
In addition, this script is only authorized to be posted to the forums on RPGMakerWeb.com.
Further, if you do decide to use this script in a commercial product, I'd ask that you let me know via a post here or a PM. Thanks.
 
Last edited:

The Dragon God

Indie Game Dev, Concept Artist
Veteran
Joined
Oct 16, 2013
Messages
246
Reaction score
6
First Language
English
Primarily Uses
I need a Youtube video of you doing this step by step. I'm a visual learner. Other that that this looks awesome!
 

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Per your request, I've added a video tutorial. Hope it helps!
 

F117Landers

Veteran
Veteran
Joined
Jun 15, 2014
Messages
63
Reaction score
5
First Language
English
Primarily Uses
Forgive my Ignorance, I am new to RPG Maker and Scripting. If I am understanding correctly:

$scene = Scene_Quest.new runs the script so that the other functions can be used

B key opens the journal for players

Is this correct?
 

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Forgive my Ignorance, I am new to RPG Maker and Scripting. If I am understanding correctly:

$scene = Scene_Quest.new runs the script so that the other functions can be used

B key opens the journal for players

Is this correct?
Not quite. So $scene = Scene_Quest.new will open the journal for players. Then if you wanted to, you could make a common event in the database that would call $scene = Scene_Quest.new  when the B button is pressed (or any other button for that matter). The rest of the functions allow you to change the quests and can be called anywhere/anytime.
 

F117Landers

Veteran
Veteran
Joined
Jun 15, 2014
Messages
63
Reaction score
5
First Language
English
Primarily Uses
Ah, that makes more sense; Thanks. The reason I ask about B in particular is because in your common event Script Scene_Quest (in the demo) you indicate B to cancel.

Edit: I have now discovered that Scripts and Common Events are different things. Also found the Common Events in your demo.
 
Last edited by a moderator:

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Glad you're getting things figured out!  If you haven't seen it yet, there's a tutorials forum on here that'll teach you how to do lots of nifty things, and you can also find guides on just the basic functions as well.
 

F117Landers

Veteran
Veteran
Joined
Jun 15, 2014
Messages
63
Reaction score
5
First Language
English
Primarily Uses
Thanks!. Though to be honest, I always have trouble sticking to tutorials. I've been going through various demo's that people have of their scripts to see how it works and how it relates to ruby.

Speaking of scripts, I did have a question about the Quest Log.  I didn't see any references, but I just wanted to double-check: Are any of the variables or switches used by your quest log?
 

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Nope. My script uses it's own internal data to track things, so it doesn't use any variables or switches. I've considered adding in the option to use them instead of the built-in data, but have yet to actually do that.
 

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Version 2.0 of the script is live! The script now offers integration with switches/variables, and a few other things. Check out the video tutorial here
 

GraveBusta

Vengful
Veteran
Joined
Aug 19, 2014
Messages
150
Reaction score
14
First Language
English
This is a pretty neat tool thanks a ton :) I am new here yet I am liking it more and more by the day finding new cool stuff all the time.
 

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
You're welcome, and thanks! It's always nice to hear that people are using and enjoying my work. And since you're new, let me say welcome! This is a great community with a lot of helpful people so if you ever have any questions or specific requests for things, don't hesitate to ask.
 

SuperMasterSword

That Guy You Dont Know
Veteran
Joined
Jun 28, 2014
Messages
117
Reaction score
48
First Language
Javascript
Primarily Uses
RMMV
Is there a way to use variables but not switches? I haven't used it in a real game yet but I plan to and I was fine using the script calls but I thought I could use the variables to check how far they are and not let them pass if they aren't. in short, well the first sentence :D . Thanks!
 

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Is there a way to use variables but not switches? I haven't used it in a real game yet but I plan to and I was fine using the script calls but I thought I could use the variables to check how far they are and not let them pass if they aren't. in short, well the first sentence :D . Thanks!
Short answer: yes :thumbsup-right:  Long answer: when using switches and variables, the script will keep everything self consistent, meaning you can use any combination of script calls/events. As an example, let's say Quest #1 uses the switches 1 and 2, and the variable 1. The following would all then be equivalent:

$game_quests.discover_quest(1) == Set Game Switch #1 to true (via event)

$game_quests.complete_quest(1) == Set Game Switch #2 to true (via event)

$game_quests.set_phase(1, 3) == Set Game Variable #1 to 3 (via event)

It doesn't matter which you use. I did this on purpose so that previous users of my script could upgrade to the new version and use switches/variables without breaking their projects and making them have to go back and re-do a bunch of work.
 
Last edited by a moderator:

SuperMasterSword

That Guy You Dont Know
Veteran
Joined
Jun 28, 2014
Messages
117
Reaction score
48
First Language
Javascript
Primarily Uses
RMMV
Ah, ok. to be honest when I first saw this script I was first reminded of the Jak and Daxter series, which if you haven't played it doesn't have phases, but only missions which are either in progress or completed. (although there also weren't any side quests, but personally I still thought it was a good game)

EDIT: and the point is that's how I thought I could use it. although partly because I might miss if I can't ever see what it said before when it switches to a new phase. (don't ask why, I don't know) (though to be honest [again] I could just copy and paste and add on to the previous phase)
 
Last edited by a moderator:

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
Man, it's been years since I played Jak and Daxter, and yeah I thought it was good too. But I actually thought about including previous phase info; like letting the player go back and read previous phases. I ended up deciding against it because - for my own project - I wanted to have some branching side quests. So by not showing old phases, I was able to set up the quest like phase 1->2->3->branch to either 4 or 5, and take that same concept even further if I wanted to. And you're right, if you really want the player to have the old phase info, then you can always just copy/paste.
 

SuperMasterSword

That Guy You Dont Know
Veteran
Joined
Jun 28, 2014
Messages
117
Reaction score
48
First Language
Javascript
Primarily Uses
RMMV
just to be sure, when I use $game_quests.set_phase would it also change the variable automatically? (and similarly change the corresponding switch if I used .discover_quest or .complete_quest?)
 

MobiusXVI

Game Maker
Veteran
Joined
Mar 20, 2013
Messages
374
Reaction score
89
First Language
English
Primarily Uses
just to be sure, when I use $game_quests.set_phase would it also change the variable automatically? (and similarly change the corresponding switch if I used .discover_quest or .complete_quest?)
Yep! Give it a try sometime. Just set up an event with a script call and then verify the change was made to the corresponding variable/switch with the F9 debug menu.
 

Mesajia

Veteran
Veteran
Joined
Aug 14, 2014
Messages
81
Reaction score
49
First Language
German
Primarily Uses
RMMV
Hello Mobius,

what a nice work. The Quest Maker is perfect for people like me who are very bad in scripting and remembering scriptlines :) And I also have a very nice overview about the quests I m planning.

But I have a question: Is it possible to show icons or smaller pictures in the Journal?
 

Users Who Are Viewing This Thread (Users: 0, Guests: 2)

Latest Threads

Latest Posts

Latest Profile Posts

The presentation pictures for FSM Castle Tiles are really bad, there is so much more content in this DLC than they show. Great DLC.^^
Anyone know/remember Mummy's Tomb / Crystals of Zong for C64? Trying to recreate it for One Map Challenge. Fun. And ... er ... challenging!


Listen.. I er... caught the live stream. :LZSwink:
The MZ first look stream was quite good. Looks like we get a lot of generator parts right off the bat this time around. Lots of cool clothes, helmets, facial marks, etc. Looking forward to playing around with it. :)
Any tips on how to share resources in here? Should I upload them in a image hosting site or Google Drive?

Forum statistics

Threads
100,821
Messages
979,953
Members
132,469
Latest member
baekyam3
Top