The RPG Maker Resource Kit

RMRK RPG Maker Creation => VX Ace => VXA Scripts Database => Topic started by: modern algebra on December 26, 2012, 07:13:14 PM

Title: [VXA] Item Instances Base
Post by: modern algebra on December 26, 2012, 07:13:14 PM
Item Instances Base
Version: 1.0.0
Author: modern algebra
Date: 26 December 2012

Version History



Description


This script changes the item system in RMVX Ace to accomodate for items of the same ID to have different stats. It can thereby serve as a base for scripts that require such functionality, such as durability for weapons and armors, charges for items, weapons that level up, socketing, etc...

This script does none of that by default - it only sets up data structures to permit that, and its only independent use is that you can manually change the stats on an item added to the party through events.

Features


Instructions

Insert this script into its own slot in the Script Editor, above Main but below Materials. This script must also be above every script that requires it.

Script


Code: [Select]
#==============================================================================
#    Instance Items Base [VXA]
#    Version: 1.0.0
#    Author: modern algebra (rmrk.net)
#    Date: 26 December 2012
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  Description:
#
#    This script changes the item system in RMVX Ace to accomodate for items
#   of the same ID to have different stats. It can thereby serve as a base for
#   scripts that require such functionality, such as durability for weapons and
#   armors, charges for items, weapons that level up, socketing, etc...
#
#    This script does none of that by default - it only sets up data structures
#   to permit that, and its only independent use is that you can manually
#   change the stats on an item added to the party through events.
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  Instructions:
#
#    Insert this script into its own slot in the Script Editor, above Main but
#   below Materials. This script must also be above every script that requires
#   it.
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  Instructions for Scripters:
#
#    If you are a scripter and want to write a script that requires this script
#   in order to be used, you have my permission. I ask only that you link back
#   to this script at RMRK, as well as provide me with a link to your script so
#   that I can ensure compatibility.
#
#    I have tried to comment the script where there might be some confusion,
#   so you should be able to read through it to gain an understanding as to
#   how it works. For the most part, it is designed to mimic as much as
#   possible the default arrangement. So, if you access the first 999 indices
#   of $data_items, $data_weapons, or $data_armors, you will receive the basic
#   classes for those. New instances begin at ID 1000, and each new instance
#   will have a unique ID, which is what is returned when you call the #id
#   method. Instances will also have a #database_id method which returns the
#   ID of its base item. Instance items are now of the Game_IItem, Game_IWeapon,
#   or Game_IArmor. Game_IItem inherits its methods from the module
#   Game_IUsableItem, and the others inherit from the module Game_IEquipItem.
#   Both Game_IEquipItem and Game_IUsableItem inherit from Game_IBaseItem.
#
#    If you need to add a new notefield tag, it is recommended you also add it
#   to MA_INSTANCE_ITEMS_BASE[:regexp_array], as any regular expressions
#   included in that array will make the #instance_based? method in
#   RPG::BaseItem return true if the item has that in its note field.
#
#    Beyond that, I recommend you read the entire script in order to gain an
#   understanding of how it works.
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  Support:
#
#    If you have any questions, feel free to ask them in the script's topic:
#
#        http://rmrk.net/index.php/topic,47427.0.html
#
#   I will try to answer as swiftly as possible. If you believe further
#  documentation is required, please let me know.
#==============================================================================

$imported = {} unless $imported
$imported[:"MA_InstanceItemsBase 1.0.0"] = true

MA_INSTANCE_ITEMS_BASE = {
  #  This sets the first index used for instance items. It should be 1000
  # unless you are using another script which makes extends the database
  # limitations beyond 999.
  :index_start => 1000,
  #  This array tracks the note field codes which identify that an item should
  # be created as new instances.
  :regexp_array => [/\\INS/i],
  #  This array holds methods which you would never want to distinguish between
  # instances
  :ignore_accessors => [],
}

#==============================================================================
# ** MAIIB_RPG_BaseItem
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  This mixes in to RPG::Item, RPG::Weapon, and RPG::Armor, adding the
#  following methods:
#    overwritten method - ==
#    new methods - instance_based?; database_id
#==============================================================================

module MAIIB_RPG_BaseItem
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Instance Based?
  #    This is true if the item is identified as instance-based.
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def instance_based?
    @ins_based = MA_INSTANCE_ITEMS_BASE[:regexp_array].any? { |regexp|
      !self.note[regexp].nil? } unless @ins_based
    @ins_based
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Data ID
  #    Essentially just aliases #id, but in case any other script overwrites
  #   or aliases #id, I made it so it just calls it
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def database_id(*args, &block)
    id(*args, &block)
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Equality?
  #    Also returns true if other is an instance item with same data.
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def ==(other)
    super(other) || (other.is_a?(Game_BaseInstanceItem) && super(other.data))
  end
end

# MAIIB_RPG_UsableItem and MAIIB_RPG_EquipItem both inherit MAIIB_RPG_BaseItem
module MAIIB_RPG_UsableItem; include MAIIB_RPG_BaseItem; end
module MAIIB_RPG_EquipItem;  include MAIIB_RPG_BaseItem; end


module RPG
  class Item;   include MAIIB_RPG_UsableItem; end
  class Weapon; include MAIIB_RPG_EquipItem;  end
  class Armor;  include MAIIB_RPG_EquipItem;  end
end

#==============================================================================
# *** DataManager
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  These modifications preserve the
#``````````````````````````````````````````````````````````````````````````````
#  Summary of Changes:
#    aliased methods - init; extract_save_contents; create_game_objects
#    new method - maiib_preserve_old_saves; set_data_to_instance_items
#==============================================================================

class << DataManager
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Initialize
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_inz_2tg6 init
  def init(*args, &block)
    Game_BaseInstanceItem.init_auto_accessors # Initialize automatic
    maiib_inz_2tg6(*args, &block)
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Extract Save Contents
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_extractsave_2jw6 extract_save_contents
  def extract_save_contents(contents, *args, &block)
    maiib_extractsave_2jw6(contents, *args, &block) # Call Original Methods
    maiib_preserve_old_saves
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Create Game Objects
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_creatgameobj_4hi8 create_game_objects
  def create_game_objects(*args, &block)
    maiib_creatgameobj_4hi8(*args, &block) # Call Original Method
    set_data_to_instance_items
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Preserve Save Contents
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def maiib_preserve_old_saves
    $game_system.init_maiib_data unless $game_system.instance_items
    set_data_to_instance_items
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Set Data Instance Items
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def set_data_to_instance_items
    $game_system.instance_items[:item].reset_data_array($data_items)
    $game_system.instance_items[:weapon].reset_data_array($data_weapons)
    $game_system.instance_items[:armor].reset_data_array($data_armors)
    $data_items = $game_system.instance_items[:item]
    $data_weapons = $game_system.instance_items[:weapon]
    $data_armors = $game_system.instance_items[:armor]
  end
end

#==============================================================================
# ** Game_BaseInstanceItem
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  This class holds an instance of an Item, Weapon, or Armor
#==============================================================================

module Game_BaseInstanceItem
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Public Instance Variables
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  attr_writer   :id
  attr_accessor :data_type
  attr_accessor :database_id
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Object Initialization
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def initialize(id, data_type, database_id)
    @id = id
    @data_type = data_type
    @database_id = database_id
    self.class.auto_accessors.each { |method|
      instance_variable_set(:"@#{method}", Marshal.load(Marshal.dump(data.send(method))))
    }
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Get ID
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def id
    ($game_system && $game_system.retrieve_database_id) ? database_id : @id
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Data
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def data
    case data_type
    when :item   then $data_items[database_id]
    when :weapon then $data_weapons[database_id]
    when :armor  then $data_armors[database_id]
    end
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Method Missing
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def method_missing(method, *args, &block)
    if method.to_s[/^(.*?[^=])=$/] && !MA_INSTANCE_ITEMS_BASE[:ignore_accessors].include?(method)
      instance_exec($1.to_sym) { |new_meth|
        eval("def #{new_meth}=(val); @#{new_meth} = val; end;
              def #{new_meth}; @#{new_meth}; end")
      }
      send(method, *args, &block)
    else
      data ? data.send(method, *args, &block) : super(method, *args, &block)
    end
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Equality?
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def ==(other); super(other) || (data && data == other); end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Is A?
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def is_a?(*args, &block)
    super(*args, &block) || (data && data.is_a?(*args, &block))
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Initialize All Automatic Accessors
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def self.init_auto_accessors
    # For all descendants
    ObjectSpace.each_object(Class) { |klass|
      klass.class_eval { klass.auto_accessors.each { |method|
        attr_accessor(method) } } if klass < self
    }
  end
end

module Game_IUsableItem; include Game_BaseInstanceItem; end
module Game_IEquipItem;  include Game_BaseInstanceItem; end

class Game_IItem
  include Game_IUsableItem
  def initialize(*args); super(*args); end
  def self.auto_accessors; [:effects]; end
end
class Game_IWeapon
  include Game_IEquipItem
  def initialize(*args); super(*args); end
  def self.auto_accessors; [:params, :features]; end
end
class Game_IArmor
  include Game_IEquipItem
  def initialize(*args); super(*args); end
  def self.auto_accessors; [:params, :features]; end
end

#==============================================================================
# ** Game_InstanceItems
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  This is a special subclass of an array which holds instance items in a
# separate hashes. It replaces the $data_ classe for items, weapons, and armors
#==============================================================================

class Game_InstanceItems < Array
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Public Instance Variables
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  attr_accessor :instance_items
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Object Initialization
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def initialize(type, data_array = [])
    @instance_items = {}
    @type = type
    @index_start = MA_INSTANCE_ITEMS_BASE[:index_start]
    @last_index = @index_start
    @free_indices = []
    reset_data_array(data_array)
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Type Class
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def type_class
    case @type
    when :item   then Game_IItem
    when :weapon then Game_IWeapon
    when :armor  then Game_IArmor
    end
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Reset Data Array
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def reset_data_array(data_array)
    clear
    for item in data_array do self << item end
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Create Instance
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def create_instance(base_item)
    base_item = self[base_item] if base_item.is_a?(Integer)
    return base_item if base_item.is_a?(Game_BaseInstanceItem) || !base_item.instance_based?
    # Get next free index
    if @free_indices.empty?
      index = @last_index
      @last_index += 1
    else
      index = @free_indices.shift
    end
    @instance_items[index] = type_class.new(index, @type, base_item.id)
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Destroy Instance
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def destroy_instance(index)
    @free_indices.push(index)
    @instance_items.delete(index)
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Retrieve Value from key
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def [](key)
    return key < @index_start ? super(key) : @instance_items[key]
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Set Value to Key
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def []=(key, value)
    key < @index_start ? super(key, value) : @instance_items[key] = value
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Items with ID
  #    This will return an array of all items with the specified ID
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def items_with_id(item_id)
    return [self[item_id]] unless self[item_id].instance_based?
    return @instance_items.values.select { |ins| ins.item_id == item_id }
  end
end

#==============================================================================
# ** Game_System
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  Summary of Changes:
#    new attr_reader - instance_items
#    new attr_accessor - retrieve_database_id
#    aliased method - initialize
#    new method - init_maiib_data
#==============================================================================

class Game_System
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Public Instance Variables
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  attr_reader   :instance_items
  attr_accessor :retrieve_database_id
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Object Initialization
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_intz_2sh7 initialize
  def initialize(*args, &block)
    maiib_intz_2sh7(*args, &block) # Call Original Method
    init_maiib_data
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Initialize Instance Items Base Data
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def init_maiib_data
    @instance_items = {
      item:   Game_InstanceItems.new(:item),
      weapon: Game_InstanceItems.new(:weapon),
      armor:  Game_InstanceItems.new(:armor)   }
    @retrieve_database_id = false
  end
end

#==============================================================================
# ** Game_BaseItem
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  Summary of Changes:
#    aliased method - set_equip; is_item?; is_weapon?; is_armor?
#==============================================================================

class Game_BaseItem
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Is Item/Weapon/Armor?
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  [:Item, :Weapon, :Armor].each { |ic|
    alias_method(:"maiib_is_#{ic.downcase}_8jf9", :"is_#{ic.downcase}?")
    define_method(:"is_#{ic.downcase}?") do |*args|
      send(:"maiib_is_#{ic.downcase}_8jf9", *args) ||
        @class == Kernel.const_get(:"Game_I#{ic}")
    end
  }
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Set Equipment
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_seteqp_1gn8 set_equip
  def set_equip(is_weapon, item_id, *args, &block)
    data = is_weapon ? $data_weapons : $data_armors
    item_id = data.create_instance(item_id).id if data[item_id] &&
      !data[item_id].is_a?(Game_BaseInstanceItem) && data[item_id].instance_based?
    maiib_seteqp_1gn8(is_weapon, item_id, *args, &block) # Call Original Method
  end
end

#==============================================================================
# ** Game_Party
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#  Summary of Changes:
#    aliased methods - initialize; items; weapons; armors; item_container;
#      item_number; members_equip_include?; gain_item; discard_members_equip
#    new methods - instance_item_container; instance_item_type;
#      instance_item_number; gain_instance_item
#==============================================================================

class Game_Party
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Public Instance Variables
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  attr_reader :newest_instance_items
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Object Initialization
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_nitialz_4ld5 initialize
  def initialize(*args, &block)
    @newest_instance_items = []
    maiib_nitialz_4ld5(*args, &block) # Call Original Method
  end
  [:items, :weapons, :armors].each { |method|
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # * Items/Weapons/Armors
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    alias_method(:"maiib_#{method}_3js6", method)
    define_method(method) do |*args|
      result = send(:"maiib_#{method}_3js6", *args) # Call Original Method
      # Delete the database items if they are instance items
      deletables = []
      for i in 0...result.size
        item = result[i]
        next if item.nil?
        break if item.is_a?(Game_BaseInstanceItem)
        deletables << i if item.instance_based?
      end
      deletables.reverse.each { |i| result.delete_at(i) }
      # Sort by Database ID, then ID.
      result.sort {|a, b| (a.database_id <=> b.database_id).nonzero? || a.id <=> b.id }
    end
  }
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Held Instances Of
  #    item : the Item to check
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def held_instances_of(item)
    maiib_items_of_type(item).select { |item2| item2.database_id == item.database_id }
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Item Container
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_itmcontain_7hq9 item_container
  def item_container(item_class, *args, &block)
    return @items   if item_class == Game_IItem
    return @weapons if item_class == Game_IWeapon
    return @armors  if item_class == Game_IArmor
    maiib_itmcontain_7hq9(item_class, *args, &block) # Call Original Method
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Items of Type
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def maiib_items_of_type(item)
    case instance_item_type(item)
    when :item   then items
    when :weapon then weapons
    when :armor  then armors
    else []
    end
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Instance Type
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def instance_item_type(item)
    case item
    when RPG::Item, Game_IItem     then :item
    when RPG::Weapon, Game_IWeapon then :weapon
    when RPG::Armor, Game_IArmor   then :armor
    end
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Gain Item
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_gainitm_3ky7 gain_item
  def gain_item(item, amount, include_equip = false, *args, &block)
    return if amount == 0
    if item.is_a?(MAIIB_RPG_BaseItem) && item.instance_based?
      count = item_number(item)
      amount = [[amount, max_item_number(item) - count].min, 0].max if amount > 0
      gain_instance_item(item, amount, include_equip)
      # Add the new items to the container
      @newest_instance_items.each { |ins|
        maiib_gainitm_3ky7(ins, amount <=> 0, include_equip) }
      # Prepare for database count
      item = item.data if item.is_a?(Game_BaseInstanceItem)
    end
    # Call Original Method and modify the count for database items
    maiib_gainitm_3ky7(item, amount, include_equip, *args, &block)
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Gain Instance Item
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def gain_instance_item(item, amount, include_equip = false)
    @newest_instance_items.clear
    if item.is_a?(Game_BaseInstanceItem)
      if amount > 0 || (item_number(item) > 0 && amount < 0)
        # Add the item
        @newest_instance_items << item
        amount -= amount <=> 0
      end
      # If amount was not 1 or -1, apply method to the database item
      item = item.data
    end
    return unless item
    if amount > 0    # Create new instances
      add_instance_items(item, amount)
    elsif amount < 0 # Find and remove instances of the same database item
      subtract_instance_items(item, -amount, include_equip)
    end
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Lose Instance Item
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def lose_instance_item (item, amount, include_equip = false)
    gain_instance_item(item, -amount, include_equip)
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Add Instance Item
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def add_instance_items(item, amount)
    data_array = $game_system.instance_items[instance_item_type(item)]
    amount.times { @newest_instance_items << data_array.create_instance(item) }
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Subtract Instance Item
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  def subtract_instance_items(item, amount, include_equip = false)
    data_array = $game_system.instance_items[instance_item_type(item)]
    for ins in held_instances_of(item)
      break if amount == 0
      @newest_instance_items << ins
      amount -= 1
    end
    # Rely on discard_members_equip to dispose of equipped items
  end
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # * Discard Members Equip
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  alias maiib_discardequip_3hx7 discard_members_equip
  def discard_members_equip(item, amount, *args, &block)
    if item.is_a?(MAIIB_RPG_BaseItem) && item.instance_based? &&
      !item.is_a?(Game_BaseInstanceItem)
      n = amount
      members.each { |actor|
        while n > 0
          ins = actor.equips.find { |equip| equip.database_id == item.database_id }
          break unless ins
          @newest_instance_items << ins
          actor.discard_equip(ins)
          n -= 1
        end
      }
    else
      # Call Original Method
      maiib_discardequip_3hx7(item, amount, *args, &block)
    end
  end
end

Credit



Support


Please post in this topic at RMRK.net if you have any questions, suggestions, error reports, or comments about this script. Please do not message me privately, as it is more efficient and useful to answer any concerns you have in the topic where others may benefit from our correspondence. Additionally, others might be able to provide you with quicker answers to your questions, as I am occasionally unavailable due to work.

Known Compatibility Issues

This script will likely not work with any other script which purports to do the same thing.

Spoiler for Yanfly's Ace Equip Engine:
There is a small bug in Yanfly's Ace Equip Engine (http://yanflychannel.wordpress.com/rmvxa/gameplay-scripts/ace-equip-engine/). Although it works functionally, when scrolling through instances of the same item, differences in stats will not be shown unless it is the first one you select after looking at some other item. ekomega (http://rmrk.net/index.php/topic,47428.msg539086.html#msg539086) investigated the problem and determined that all you need to do to fix this error is to find line 1098 in Yanfly's script, which should look like this:

Code: [Select]
#--------------------------------------------------------------------------
  # overwrite method: update_help
  #--------------------------------------------------------------------------
  def update_help
    super
    return if @actor.nil?
    return if @status_window.nil?
   return if @last_item == item
    @last_item = item
    temp_actor = Marshal.load(Marshal.dump(@actor))
    temp_actor.force_change_equip(@slot_id, item)
    @status_window.set_temp_actor(temp_actor)
  end

All you need to do is comment out or delete the following line:

Code: [Select]
return if @last_item == item

As far as I know, that should not cause any problems re: lag, since I don't believe that method is called except when the help window is likely to require updating. However, if deleting the line altogether makes you nervous, then you can replace it with:

Code: [Select]
return if @last_item.equal?(item)

Many thanks to ekomega for discovering and fixing this incompatibility.

Demo


This script does nothing on its own, so a demo would have no purpose.

Author's Notes


I actually finished this a few months ago, but then did not do anything with it as I planned to write a few sample scripts before releasing it. I never did that, so I let it stagnate for a while. Unfortunately, this means I am not entirely certain that it was actually finished, but I think it is. Please let me know if anything is missing.

Terms of Use


I adopt RMRK's default Terms of Use (http://rmrk.net/index.php/topic,45481.0.html).
Title: Re: [VXA] Item Instances Base
Post by: D&P3 on December 27, 2012, 09:33:46 AM
So this script is more of a scripters utility rather than something for the regular user?
I don't see myself making any sort of script that would require such a base but nevertheless should the day arrive, I'm sure I'll put it to good use ^-^
Thanks for the base script Modern.
Title: Re: [VXA] Item Instances Base
Post by: modern algebra on December 27, 2012, 03:10:55 PM
Yeah, basically. It is mostly for my own purposes, since I intend to write a few of those scripts sometime in the unspecified future, but I figured it might be valuable to others as well.
Title: Re: [VXA] Item Instances Base
Post by: p3king on February 26, 2013, 08:53:23 PM
Hi modern algebra!

I really like this script!
I´m currently using it together with your Equipment Stat Variance script and it works great.

What I still need for my project however is equip durability so I am currently trying to create this myself with your base. Well actually I think I already did kind of.

I did not yet implement any hud stuff or events to alter the durability though, because it would be nice to know if I am on the right way with this. Would you be so kind and fly over my code?

As I´m still very new to both the Maker and Ruby it would also be really cool if you (and of course everyone else here) could point out which parts I could do better as I´m always interested in using best practice if possible :)

Here´s the script (unfinished, dev-use only):
Code: [Select]
# ---------------------------------------------------------------
# Instanced Equip Durability script by p3king
# -> Using InstanceItemBase script by modern algebra (rmrk.net)
# ---------------------------------------------------------------

if $imported && $imported[:"MA_InstanceItemsBase 1.0.0"]
   
  # The default maximum durability an item has when spawned.
  # Set this in the armor or weapon notebox to customize:
  #
  # \dur_max[200] 
  #     Sets max. durability of this item to 200
  #
  # The value below is taken when you put nothing in the notebox.
  P3_IED_DEFAULT_MAX_DURABILITY = 100
 
  # The default current durability an item has when spawned.
  # Set this in the armor or weapon notebox to customize:
  #
  # \dur_cur[150]
  #     Sets current dur. for this item to 150 (without variance)
  #
  # The value below is taken when you put nothing in the notebox.
  P3_IED_DEFAULT_CUR_DURABILITY = 100 
 
  # The default variance in percent that will be applied to the
  # current durability. This value will be subtracted.
  # Set this in the armor or weapon notebox to customize:
  #
  # \dur_var[50] 
  #     Sets durability variance to 50%. This means the item's
  #     current durability will be decreased by up to 50% (random).
  #
  # The value below is taken when you put nothing in the notebox.
  # Note that currently, when nothing is set in the notebox the
  # item won´t get instanced and will not use durability.
  P3_IED_DEFAULT_VAR_DURABILITY = 30
 
 
 
  P3_IED_REGEXP = {
    "cur" => /\\DUR_CUR\[(\d+)\]/i,
    "max" => /\\DUR_MAX\[(\d+)\]/i,
    "var" => /\\DUR_VAR\[(\d+)\]/i
  }

  P3_IED_REGEXP.each_value do |regexp|
    MA_INSTANCE_ITEMS_BASE[:regexp_array].push(regexp)
  end
 
module Game_IEquipItem
  alias zsiid_initialize initialize
 
  attr_reader :get_durability
  attr_reader :get_max_durability
  attr_reader :is_broken
  attr_reader :is_durability_enabled
  attr_reader :change_durability
 
  # check for notebox vars
  def initialize(*args)
    zsiid_initialize(*args)
   
    @durability_enabled = false
    @durability = self.note[P3_IED_REGEXP["cur"]].nil? ? -1 : $1.to_i
    @durability_max = self.note[P3_IED_REGEXP["max"]].nil? ? -1 : $1.to_i
    dur_var_perc = self.note[P3_IED_REGEXP["var"]].nil? ? -1 : $1.to_i
   
    if @durability > -1 or @durability_max > -1 or dur_var_perc > -1
      @durability_enabled = true
      set_default_durability
      apply_variable_durability
      # print(self.name + " > max: " + @durability_max.to_s + ", cur: " + @durability.to_s + "\n");
    end
  end
 
  # get item dur.
  def get_durability
    @durability
  end
 
  # get item max dur.
  def get_max_durability
    @durability_max
  end
 
  # return if item is broken
  def is_broken?
    use_durability? and @durability == 0
  end
 
  # return if item uses durability function
  def is_durability_enabled?
    @durability_enabled
  end
 
  # change durability on item
  def change_durability(amount)
    if @durability_enabled
      @durability = @durability + amount
      anti_manaburn
    end
  end
 
  # put default values
  def set_default_durability
    @durability_max = P3_IED_DEFAULT_MAX_DURABILITY if @durability_max == -1
    @durability = @durability_max if @durability == -1
       
  end
 
  # apply variable durability
  def apply_variable_durability
    var_perc = self.note[P3_IED_REGEXP["var"]].nil? ? P3_IED_DEFAULT_VAR_DURABILITY : $1.to_i
    if var_perc != 0
      rand = Random.new(Time.now.to_i)
      random_val = 1.0 - (rand(var_perc).to_f / 100.0)
      @durability = (@durability * random_val).to_i
      anti_manaburn
    end   
  end
 
  # prevent values going over min / max
  def anti_manaburn
    @durability = @durability_max if @durability > @durability_max
    @durability = 0 if @durability < 0
  end
 
end

else
  # sentence and structure stolen from modern algebra
  msgbox("Item Durability could not be installed because you either do not have Instance Items Base or because you have that script placed below the Item Durability script.")
end
Title: Re: [VXA] Item Instances Base
Post by: Fall From Eden on February 26, 2013, 11:04:47 PM
Well, I'm not Modern Algebra, but I can help you a little bit with the code that you have. :)

Firstly, you really don't need the get_* methods. Instead of defining a method named "get_durability", all you need is an attr_reader for the @durability instance variable. As it is, you have readers for methods, which isn't what you want to do. Readers and accessors make use of instance variables, not methods -- so defining the "get_durability" method and then creating a reader for "get_durability" is redundant and your hand-written "get_durability" method overrides the method generated for the (non-existent) @get_durability instance variable that your attr_reader created.

I'd also change the logic for your "is_broken?" method a little bit. Right now, you're checking if durability is in use for the item (good), and if its durability is equal to zero (not so good) -- so what if the durability is less than zero? Suddenly, it's not broken, which would be a bug, I think. :) Also, I don't see a "use_durability?" method anywhere, so I'm assuming you meant to reference the "is_durability_enabled?" method, which is really just checking if the @durability_enabled instance variable has a truth-y value. Why not check @durability_enabled directly?

In your "apply_variable_durability", you create a local variable named "rand" with a seeded random number, which is also kind of redundant: the Kernel#rand method already does this.

Here's a modified version so that you can hopefully see what I'm talking about with all of this:

Code: [Select]
if $imported && $imported[:"MA_InstanceItemsBase 1.0.0"]
  P3_IED_DEFAULT_MAX_DURABILITY = 100
  P3_IED_DEFAULT_CUR_DURABILITY = 100 
  P3_IED_DEFAULT_VAR_DURABILITY = 30
 
  # Note: I'd use symbols as keys here, not strings.
  P3_IED_REGEXP = {
    :cur => /\\DUR_CUR\[(\d+)\]/i,
    :max => /\\DUR_MAX\[(\d+)\]/i,
    :var => /\\DUR_VAR\[(\d+)\]/i
  }

  P3_IED_REGEXP.each_value do |regexp|
    MA_INSTANCE_ITEMS_BASE[:regexp_array].push(regexp)
  end
 
  module Game_IEquipItem
    alias :zsiid_initialize :initialize
    attr_reader :durability, :max_durability, :durability_enabled
   
    def initialize(*args)
      zsiid_initialize(*args)
      # Note: technically, you could leave this out here, actually. If the
      # variable is not instantiated, it evaluates to nil, which would work
      # fine for conditionals.
      @durability_enabled = false
      # Note: I removed your checks for nil here because both nil and false are
      # automatically evaluated to false; no need to explicitly check for it.
      @durability = self.note[P3_IED_REGEXP[:cur]] ? $1.to_i : -1
      @durability_max = self.note[P3_IED_REGEXP[:max]] ? $1.to_i : -1
      dur_var_perc = self.note[P3_IED_REGEXP[:var]] ? $1.to_i : -1
     
      if @durability > -1 or @durability_max > -1 or dur_var_perc > -1
        @durability_enabled = true
        set_default_durability
        apply_variable_durability
      end
    end
   
    def is_broken?
      @durability_enabled and @durability <= 0
    end
   
    def change_durability(amount)
      return unless @durability_enabled
      # Note: this is shorthand for what you were doing before.
      @durability += amount
      anti_manaburn
    end
   
    def set_default_durability
      @durability_max = P3_IED_DEFAULT_MAX_DURABILITY if @durability_max == -1
      @durability = @durability_max if @durability == -1
    end
   
    def apply_variable_durability
      var_perc = self.note[P3_IED_REGEXP[:var]] ? $1.to_i : P3_IED_DEFAULT_VAR_DURABILITY
      if var_perc != 0
        # Note: no need to add .to_f to rand in my opinion.
        random_val = 1.0 - (rand(var_perc) / 100.0)
        @durability = (@durability * random_val).to_i
        anti_manaburn
      end   
    end
   
    # Note: I'd rename this method to something like "clamp_durability",
    # personally... describe what it does better.
    def anti_manaburn
      @durability = @durability_max if @durability > @durability_max
      @durability = 0 if @durability < 0
    end
  end
else
  msgbox("Item Durability could not be installed because you either do not have Instance Items Base or because you have that script placed below the Item Durability script.")
end

Also, be aware of the fact that I wrote these changes really quickly and haven't tested them, so if something doesn't work, just assume that I put a problem in there intentionally to help you learn! :D
Title: Re: [VXA] Item Instances Base
Post by: p3king on February 26, 2013, 11:49:45 PM
Wow thanks alot for your effort, that helps alot.

I read about the accessors earlier and I already tried them but I probably did something wrong.
Also, coming from php I´m kind of used to define my getters and setters, I need to stop that :)

I let is_broken? only check for zero because in theory it should never be able to get below.. but you´re right that it´s better checking again. use_durability was the name of durability_enabled before i renamed it.. but not the accessor.

That local rand var you found was a line I forgot to remove. I didn´t know of rand() and google first led me to Random.new.

Regarding the anti_manaburn method, I just couldn´t think of a fitting name in that moment and that was the first thing coming to my mind :D

I´ll update and test my script later tomorrow, now it´s time for me to sleep ;)
Title: Re: [VXA] Item Instances Base
Post by: Fall From Eden on February 27, 2013, 01:07:16 AM
Well, it looks like you were trying to use accessors as public / private declarations, which isn't what they are. For now, think of them as syntactic sugar for writing your own getter and setter methods (interesting tidbit, though: they're actually faster when executed than methods are).

And yes, I noticed that in theory, the durability should never get below zero -- but don't always rely on your design that way. It's safer to be sane than to rely on things going as planned. ;)