#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# Resource Tester by Blizzard
# Version: 1.05b
# Type: Time-and-Space saving Utility
# Date: 16.8.2007
# Date v1.0b: 17.8.2007
# Date v1.01b: 17.9.2007
# Date v1.02b: 27.3.2008
# Date v1.05b: 3.4.2008
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# 
# Compatibility:
# 
#   Compatible with anything, even the SDK 2.x (because it gets overriden).
#   Will not search for script-required resources.
# 
# 
# Features:
# 
#   - finds all resources used in your game
#   - incredible speed: searching trough a game with over 300 maps is a matter
#     of just seconds
#   - optional feature to list all actors, skills, weapons, armors, items,
#     enemies, troops, states, animations, tilesets and common events that are
#     never being used anywhere in the game and only log all the used data
#   
# new in 1.0b:
#   - fixed the typing mistake that would cause adding the skill with ID 1
#     into the logged skills instead of the real skill when the script was
#     processing a "Force Action" event command
#   - rewritten conditions using classic syntax to avoid RGSS conditioning bug
#   - now beta
# 
# new in 1.01b:
#   - fixed glitch where changing a Battle End ME would be logged as SE
#   - improved coding
# 
# new in 1.02b:
#   - fixed bug caused by a typing mistake
# 
# new in 1.05b:
#   - fixed other potential bugs
# 
# 
# Explanation:
# 
#   This script will search through your database and your maps for resources
#   that are being used. It will create a list of the resources used in your
#   game.
# 
# 
# How to use:
# 
#   Put this script over all other scripts in the editor. Set the player's
#   starting position and starting party. Set everything up as if you were
#   about to set up the game to be playable and run your game once. Several
#   text files will be created during that time. Now you can remove the script
#   from the editor and open the text files. They will include lists of
#   resources that your game uses, but the file names will not have extensions.
#   Note that resources that are "hard-coded" into your scripts, cannot be
#   located by this script.
# 
# 
# Configuration:
# 
#   Set up following constants to suit your needs:
# 
#   DEEP_TESTING - When this value is false, the script will just iterate
#                  through your database and maps and find out which resources
#                  are being accessed. When this values is true, the script
#                  will find out which data isn't used first and then create a
#                  list of resources used by data that is being used. It will
#                  also create a file of which data isn't used. (i.e. a skill
#                  that no one uses in the entire game)
# 
#
# How the script works:
# 
# - when DEEP_TESTING is false
# 
#    1. The script will open all your maps and gather a list of all events. It
#       will iterate through all the events' codes and find resources that are
#       being accessed (i.e. playing a sound, changing windowskin, etc.).
#    2. It will do the same with all common events and battle events.
#    3. It will open all actor, skill, weapon, armor, item, enemy, animations,
#       tilesets and system data.
#    4. It will iterate through all of them and find all resources ever being
#       accessed.
#  
# - when DEEP_TESTING is true
# 
#    1. Your system data will be logged and the player's starting map ID will
#       be accesssed. This map will be logged as "pending".
#         -> logs audio FILES, graphic FILES from system and all actors
#    2. Pending maps are being accessed, their properties are being logged:
#         -> logs new tilesets, troops, event pages and audio FILES
#       Maps are logged as completed.
#    3. Pending troops will be checked:
#         -> logs new enemies and battle events
#       Troops are logged as completed.
#    4. Pending enemies will be checked:
#         -> logs new skills, weapons, armors items, animations and
#            battler FILES
#       Enemies are logged as completed.
#    5. Pending actors will be checked:
#         -> logs classes, weapons, armors, character FILES and battler FILES
#       Actors are logged as completed.
#    6. Pending classes will be checked:
#         -> logs new skills
#       Classes are logged as completed.
#    7. Pending skills will be checked:
#         -> logs new event pages, states, animations, icon FILES and
#            audio FILES
#       Skills are logged as completed.
#    8. Pending items will be checked:
#         -> logs new event pages, states, animations, icon FILES and
#            audio FILES
#       Items are logged as completed.
#    9. Pending events are getting tested on following commands:
#       - call common event     -> logs new event pages
#       - change items          -> logs new items
#       - change weapons        -> logs new weapons
#       - change armors         -> logs new armors
#       - change party members  -> logs new actors and classes
#       - change windowskin     -> logs new windowskin FILES
#       - change battle audio   -> logs new audio FILES
#       - transfer player       -> logs new maps
#       - change map settings   -> logs new panorama/fog/battleback FILES
#       - show animation        -> logs new animations
#       - move event            -> logs new character FILES
#       - execute transition    -> logs new transition FILES
#       - show picture          -> logs new picture FILES
#       - play any audio file   -> logs new audio FILES
#       - battle processing     -> logs new troops
#       - shop processing       -> logs new weapons, armors and items
#       - name processing       -> logs new actors and classes
#       - change actor state    -> logs new actors, classes and states
#       - change actor skill    -> logs new actors, classes and skills
#       - change actor class    -> logs new actors and classes
#       - change actor graphic  -> logs new actors, classes and character FILES
#       - change enemy state    -> logs new stantes
#       - enemy transform       -> logs new enemies
#       - show battle animation -> logs new animations
#       - force action          -> logs new skills
#       Also logs new character FILES if the page is a normal event page.
#       Common Events are logged as completed.
#   10. Returns to step 2 if there are any pending maps, troops, enemies,
#       actors, classes, skills, items, or event pages.
#   11. Pending weapons will be checked:
#         -> logs new states, animations and icon FILES
#   12. Pending armors will be checked:
#         -> logs new states and icon FILES
#   13. Pending states and zero-hp states will be checked:
#         -> logs new states and animations
#   14. Pending animations will be checked:
#         -> logs new animation FILES and audio FILES
#   15. Pending tilesets will be checked:
#         -> logs new tileset FILES, autotile FILES, panorama FILES, fog FILES
#            and battleback FILES
#   16. Saves a list of resources and saves a list of unused actors, classes,
#       skills, weapons, armors, items, enemies, troops, states, animations, 
#       tilesets, common events and maps.
# 
# 
# Final Notes:
# 
# - If you use a system where you have more than one start location for the
#   game (i.e. a separated mode with a different storyline) and want to turn
#   DEEP_TESTING on during the testing, then just put in the starting map a
#   dummy event with teleport commands to the starting maps of the other game
#   modes.
#   
# - If you are using DEEP_TESTING, be careful, since teleport commands that
#   use a variable to determine the new map ID can't be tested. Do as you
#   would with with different game modes: Create dummy events.
#   
# - Auto-start and parallel process common events will be automatically logged
#   from the beginning.
#   
# - The best moment to use this script is just before you are going to compile
#   and encrypt the game data. If you want to use it for resource cleaning,
#   turn off DEEP_TESTING.
# 
# 
# If you find any bugs, please report them here:
# http://www.chaosproject.co.nr
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

DEEP_TESTING = true

#==============================================================================
# Main
#==============================================================================

begin
  time = Time.now
  _actors     = load_data('Data/Actors.rxdata')
  _classes    = load_data('Data/Classes.rxdata')
  _skills     = load_data('Data/Skills.rxdata')
  _items      = load_data('Data/Items.rxdata')
  _weapons    = load_data('Data/Weapons.rxdata')
  _armors     = load_data('Data/Armors.rxdata')
  _enemies    = load_data('Data/Enemies.rxdata')
  _states     = load_data('Data/States.rxdata')
  _animations = load_data('Data/Animations.rxdata')
  _tilesets   = load_data('Data/Tilesets.rxdata')
  _pages      = load_data('Data/CommonEvents.rxdata')
  _troops     = load_data('Data/Troops.rxdata')
  _maps       = load_data('Data/MapInfos.rxdata')
  _system     = load_data('Data/System.rxdata')
  bgm, bgs, se, me, animations, autotiles, battlebacks, battlers, characters,
  fogs, gameovers, icons, panoramas, pictures, tilesets, titles, transitions,
  windowskins = [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
                [], []
  windowskins.push(_system.windowskin_name)
  titles.push(_system.title_name)
  gameovers.push(_system.gameover_name)
  transitions.push(_system.battle_transition)
  bgm.push(_system.title_bgm.name)
  me.push(_system.battle_end_me.name, _system.gameover_me.name)
  se.push(_system.cursor_se.name, _system.decision_se.name,
          _system.cancel_se.name, _system.buzzer_se.name,
          _system.equip_se.name, _system.shop_se.name, _system.save_se.name,
          _system.load_se.name, _system.battle_start_se.name,
          _system.escape_se.name, _system.actor_collapse_se.name,
          _system.enemy_collapse_se.name)
  if DEEP_TESTING
    p_maps, p_troops, p_enemies, p_actors, p_classes, p_skills, p_items,
    p_pages, p_weapons, p_armors, p_states, p_animations, p_tilesets, c_maps,
    c_troops, c_enemies, c_actors, c_classes, c_skills, c_items, c_pages = 
    [_system.start_map_id], [], [], _system.party_members.clone, [], [], [],
    [], [], [], [], [], [], [], [], [], [], [], [], [], []
    (_pages - [nil]).each {|page|
        if [1, 2].include?(page.trigger)
          p_pages.push(page)
          c_pages.push(page.id)
        end}
    p_maps.delete(0)
    raise 'Error! There is no start map defined!' if p_maps.size == 0
    loop do
      Graphics.update
      p_maps.clone.each {|id|
          map = load_data(sprintf("Data/Map%03d.rxdata", id))
          map.events.values.each {|i| p_pages += i.pages}
          p_tilesets.push(map.tileset_id)
          map.encounter_list.each {|i|
              p_troops.push(i) unless (c_troops | p_troops).include?(i)}
          bgm.push(map.bgm.name) if map.autoplay_bgm
          bgs.push(map.bgs.name) if map.autoplay_bgs
          c_maps.push(p_maps.shift)}
      p_troops.clone.each {|id|
          p_pages += _troops[id].pages
          _troops[id].members.each {|member|
              unless (c_enemies | p_enemies).include?(member.enemy_id)
                p_enemies.push(member.enemy_id)
              end}
          c_troops.push(p_troops.shift)}
      p_enemies.clone.each {|id|
          _enemies[id].actions.each {|i|
              unless (c_skills | p_skills).include?(i.skill_id)
                p_skills.push(i.skill_id)
              end}
          if _enemies[id].treasure_prob > 0
            if _enemies[id].item_id > 0
              unless (c_items | p_items).include?(_enemies[id].item_id)
                p_items.push(_enemies[id].item_id)
              end
            elsif _enemies[id].weapon_id > 0
              unless p_weapons.include?(_enemies[id].weapon_id)
                p_weapons.push(_enemies[id].weapon_id)
              end
            elsif _enemies[id].armor_id > 0
              unless p_armors.include?(_enemies[id].armor_id)
                p_armors.push(_enemies[id].armor_id)
              end
            end
          end
          [_enemies[id].animation1_id, _enemies[id].animation2_id].each {|i|
              p_animations.push(i) unless i == 0 || p_animations.include?(i)}
          battlers.push(_enemies[id].battler_name)
          c_enemies.push(p_enemies.shift)}
      p_actors.clone.each {|id|
          actor = _actors[id]
          unless _actors[id].weapon_id == 0 || p_weapons.include?(_actors[id].weapon_id)
            p_weapons.push(_actors[id].weapon_id)
          end
          [_actors[id].armor1_id, _actors[id].armor2_id,
          _actors[id].armor3_id, _actors[id].armor4_id].each {|i|
              p_armors.push(i) unless i == 0 || p_armors.include?(i)}
          unless (c_classes | p_classes).include?(_actors[id].class_id)
            p_classes.push(_actors[id].class_id)
          end
          characters.push(_actors[id].character_name)
          battlers.push(_actors[id].battler_name)
          c_actors.push(p_actors.shift)}
      p_classes.clone.each {|id|
          _classes[id].learnings.each {|i|
              unless (c_skills | p_skills).include?(i.skill_id)
                p_skills.push(i.skill_id)
              end}
          c_classes.push(p_classes.shift)}
      p_skills.clone.each {|id|
          unless _skills[id].common_event_id == 0 ||
              c_pages.include?(_skills[id].common_event_id)
            p_pages.push(_pages[_skills[id].common_event_id])
            c_pages.push(_skills[id].common_event_id)
          end
          _skills[id].plus_state_set.each {|i|
              p_states.push(i) unless p_states.include?(i)}
          [_skills[id].animation1_id, _skills[id].animation2_id].each {|i|
              p_animations.push(i) unless i == 0 || p_animations.include?(i)}
          icons.push(_skills[id].icon_name)
          se.push(_skills[id].menu_se.name)
          c_skills.push(p_skills.shift)}
      p_items.clone.each {|id|
          unless _items[id].common_event_id == 0 ||
              c_pages.include?(_items[id].common_event_id)
            p_pages.push(_pages[_items[id].common_event_id])
            c_pages.push(_items[id].common_event_id)
          end
          _items[id].plus_state_set.each {|i|
              p_states.push(i) unless p_states.include?(i)}
          [_items[id].animation1_id, _items[id].animation2_id].each {|i|
              p_animations.push(i) unless i == 0 || p_animations.include?(i)}
          icons.push(_items[id].icon_name)
          se.push(_items[id].menu_se.name)
          c_items.push(p_items.shift)}
      p_pages.clone.each {|page|
          characters.push(page.graphic.character_name) if page.is_a?(RPG::Event::Page)
          page.list.each {|command|
              case command.code
              when 117
                p_pages.push(_pages[command.parameters[0]])
                c_pages.push(command.parameters[0])
              when 126
                if command.parameters[1] == 0 && command.parameters[2] == 0 &&
                    !(c_items | p_items).include?(command.parameters[0])
                  p_items.push(command.parameters[0])
                end
              when 127
                if command.parameters[1] == 0 && command.parameters[2] == 0 &&
                    !p_weapons.include?(command.parameters[0])
                  p_weapons.push(command.parameters[0])
                end
              when 128
                if command.parameters[1] == 0 && command.parameters[2] == 0 &&
                    !p_armors.include?(command.parameters[0])
                  p_armors.push(command.parameters[0])
                end
              when 129
                if command.parameters[1] == 0 &&
                    !(c_actors | p_actors).include?(command.parameters[0])
                  p_actors.push(command.parameters[0])
                end
              when 131 then windowskins.push(command.parameters[0])
              when 132 then bgm.push(command.parameters[0].name)
              when 133 then me.push(command.parameters[0].name)
              when 201
                if command.parameters[0] == 0 &&
                    !(c_maps | p_maps).include?(command.parameters[1])
                  p_maps.push(command.parameters[1])
                end
              when 204
                case command.parameters[0]
                when 0 then panoramas.push(command.parameters[1])
                when 1 then fogs.push(command.parameters[1])
                when 2 then battlebacks.push(command.parameters[1])
                end
              when 207
                unless p_animations.include?(command.parameters[1])
                  p_animations.push(command.parameters[1])
                end
              when 209
                command.parameters[1].list.each {|move|
                    characters.push(move.parameters[0]) if move.code == 41}
              when 222 then transitions.push(command.parameters[0])
              when 231 then pictures.push(command.parameters[1])
              when 241 then bgm.push(command.parameters[0].name)
              when 245 then bgs.push(command.parameters[0].name)
              when 249 then me.push(command.parameters[0].name)
              when 250 then se.push(command.parameters[0].name)
              when 301
                unless c_troops.include?(command.parameters[0])
                  p_troops.push(command.parameters[0])
                end
              when 302
                goods = [command.parameters]
                index = page.list.index(command)
                loop do
                  index += 1
                  if page.list[index].code == 605
                    goods.push(page.list[index].parameters)
                  else
                    break
                  end
                end
                goods.each {|good|
                    case good[0]
                    when 0
                      unless (c_items | p_items).include?(good[1])
                        p_items.push(good[1])
                      end
                    when 1
                      p_weapons.push(good[1]) unless p_weapons.include?(good[1])
                    when 2
                      p_armors.push(good[1]) unless p_armors.include?(good[1])
                    end}
              when 303
                unless (c_actors | p_actors).include?(command.parameters[0])
                  p_actors.push(command.parameters[0])
                end
              when 313
                if command.parameters[1] == 0 &&
                    !p_states.include?(command.parameters[2])
                  p_states.push(command.parameters[2]) 
                end
              when 318
                unless (c_actors | p_actors).include?(command.parameters[0])
                  p_actors.push(command.parameters[0])
                end
                if command.parameters[1] == 0 &&
                    !(c_skills | p_skills).include?(command.parameters[2])
                  p_skills.push(command.parameters[2]) 
                end
              when 321
                unless (c_actors | p_actors).include?(command.parameters[0])
                  p_actors.push(command.parameters[0])
                end
                unless (c_classes | p_classes).include?(command.parameters[1])
                  p_classes.push(command.parameters[1])
                end
              when 322
                unless (c_actors | p_actors).include?(command.parameters[0])
                  p_actors.push(command.parameters[0])
                end
                characters.push(command.parameters[1])
                battlers.push(command.parameters[3])
              when 333
                if command.parameters[1] == 0 &&
                    !p_states.include?(command.parameters[2])
                  p_states.push(command.parameters[2]) 
                end
              when 336
                unless (c_enemies | p_enemies).include?(command.parameters[1])
                  p_enemies.push(command.parameters[1])
                end
              when 337
                unless p_animations.include?(command.parameters[2])
                  p_animations.push(command.parameters[2])
                end
              when 339
                if command.parameters[2] != 0 &&
                    !(c_skills | p_skills).include?(command.parameters[3])
                  p_skills.push(command.parameters[3]) 
                end
              end}
          p_pages.shift}
      all = [p_maps, p_troops, p_enemies, p_actors, p_classes, p_skills,
             p_items, p_pages]
      break if all.all? {|ary| ary.size == 0}
    end
    p_weapons.each {|id|
        _weapons[id].plus_state_set.each {|i|
            p_states.push(i) unless p_states.include?(i)}
        [_weapons[id].animation1_id, _weapons[id].animation2_id].each {|i|
            p_animations.push(i) unless i == 0 || p_animations.include?(i)}
        icons.push(_weapons[id].icon_name)}
    p_armors.each {|id|
        unless _armors[id].auto_state_id == 0 ||
            p_states.include?(_armors[id].auto_state_id)
          p_states.push(_armors[id].auto_state_id)
        end
        icons.push(_armors[id].icon_name)}
    (_states - [nil]).each {|s|
        p_states.push(s.id) if s.zero_hp && !p_states.include?(s.id)}
    size = 0
    while size != p_states.size
      size = p_states.size
      p_states.clone.each {|id| _states[id].plus_state_set.each {|i|
              p_states.push(i) unless p_states.include?(i)}}
    end
    p_states.each {|id|
        unless _states[id].animation_id == 0 ||
            p_animations.include?(_states[id].animation_id)
          p_animations.push(_states[id].animation_id)
        end}
    p_animations.each {|id|
        animations.push(_animations[id].animation_name)
        _animations[id].timings.each {|timing| se.push(timing.se.name)}}
    p_tilesets.each {|id|
        tilesets.push(_tilesets[id].tileset_name)
        fogs.push(_tilesets[id].fog_name)
        panoramas.push(_tilesets[id].panorama_name)
        battlebacks.push(_tilesets[id].battleback_name)
        (0..6).each {|i| autotiles.push(_tilesets[id].autotile_names[i])}}
    all = [[_maps, _maps.keys - c_maps],
    [_troops, (1..._troops.size).to_a - c_troops],
    [_enemies, (1..._enemies.size).to_a - c_enemies],
    [_actors, (1..._actors.size).to_a - c_actors],
    [_classes, (1..._classes.size).to_a - c_classes],
    [_skills, (1..._skills.size).to_a - c_skills],
    [_items, (1..._items.size).to_a - c_items],
    [_pages, (1..._pages.size).to_a - c_pages],
    [_weapons, (1..._weapons.size).to_a - p_weapons],
    [_armors, (1..._armors.size).to_a - p_armors],
    [_states, (1..._states.size).to_a - p_states],
    [_animations, (1..._animations.size).to_a - p_animations],
    [_tilesets, (1..._tilesets.size).to_a - p_tilesets]]
    datas = ['Maps', 'Troops', 'Enemies', 'Actors', 'Classes', 'Skills',
             'Items', 'Common Events', 'Weapons', 'Armors', 'States',
             'Animations', 'Tilesets']
    file = File.open('Unused.txt', 'wb')
    datas.each_index {|i|
        file.write("\r\n  --::>> Unused #{datas[i]} <<::--\r\n\r\n")
        (all[i][1] | all[i][1]).each {|one|
            file.write("#{sprintf('%03d', one)}  :  #{all[i][0][one].name}\r\n")}
        file.write("\r\n")}
  else
    (_actors - [nil]).each {|actor|
        characters.push(actor.character_name)
        battlers.push(actor.battler_name)}
    (_skills + _items - [nil]).each {|object|
        icons.push(object.icon_name)
        se.push(object.menu_se.name)}
    (_weapons + _armors - [nil]).each {|object| icons.push(object.icon_name)}
    (_enemies - [nil]).each {|enemy| battlers.push(enemy.battler_name)}
    (_animations - [nil]).each {|animation|
        animations.push(animation.animation_name)
        animation.timings.each {|timing| se.push(timing.se.name)}}
    (_tilesets - [nil]).each {|tileset|
        tilesets.push(tileset.tileset_name)
        fogs.push(tileset.fog_name)
        panoramas.push(tileset.panorama_name)
        battlebacks.push(tileset.battleback_name)
        (0..6).each {|i| autotiles.push(tileset.autotile_names[i])}}
    (_troops - [nil]).each {|troop| _pages += troop.pages}
    _keys = _maps.keys
    _keys.each_index {|i|
        Graphics.update if i % 20 == 0
        events = load_data(sprintf("Data/Map%03d.rxdata", _keys[i])).events
        events.values.each {|event| _pages += event.pages}}
    _pages -= [nil]
    _pages.each_index {|i|
        Graphics.update if i % 100 == 0
        if _pages[i].is_a?(RPG::Event::Page)
          characters.push(_pages[i].graphic.character_name)
        end
        _pages[i].list.each {|command|
            case command.code
            when 131 then windowskins.push(command.parameters[0])
            when 132 then bgm.push(command.parameters[0].name)
            when 133 then se.push(command.parameters[0].name)
            when 204
              case command.parameters[0]
              when 0 then panoramas.push(command.parameters[1])
              when 1 then fogs.push(command.parameters[1])
              when 2 then battlebacks.push(command.parameters[1])
              end
            when 209
              command.parameters[1].list.each {|move|
                  characters.push(move.parameters[0]) if move.code == 41}
            when 222 then transitions.push(command.parameters[0])
            when 231 then pictures.push(command.parameters[1])
            when 241 then bgm.push(command.parameters[0].name)
            when 245 then bgs.push(command.parameters[0].name)
            when 249 then me.push(command.parameters[0].name)
            when 250 then se.push(command.parameters[0].name)
            when 322
              characters.push(command.parameters[1])
              battlers.push(command.parameters[3])
            end}}
  end
  folders = ['Audio/BGM', 'Audio/BGS', 'Audio/ME', 'Audio/SE', 'Animations',
             'Autotiles', 'Battlebacks', 'Battlers', 'Characters', 'Fogs',
             'Gameovers', 'Icons', 'Panoramas', 'Pictures', 'Tilesets',
             'Titles', 'Transitions', 'Windowskins']
  all = [bgm, bgs, me, se, animations, autotiles, battlebacks, battlers,
         characters, fogs, gameovers, icons, panoramas, pictures, tilesets,
         titles, transitions, windowskins]
  all.each {|ary| ary.delete(''); ary.sort!}
  file = File.open('Tested Resources.txt', 'wb')
  folders.each_index {|i|
      file.write("\r\n  ---::{ #{folders[i]} }::---\r\n\r\n")
      (all[i] | all[i]).each {|one| file.write("#{folders[i]}/#{one}\r\n")}
      file.write("\r\n")}
  file.close
  p "Resource Test successful! Time needed: #{Time.now-time} seconds"
rescue Errno::ENOENT
  filename = $!.message.sub('No such file or directory - ', '')
  print("File -- #{filename} -- is missing.")
end

exit
