From 1977bd866c679229191c82b2f1378cc22ef72b6e Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Sun, 31 Mar 2024 23:19:31 +0100 Subject: [PATCH] Anim Editor: added filter text box to selection screen, disabled animations are listed in red --- .../Control elements/008_List.rb | 22 ++ .../903_Anim Compiler/001_Anim compiler.rb | 24 ++- .../904_Anim Editor/001_AnimationEditor.rb | 64 +++--- .../904_Anim Editor/010_AnimationSelector.rb | 189 ++++++++++++------ .../Anim Editor elements/001_Canvas.rb | 12 +- 5 files changed, 208 insertions(+), 103 deletions(-) diff --git a/Data/Scripts/801_UI controls/Control elements/008_List.rb b/Data/Scripts/801_UI controls/Control elements/008_List.rb index 0c2288d1c..bbdbae989 100644 --- a/Data/Scripts/801_UI controls/Control elements/008_List.rb +++ b/Data/Scripts/801_UI controls/Control elements/008_List.rb @@ -146,10 +146,32 @@ class UIControls::List < UIControls::BaseControl ) end txt = (val.is_a?(Array)) ? val[1] : val + text_color = TEXT_COLOR + if txt[/^\\c\[([0-9]+)\]/i] + text_colors = [ + [ 0, 112, 248], [120, 184, 232], # 1 Blue + [232, 32, 16], [248, 168, 184], # 2 Red + [ 96, 176, 72], [174, 208, 144], # 3 Green + [ 72, 216, 216], [168, 224, 224], # 4 Cyan + [208, 56, 184], [232, 160, 224], # 5 Magenta + [232, 208, 32], [248, 232, 136], # 6 Yellow + [160, 160, 168], [208, 208, 216], # 7 Gray + [240, 240, 248], [200, 200, 208], # 8 White + [114, 64, 232], [184, 168, 224], # 9 Purple + [248, 152, 24], [248, 200, 152], # 10 Orange + MessageConfig::DARK_TEXT_MAIN_COLOR, + MessageConfig::DARK_TEXT_SHADOW_COLOR, # 11 Dark default + MessageConfig::LIGHT_TEXT_MAIN_COLOR, + MessageConfig::LIGHT_TEXT_SHADOW_COLOR # 12 Light default + ] + self.bitmap.font.color = Color.new(*text_colors[2 * ($1.to_i - 1)]) + txt = txt.gsub(/^\\c\[[0-9]+\]/i, "") + end draw_text(self.bitmap, @interactions[i].x + TEXT_PADDING_X, @interactions[i].y + TEXT_OFFSET_Y - (@top_row * ROW_HEIGHT), txt) + self.bitmap.font.color = TEXT_COLOR end end diff --git a/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb b/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb index a31e7991f..ab2f6fe5f 100644 --- a/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb +++ b/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb @@ -154,25 +154,33 @@ module Compiler when "Target" then particle[:graphic] = "TARGET" end end - # Ensure that particles don't have a focus involving a user, and the - # animation doesn't play a user's cry, if the animation itself doesn't - # involve a user + # If the animation doesn't involve a user, ensure that particles don't + # have a focus/graphic that involves a user, and that the animation + # doesn't play a user's cry if hash[:no_user] if GameData::Animation::FOCUS_TYPES_WITH_USER.include?(particle[:focus]) raise _INTL("Particle \"{1}\" can't have a \"Focus\" that involves a user if property \"NoUser\" is set to true.", - particle[:name]) + "\n" + FileLineData.linereport + particle[:name]) + "\n" + FileLineData.linereport + end + if ["USER", "USER_OPP", "USER_FRONT", "USER_BACK"].include?(particle[:graphic]) + raise _INTL("Particle \"{1}\" can't have a \"Graphic\" that involves a user if property \"NoUser\" is set to true.", + particle[:name]) + "\n" + FileLineData.linereport end if particle[:name] == "SE" && particle[:user_cry] && !particle[:user_cry].empty? raise _INTL("Animation can't play the user's cry if property \"NoUser\" is set to true.") + "\n" + FileLineData.linereport end end - # Ensure that particles don't have a focus involving a target, and the - # animation doesn't play a target's cry, if the animation itself doesn't - # involve a target + # If the animation doesn't involve a target, ensure that particles don't + # have a focus/graphic that involves a target, and that the animation + # doesn't play a target's cry if hash[:no_target] if GameData::Animation::FOCUS_TYPES_WITH_TARGET.include?(particle[:focus]) raise _INTL("Particle \"{1}\" can't have a \"Focus\" that involves a target if property \"NoTarget\" is set to true.", - particle[:name]) + "\n" + FileLineData.linereport + particle[:name]) + "\n" + FileLineData.linereport + end + if ["TARGET", "TARGET_OPP", "TARGET_FRONT", "TARGET_BACK"].include?(particle[:graphic]) + raise _INTL("Particle \"{1}\" can't have a \"Graphic\" that involves a target if property \"NoTarget\" is set to true.", + particle[:name]) + "\n" + FileLineData.linereport end if particle[:name] == "SE" && particle[:target_cry] && !particle[:target_cry].empty? raise _INTL("Animation can't play the target's cry if property \"NoTarget\" is set to true.") + "\n" + FileLineData.linereport diff --git a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb index c576dc173..d311134b9 100644 --- a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb +++ b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb @@ -191,6 +191,38 @@ class AnimationEditor return @components[:particle_list].particle_index end + def load_settings + # TODO: Load these from a saved file. + @settings = { + :side_sizes => [1, 1], + :user_index => 0, + :target_indices => [1], + :user_opposes => false, + # TODO: Ideally be able to independently choose base graphics, which will + # be a separate setting here. + :canvas_bg => "indoor1", + # NOTE: These sprite names are also used in Pokemon.play_cry and so should + # be a species ID (being a string is fine). + :user_sprite_name => "ARCANINE", + :target_sprite_name => "CHARIZARD" + } + end + + def save + GameData::Animation.register(@anim, @anim_id) + Compiler.write_battle_animation_file(@anim[:pbs_path]) + if @anim[:pbs_path] != @pbs_path + if GameData::Animation::DATA.any? { |_key, anim| anim.pbs_path == @pbs_path } + Compiler.write_battle_animation_file(@pbs_path) + elsif FileTest.exist?("PBS/Animations/" + @pbs_path + ".txt") + File.delete("PBS/Animations/" + @pbs_path + ".txt") + end + @pbs_path = @anim[:pbs_path] + end + end + + #----------------------------------------------------------------------------- + #----------------------------------------------------------------------------- # Returns the animation's name for display in the menu bar and elsewhere. @@ -474,38 +506,6 @@ class AnimationEditor #----------------------------------------------------------------------------- - def load_settings - # TODO: Load these from a saved file. - @settings = { - :side_sizes => [1, 1], - :user_index => 0, - :target_indices => [1], - :user_opposes => false, - # TODO: Ideally be able to independently choose base graphics, which will - # be a separate setting here. - :canvas_bg => "indoor1", - # NOTE: These sprite names are also used in Pokemon.play_cry and so should - # be a species ID (being a string is fine). - :user_sprite_name => "ARCANINE", - :target_sprite_name => "CHARIZARD" - } - end - - def save - GameData::Animation.register(@anim, @anim_id) - Compiler.write_battle_animation_file(@anim[:pbs_path]) - if @anim[:pbs_path] != @pbs_path - if GameData::Animation::DATA.any? { |_key, anim| anim.pbs_path == @pbs_path } - Compiler.write_battle_animation_file(@pbs_path) - elsif FileTest.exist?("PBS/Animations/" + @pbs_path + ".txt") - File.delete("PBS/Animations/" + @pbs_path + ".txt") - end - @pbs_path = @anim[:pbs_path] - end - end - - #----------------------------------------------------------------------------- - def draw_editor_background # Fill the whole screen with white @screen_bitmap.bitmap.fill_rect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, Color.white) diff --git a/Data/Scripts/904_Anim Editor/010_AnimationSelector.rb b/Data/Scripts/904_Anim Editor/010_AnimationSelector.rb index cae1b1a6d..7500badb1 100644 --- a/Data/Scripts/904_Anim Editor/010_AnimationSelector.rb +++ b/Data/Scripts/904_Anim Editor/010_AnimationSelector.rb @@ -2,30 +2,37 @@ # #=============================================================================== class AnimationEditor::AnimationSelector - BORDER_THICKNESS = 4 + BORDER_THICKNESS = 4 + LABEL_OFFSET_X = -4 # Position of label relative to what they're labelling + LABEL_OFFSET_Y = -32 - QUIT_BUTTON_WIDTH = 80 - QUIT_BUTTON_HEIGHT = 30 + QUIT_BUTTON_WIDTH = 80 + QUIT_BUTTON_HEIGHT = 30 - TYPE_BUTTONS_X = 2 - TYPE_BUTTONS_Y = 62 - TYPE_BUTTON_WIDTH = 100 - TYPE_BUTTON_HEIGHT = 48 + TYPE_BUTTONS_X = 2 + TYPE_BUTTONS_Y = 62 + TYPE_BUTTON_WIDTH = 100 + TYPE_BUTTON_HEIGHT = 48 - MOVES_LIST_X = TYPE_BUTTONS_X + TYPE_BUTTON_WIDTH + 4 - MOVES_LIST_Y = TYPE_BUTTONS_Y + 4 - MOVES_LIST_WIDTH = 200 - MOVES_LIST_HEIGHT = 26 * UIControls::List::ROW_HEIGHT + MOVES_LIST_X = TYPE_BUTTONS_X + TYPE_BUTTON_WIDTH + 4 + MOVES_LIST_Y = TYPE_BUTTONS_Y + 4 + MOVES_LIST_WIDTH = 200 + MOVES_LIST_HEIGHT = 26 * UIControls::List::ROW_HEIGHT - ANIMATIONS_LIST_X = MOVES_LIST_X + MOVES_LIST_WIDTH + 8 - ANIMATIONS_LIST_Y = MOVES_LIST_Y - ANIMATIONS_LIST_WIDTH = 300 - ANIMATIONS_LIST_HEIGHT = MOVES_LIST_HEIGHT + ANIMATIONS_LIST_X = MOVES_LIST_X + MOVES_LIST_WIDTH + 8 + ANIMATIONS_LIST_Y = MOVES_LIST_Y + ANIMATIONS_LIST_WIDTH = 300 + ANIMATIONS_LIST_HEIGHT = MOVES_LIST_HEIGHT - ACTION_BUTTON_WIDTH = 200 - ACTION_BUTTON_HEIGHT = 48 - ACTION_BUTTON_X = ANIMATIONS_LIST_X + ANIMATIONS_LIST_WIDTH + 4 - ACTION_BUTTON_Y = TYPE_BUTTONS_Y + ((ANIMATIONS_LIST_HEIGHT - (ACTION_BUTTON_HEIGHT * 3)) / 2) + 4 + ACTION_BUTTON_WIDTH = 200 + ACTION_BUTTON_HEIGHT = 48 + ACTION_BUTTON_X = ANIMATIONS_LIST_X + ANIMATIONS_LIST_WIDTH + 4 + ACTION_BUTTON_Y = TYPE_BUTTONS_Y + ((ANIMATIONS_LIST_HEIGHT - (ACTION_BUTTON_HEIGHT * 3)) / 2) + 4 + + FILTER_BOX_WIDTH = ACTION_BUTTON_WIDTH + FILTER_BOX_HEIGHT = UIControls::TextBox::TEXT_BOX_HEIGHT + FILTER_BOX_X = ACTION_BUTTON_X + FILTER_BOX_Y = MOVES_LIST_Y # Pop-up window MESSAGE_BOX_WIDTH = AnimationEditor::WINDOW_WIDTH * 3 / 4 @@ -35,34 +42,35 @@ class AnimationEditor::AnimationSelector MESSAGE_BOX_SPACING = 16 def initialize - generate_lists + @animation_type = 0 # 0=move, 1=common + @filter_text = "" + @quit = false + generate_full_lists + initialize_viewports + initialize_bitmaps + initialize_controls + refresh + end + + def initialize_viewports @viewport = Viewport.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT) @viewport.z = 99999 @pop_up_viewport = Viewport.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT) @pop_up_viewport.z = @viewport.z + 50 + end + + def initialize_bitmaps + # Background @screen_bitmap = BitmapSprite.new(AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, @viewport) + # Semi-transparent black overlay to dim the screen while a pop-up window is open @pop_up_bg_bitmap = BitmapSprite.new(AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, @pop_up_viewport) @pop_up_bg_bitmap.z = -100 @pop_up_bg_bitmap.visible = false + # Draw in these bitmaps draw_editor_background - @animation_type = 0 # 0=move, 1=common - @quit = false - create_controls - refresh end - def dispose - @screen_bitmap.dispose - @pop_up_bg_bitmap.dispose - @components.dispose - @viewport.dispose - @pop_up_viewport.dispose - end - - LABEL_OFFSET_X = -4 - LABEL_OFFSET_Y = -32 - - def create_controls + def initialize_controls @components = UIControls::ControlsContainer.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT) # Quit button btn = UIControls::Button.new(QUIT_BUTTON_WIDTH, QUIT_BUTTON_HEIGHT, @viewport, _INTL("Quit")) @@ -82,11 +90,6 @@ class AnimationEditor::AnimationSelector btn.set_fixed_size @components.add_control_at(val[0], btn, TYPE_BUTTONS_X, TYPE_BUTTONS_Y + (i * TYPE_BUTTON_HEIGHT)) end - # TODO: Add filter text box for :moves_list's contents. Applies the filter - # upon every change to the text box's value. Perhaps it should only do - # so after 0.5 seconds of non-typing. What exactly should the filter - # be applied to? Animation's name, move's name (if there is one), what - # else? Flags? # Moves list label label = UIControls::Label.new(MOVES_LIST_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Moves")) label.header = true @@ -107,8 +110,25 @@ class AnimationEditor::AnimationSelector btn.set_fixed_size @components.add_control_at(val[0], btn, ACTION_BUTTON_X, ACTION_BUTTON_Y + (i * ACTION_BUTTON_HEIGHT)) end + # Filter text box + text_box = UIControls::TextBox.new(FILTER_BOX_WIDTH, FILTER_BOX_HEIGHT, @viewport, "") + @components.add_control_at(:filter, text_box, FILTER_BOX_X, FILTER_BOX_Y) + # Filter text box label + label = UIControls::Label.new(FILTER_BOX_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Filter text")) + label.header = true + @components.add_control_at(:filter_label, label, FILTER_BOX_X + LABEL_OFFSET_X, FILTER_BOX_Y + LABEL_OFFSET_Y) end + def dispose + @screen_bitmap.dispose + @pop_up_bg_bitmap.dispose + @components.dispose + @viewport.dispose + @pop_up_viewport.dispose + end + + #----------------------------------------------------------------------------- + def draw_editor_background # Fill the whole screen with white @screen_bitmap.bitmap.fill_rect(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, Color.white) @@ -120,6 +140,8 @@ class AnimationEditor::AnimationSelector areas.each do |area| @screen_bitmap.bitmap.outline_rect(area[0] - 2, area[1] - 2, area[2] + 4, area[3] + 4, Color.black) end + # Make the pop-up background semi-transparent + @pop_up_bg_bitmap.bitmap.fill_rect(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, Color.new(0, 0, 0, 128)) end #----------------------------------------------------------------------------- @@ -198,36 +220,72 @@ class AnimationEditor::AnimationSelector #----------------------------------------------------------------------------- - def generate_lists - @move_list = [] - @common_list = [] - @move_animations = {} - @common_animations = {} + def generate_full_lists + @full_move_animations = {} + @full_common_animations = {} GameData::Animation.keys.each do |id| anim = GameData::Animation.get(id) name = "" + name += "\\c[2]" if anim.ignore name += _INTL("[Foe]") + " " if anim.opposing_animation? name += "[#{anim.version}]" + " " if anim.version > 0 name += (anim.name || anim.move) if anim.move_animation? move_name = GameData::Move.try_get(anim.move)&.name || anim.move - @move_list.push([anim.move, move_name]) if !@move_animations[anim.move] - @move_animations[anim.move] ||= [] - @move_animations[anim.move].push([id, name]) + @full_move_animations[anim.move] ||= [] + @full_move_animations[anim.move].push([id, name, move_name]) elsif anim.common_animation? - @common_list.push([anim.move, anim.move]) if !@common_animations[anim.move] - @common_animations[anim.move] ||= [] - @common_animations[anim.move].push([id, name]) + @full_common_animations[anim.move] ||= [] + @full_common_animations[anim.move].push([id, name]) end end + @full_move_animations.values.each do |val| + val.sort! { |a, b| a[1] <=> b[1] } + end + @full_common_animations.values.each do |val| + val.sort! { |a, b| a[1] <=> b[1] } + end + apply_list_filter + end + + def apply_list_filter + # Apply filter + if @filter_text == "" + @move_animations = @full_move_animations.clone + @common_animations = @full_common_animations.clone + else + filter = @filter_text.downcase + @move_animations.clear + @full_move_animations.each_pair do |move, anims| + anims.each do |anim| + next if !anim[1].downcase.include?(filter) && !anim[2].downcase.include?(filter) + @move_animations[move] ||= [] + @move_animations[move].push(anim) + end + end + @common_animations.clear + @full_common_animations.each_pair do |common, anims| + anims.each do |anim| + next if !anim[1].downcase.include?(filter) && !common.downcase.include?(filter) + @common_animations[common] ||= [] + @common_animations[common].push(anim) + end + end + end + # Create move list from the filtered results + @move_list = [] + @move_animations.each_pair do |move_id, anims| + @move_list.push([move_id, anims[0][2]]) + end + @move_list.uniq! @move_list.sort! + # Create common list from the filtered results + @common_list = [] + @common_animations.each_pair do |move_id, anims| + @common_list.push([move_id, move_id]) + end + @common_list.uniq! @common_list.sort! - @move_animations.values.each do |val| - val.sort! { |a, b| a[1] <=> b[1] } - end - @common_animations.values.each do |val| - val.sort! { |a, b| a[1] <=> b[1] } - end end def selected_move_animations @@ -284,7 +342,7 @@ class AnimationEditor::AnimationSelector new_id = GameData::Animation.keys.max + 1 screen = AnimationEditor.new(new_id, new_anim) screen.run - generate_lists + generate_full_lists when :moves @animation_type = 0 @components.get_control(:moves_list).selected = -1 @@ -298,7 +356,7 @@ class AnimationEditor::AnimationSelector if anim_id screen = AnimationEditor.new(anim_id, GameData::Animation.get(anim_id).clone_as_hash) screen.run - generate_lists + generate_full_lists end when :copy anim_id = selected_animation_id @@ -308,7 +366,7 @@ class AnimationEditor::AnimationSelector new_id = GameData::Animation.keys.max + 1 screen = AnimationEditor.new(new_id, new_anim) screen.run - generate_lists + generate_full_lists end when :delete anim_id = selected_animation_id @@ -320,7 +378,7 @@ class AnimationEditor::AnimationSelector elsif FileTest.exist?("PBS/Animations/" + pbs_path + ".txt") File.delete("PBS/Animations/" + pbs_path + ".txt") end - generate_lists + generate_full_lists end end refresh @@ -334,6 +392,13 @@ class AnimationEditor::AnimationSelector end @components.clear_changed end + # Detect change to filter text + filter_ctrl = @components.get_control(:filter) + if filter_ctrl.value != @filter_text + @filter_text = filter_ctrl.value + apply_list_filter + refresh + end end def run diff --git a/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb b/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb index ccaaf33b7..7c92d0204 100644 --- a/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb +++ b/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb @@ -300,6 +300,8 @@ class AnimationEditor::Canvas < Sprite else @particle_sprites[index].dispose if @particle_sprites[index] && !@particle_sprites[index].disposed? @particle_sprites[index] = [] + @frame_sprites[index].dispose if @frame_sprites[index] && !@frame_sprites[index].disposed? + @frame_sprites[index] = [] end @particle_sprites[index][target_idx] = Sprite.new(self.viewport) create_frame_sprite(index, target_idx) @@ -307,6 +309,8 @@ class AnimationEditor::Canvas < Sprite if @particle_sprites[index].is_a?(Array) @particle_sprites[index].each { |s| s.dispose if s && !s.disposed? } @particle_sprites[index] = nil + @frame_sprites[index].each { |s| s.dispose if s && !s.disposed? } + @frame_sprites[index] = nil else return if @particle_sprites[index] && !@particle_sprites[index].disposed? end @@ -502,7 +506,13 @@ class AnimationEditor::Canvas < Sprite end def refresh_particle(index) - target_indices.each { |target_idx| refresh_sprite(index, target_idx) } + one_per_side = [:target_side_foreground, :target_side_background].include?(@anim[:particles][index][:focus]) + sides_covered = [] + target_indices.each do |target_idx| + next if one_per_side && sides_covered.include?(target_idx % 2) + refresh_sprite(index, target_idx) + sides_covered.push(target_idx % 2) + end end def refresh_particle_frame