diff --git a/Data/Scripts/801_UI controls/002_ControlsContainer.rb b/Data/Scripts/801_UI controls/002_ControlsContainer.rb index ae7fa0b98..c6d79a592 100644 --- a/Data/Scripts/801_UI controls/002_ControlsContainer.rb +++ b/Data/Scripts/801_UI controls/002_ControlsContainer.rb @@ -221,7 +221,6 @@ class UIControls::ControlsContainer end def add_control(id, control, add_offset = false, rows = 1) - i = @controls.length ctrl_x, ctrl_y = next_control_position(add_offset) ctrl_x += 4 if control.is_a?(UIControls::List) add_control_at(id, control, ctrl_x, ctrl_y) diff --git a/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb b/Data/Scripts/801_UI controls/Control elements/008_BitmapButton.rb similarity index 100% rename from Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb rename to Data/Scripts/801_UI controls/Control elements/008_BitmapButton.rb diff --git a/Data/Scripts/801_UI controls/Control elements/008_List.rb b/Data/Scripts/801_UI controls/Control elements/009_List.rb similarity index 100% rename from Data/Scripts/801_UI controls/Control elements/008_List.rb rename to Data/Scripts/801_UI controls/Control elements/009_List.rb diff --git a/Data/Scripts/801_UI controls/Control elements/009_DropdownList.rb b/Data/Scripts/801_UI controls/Control elements/010_DropdownList.rb similarity index 100% rename from Data/Scripts/801_UI controls/Control elements/009_DropdownList.rb rename to Data/Scripts/801_UI controls/Control elements/010_DropdownList.rb diff --git a/Data/Scripts/801_UI controls/Control elements/010_TextBoxDropdownList.rb b/Data/Scripts/801_UI controls/Control elements/011_TextBoxDropdownList.rb similarity index 100% rename from Data/Scripts/801_UI controls/Control elements/010_TextBoxDropdownList.rb rename to Data/Scripts/801_UI controls/Control elements/011_TextBoxDropdownList.rb diff --git a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb index d311134b9..4a7f9c416 100644 --- a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb +++ b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb @@ -2,6 +2,10 @@ # #=============================================================================== class AnimationEditor + attr_reader :property_pane + attr_reader :components + attr_reader :anim + BORDER_THICKNESS = 4 WINDOW_WIDTH = Settings::SCREEN_WIDTH + 352 + (BORDER_THICKNESS * 4) WINDOW_HEIGHT = Settings::SCREEN_HEIGHT + 352 + (BORDER_THICKNESS * 4) @@ -121,6 +125,7 @@ class AnimationEditor @screen_bitmap = BitmapSprite.new(WINDOW_WIDTH, WINDOW_HEIGHT, @viewport) @screen_bitmap.z = -100 # Background in which to draw the outline of the SE list box in the SE side pane + # TODO: Get rid of this by drawing a list's box in the control itself. @se_list_box_bitmap = BitmapSprite.new(WINDOW_WIDTH, WINDOW_HEIGHT, @viewport) @se_list_box_bitmap.z = -90 @se_list_box_bitmap.visible = false @@ -128,6 +133,20 @@ class AnimationEditor @pop_up_bg_bitmap = BitmapSprite.new(WINDOW_WIDTH, WINDOW_HEIGHT, @pop_up_viewport) @pop_up_bg_bitmap.z = -100 @pop_up_bg_bitmap.visible = false + # Bitmaps for "delete this property change" buttons in the side pane + @delete_bitmap = Bitmap.new(16, 16) + @delete_disabled_bitmap = Bitmap.new(16, 16) + 14.times do |i| + case i + when 0, 13 then wid = 3 + when 1, 12 then wid = 4 + else wid = 5 + end + @delete_bitmap.fill_rect([i - 1, 1].max, i + 1, wid, 1, Color.red) + @delete_bitmap.fill_rect([i - 1, 1].max, 14 - i, wid, 1, Color.red) + @delete_disabled_bitmap.fill_rect([i - 1, 1].max, i + 1, wid, 1, Color.new(160, 160, 160)) + @delete_disabled_bitmap.fill_rect([i - 1, 1].max, 14 - i, wid, 1, Color.new(160, 160, 160)) + end # Draw in these bitmaps draw_editor_background end @@ -139,9 +158,11 @@ class AnimationEditor # Canvas @components[:canvas] = AnimationEditor::Canvas.new(@canvas_viewport, @anim, @settings) # Side panes - [:commands_pane, :color_tone_pane, :se_pane, :particle_pane, :keyframe_pane].each do |pane| - @components[pane] = UIControls::ControlsContainer.new(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT, - ([:commands_pane, :color_tone_pane].include?(pane)) ? SIDE_PANE_DELETE_MARGIN : 0) + AnimationEditor::SidePanes.each_pane do |pane, hash| + @components[pane] = UIControls::ControlsContainer.new( + SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT, + hash[:deletable_properties].nil? ? 0 : SIDE_PANE_DELETE_MARGIN + ) end # Timeline/particle list @components[:particle_list] = AnimationEditor::ParticleList.new( @@ -174,6 +195,8 @@ class AnimationEditor @screen_bitmap.dispose @se_list_box_bitmap.dispose @pop_up_bg_bitmap.dispose + @delete_bitmap.dispose + @delete_disabled_bitmap.dispose @components.each_value { |c| c.dispose } @components.clear @viewport.dispose @@ -223,8 +246,6 @@ class AnimationEditor #----------------------------------------------------------------------------- - #----------------------------------------------------------------------------- - # Returns the animation's name for display in the menu bar and elsewhere. def get_animation_display_name ret = "" @@ -277,139 +298,20 @@ class AnimationEditor pane.increment_row_count(1) end - def generate_delete_property_button_bitmaps - delete_bitmap = Bitmap.new(16, 16) - delete_disabled_bitmap = Bitmap.new(16, 16) - 14.times do |i| - case i - when 0, 13 then wid = 3 - when 1, 12 then wid = 4 - else wid = 5 - end - delete_bitmap.fill_rect([i - 1, 1].max, i + 1, wid, 1, Color.red) - delete_bitmap.fill_rect([i - 1, 1].max, 14 - i, wid, 1, Color.red) - delete_disabled_bitmap.fill_rect([i - 1, 1].max, i + 1, wid, 1, Color.new(160, 160, 160)) - delete_disabled_bitmap.fill_rect([i - 1, 1].max, 14 - i, wid, 1, Color.new(160, 160, 160)) - end - return delete_bitmap, delete_disabled_bitmap - end - - def set_commands_pane_contents - pane = @components[:commands_pane] - pane.add_header_label(:header, _INTL("Edit particle at keyframe")) - # Tab buttons - add_side_pane_tab_buttons(:commands_pane, pane) - # Properties - pane.add_labelled_number_text_box(:x, _INTL("X"), -999, 999, 0) - pane.add_labelled_number_text_box(:y, _INTL("Y"), -999, 999, 0) - pane.add_labelled_number_slider(:z, _INTL("Priority"), -50, 50, 0) - # TODO: If the graphic is user's sprite/target's sprite, make :frame instead - # a choice of front/back/same as the main sprite/opposite of the main - # sprite. Will need two controls in the same space, which is doable. - # Will also need to change the graphic chooser to only have "user"/ - # "target" options rather than all the variants that this control - # would manage. - pane.add_labelled_number_text_box(:frame, _INTL("Frame"), 0, 99, 0) - pane.add_labelled_checkbox(:visible, _INTL("Visible"), true) - pane.add_labelled_number_slider(:opacity, _INTL("Opacity"), 0, 255, 255) - pane.add_labelled_number_text_box(:zoom_x, _INTL("Zoom X"), 0, 1000, 100) - pane.add_labelled_number_text_box(:zoom_y, _INTL("Zoom Y"), 0, 1000, 100) - pane.add_labelled_number_text_box(:angle, _INTL("Angle"), -1080, 1080, 0) - pane.add_labelled_checkbox(:flip, _INTL("Flip"), false) - pane.add_labelled_dropdown_list(:blending, _INTL("Blending"), { - 0 => _INTL("None"), - 1 => _INTL("Additive"), - 2 => _INTL("Subtractive") - }, 0) - # TODO: Add buttons that shift all commands from the current keyframe and - # later forwards/backwards in time? - # Add all "delete" buttons - delete_bitmap, delete_disabled_bitmap = generate_delete_property_button_bitmaps - DELETABLE_COMMAND_PANE_PROPERTIES.each do |property| - parent = pane.get_control(property) - btn = UIControls::BitmapButton.new(parent.x + parent.width + 6, parent.y + 2, - pane.viewport, delete_bitmap, delete_disabled_bitmap) - btn.set_interactive_rects - pane.controls.push([(property.to_s + "_delete").to_sym, btn]) - end - end - - def set_color_tone_pane_contents - pane = @components[:color_tone_pane] - pane.add_header_label(:header, _INTL("Edit particle at keyframe")) - # Tab buttons - add_side_pane_tab_buttons(:color_tone_pane, pane) - # Properties - pane.add_labelled_number_slider(:color_red, _INTL("Color Red"), 0, 255, 0) - pane.add_labelled_number_slider(:color_green, _INTL("Color Green"), 0, 255, 0) - pane.add_labelled_number_slider(:color_blue, _INTL("Color Blue"), 0, 255, 0) - pane.add_labelled_number_slider(:color_alpha, _INTL("Color Alpha"), 0, 255, 0) - pane.add_labelled_number_slider(:tone_red, _INTL("Tone Red"), -255, 255, 0) - pane.add_labelled_number_slider(:tone_green, _INTL("Tone Green"), -255, 255, 0) - pane.add_labelled_number_slider(:tone_blue, _INTL("Tone Blue"), -255, 255, 0) - pane.add_labelled_number_slider(:tone_gray, _INTL("Tone Gray"), 0, 255, 0) - # Add all "delete" buttons - delete_bitmap, delete_disabled_bitmap = generate_delete_property_button_bitmaps - DELETABLE_COLOR_TONE_PANE_PROPERTIES.each do |property| - parent = pane.get_control(property) - btn = UIControls::BitmapButton.new(parent.x + parent.width + 6, parent.y + 2, - pane.viewport, delete_bitmap, delete_disabled_bitmap) - btn.set_interactive_rects - pane.controls.push([(property.to_s + "_delete").to_sym, btn]) - end - end - - def set_se_pane_contents - pane = @components[:se_pane] - pane.add_header_label(:header, _INTL("Edit sound effects at keyframe")) - size = pane.control_size - size[0] -= 10 - size[1] = UIControls::List::ROW_HEIGHT * 5 # 5 rows - list = UIControls::List.new(*size, pane.viewport, []) - pane.add_control_at(:list, list, 5, 30) - button_height = UIControls::ControlsContainer::LINE_SPACING - add = UIControls::Button.new(101, button_height, pane.viewport, _INTL("Add")) - add.set_fixed_size - pane.add_control_at(:add, add, 1, 154) - edit = UIControls::Button.new(100, button_height, pane.viewport, _INTL("Edit")) - edit.set_fixed_size - pane.add_control_at(:edit, edit, 102, 154) - delete = UIControls::Button.new(101, button_height, pane.viewport, _INTL("Delete")) - delete.set_fixed_size - pane.add_control_at(:delete, delete, 202, 154) - end - - def set_particle_pane_contents - pane = @components[:particle_pane] - pane.add_header_label(:header, _INTL("Edit particle properties")) - pane.add_labelled_text_box(:name, _INTL("Name"), "") - pane.get_control(:name).set_blacklist("User", "Target", "SE") - pane.add_labelled_label(:graphic_name, _INTL("Graphic"), "") - pane.add_labelled_button(:graphic, "", _INTL("Change")) - pane.add_labelled_dropdown_list(:focus, _INTL("Focus"), {}, :undefined) - # FlipIfFoe - # RotateIfFoe - pane.add_button(:duplicate, _INTL("Duplicate this particle")) - pane.add_button(:delete, _INTL("Delete this particle")) - # Shift all command timings by X keyframes (text box and button) - # Move particle up/down the list? - end - - # TODO: :keyframe_pane is currently inaccessible (intentionally). If it will - # have its own commands and should be accessible again, change def - # on_mouse_release in ParticleList. - def set_keyframe_pane_contents - keyframe_pane = @components[:keyframe_pane] - keyframe_pane.add_header_label(:header, _INTL("Edit keyframe")) - # TODO: Various command-shifting options. - end - def set_side_panes_contents - set_commands_pane_contents - set_color_tone_pane_contents - set_se_pane_contents - set_particle_pane_contents - set_keyframe_pane_contents + AnimationEditor::SidePanes.each_pane do |pane, hash| + deletable_properties = hash[:deletable_properties] + AnimationEditor::SidePanes.each_property(pane) do |property, hash| + hash[:new].call(@components[pane], self) if hash[:new] + if deletable_properties&.include?(property) + parent = @components[pane].get_control(property) + btn = UIControls::BitmapButton.new(parent.x + parent.width + 6, parent.y + 2, + @components[pane].viewport, @delete_bitmap, @delete_disabled_bitmap) + btn.set_interactive_rects + @components[pane].controls.push([(property.to_s + "_delete").to_sym, btn]) + end + end + end end def set_particle_list_contents @@ -528,23 +430,11 @@ class AnimationEditor #----------------------------------------------------------------------------- def refresh_component_visibility(component_sym) - component = @components[component_sym] # Panes are all mutually exclusive - case component_sym - when :commands_pane, :color_tone_pane - component.visible = (keyframe >= 0 && particle_index >= 0 && - @anim[:particles][particle_index] && - @anim[:particles][particle_index][:name] != "SE") && - @property_pane == component_sym - when :se_pane - component.visible = (keyframe >= 0 && particle_index >= 0 && - @anim[:particles][particle_index] && - @anim[:particles][particle_index][:name] == "SE") - @se_list_box_bitmap.visible = component.visible - when :particle_pane - component.visible = (keyframe < 0 && particle_index >= 0) - when :keyframe_pane - component.visible = (keyframe >= 0 && particle_index < 0) + side_pane = AnimationEditor::SidePanes.get_pane(component_sym) + if side_pane && side_pane[:set_visible] + @components[component_sym].visible = side_pane[:set_visible].call(self, @anim, keyframe, particle_index) + @se_list_box_bitmap.visible = @components[component_sym].visible if component_sym == :se_pane end end @@ -569,145 +459,6 @@ class AnimationEditor when :canvas component.keyframe = keyframe component.selected_particle = particle_index - when :commands_pane - new_vals = AnimationEditor::ParticleDataHelper.get_all_keyframe_particle_values(@anim[:particles][particle_index], keyframe) - component.controls.each do |ctrl| - next if !new_vals.include?(ctrl[0]) - ctrl[1].value = new_vals[ctrl[0]][0] if ctrl[1].respond_to?("value=") - # TODO: new_vals[ctrl[0]][1] is whether the value is being interpolated, - # which should be indicated somehow in ctrl[1]. - end - # Set an appropriate range for the priority (z) property depending on the - # particle's focus - case @anim[:particles][particle_index][:focus] - when :user_and_target - component.get_control(:z).min_value = GameData::Animation::USER_AND_TARGET_SEPARATION[2] - 50 - component.get_control(:z).max_value = 50 - else - component.get_control(:z).min_value = -50 - component.get_control(:z).max_value = 50 - end - # Disable the "Frame" control if the particle's graphic is predefined to - # be the user's or target's sprite - # TODO: Also disable it if the particle's graphic isn't a spritesheet. - if ["USER", "USER_OPP", "USER_FRONT", "USER_BACK", - "TARGET", "TARGET_OPP", "TARGET_FRONT", "TARGET_BACK"].include?(@anim[:particles][particle_index][:graphic]) - component.get_control(:frame).disable - else - component.get_control(:frame).enable - end - # Enable/disable property delete buttons - DELETABLE_COMMAND_PANE_PROPERTIES.each do |property| - if AnimationEditor::ParticleDataHelper.has_command_at?(@anim[:particles][particle_index], property, keyframe) - component.get_control((property.to_s + "_delete").to_sym).enable - else - component.get_control((property.to_s + "_delete").to_sym).disable - end - end - when :color_tone_pane - new_vals = AnimationEditor::ParticleDataHelper.get_all_keyframe_particle_values(@anim[:particles][particle_index], keyframe) - component.controls.each do |ctrl| - next if !new_vals.include?(ctrl[0]) - ctrl[1].value = new_vals[ctrl[0]][0] if ctrl[1].respond_to?("value=") - # TODO: new_vals[ctrl[0]][1] is whether the value is being interpolated, - # which should be indicated somehow in ctrl[1]. - end - # Enable/disable property delete buttons - DELETABLE_COLOR_TONE_PANE_PROPERTIES.each do |property| - if AnimationEditor::ParticleDataHelper.has_command_at?(@anim[:particles][particle_index], property, keyframe) - component.get_control((property.to_s + "_delete").to_sym).enable - else - component.get_control((property.to_s + "_delete").to_sym).disable - end - end - when :se_pane - se_particle = @anim[:particles].select { |p| p[:name] == "SE" }[0] - kyfrm = keyframe - # Populate list of files - list = [] - se_particle.each_pair do |property, values| - next if !values.is_a?(Array) - values.each do |val| - next if val[0] != kyfrm - text = AnimationEditor::ParticleDataHelper.get_se_display_text(property, val) - case property - when :user_cry then list.push(["USER", text]) - when :target_cry then list.push(["TARGET", text]) - when :se then list.push([val[2], text]) - end - end - end - list.sort! { |a, b| a[1].downcase <=> b[1].downcase } - component.get_control(:list).values = list - # Enable/disable the "Edit" and "Delete" buttons - if list.length > 0 && component.get_control(:list).value - component.get_control(:edit).enable - component.get_control(:delete).enable - else - component.get_control(:edit).disable - component.get_control(:delete).disable - end - when :particle_pane - # Display particle's graphic's name - new_vals = AnimationEditor::ParticleDataHelper.get_all_particle_values(@anim[:particles][particle_index]) - component.controls.each do |ctrl| - next if !new_vals.include?(ctrl[0]) - ctrl[1].value = new_vals[ctrl[0]] if ctrl[1].respond_to?("value=") - end - graphic_name = @anim[:particles][particle_index][:graphic] - graphic_override_names = { - "USER" => _INTL("[[User's sprite]]"), - "USER_OPP" => _INTL("[[User's other side sprite]]"), - "USER_FRONT" => _INTL("[[User's front sprite]]"), - "USER_BACK" => _INTL("[[User's back sprite]]"), - "TARGET" => _INTL("[[Target's sprite]]"), - "TARGET_OPP" => _INTL("[[Target's other side sprite]]"), - "TARGET_FRONT" => _INTL("[[Target's front sprite]]"), - "TARGET_BACK" => _INTL("[[Target's back sprite]]"), - } - graphic_name = graphic_override_names[graphic_name] if graphic_override_names[graphic_name] - component.get_control(:graphic_name).text = graphic_name - # Enable/disable the Graphic and Focus controls for "User"/"Target" - if ["User", "Target"].include?(@anim[:particles][particle_index][:name]) - component.get_control(:graphic).disable - component.get_control(:focus).disable - else - component.get_control(:graphic).enable - component.get_control(:focus).enable - end - # Enable/disable the Duplicate button - if ["SE"].include?(@anim[:particles][particle_index][:name]) - component.get_control(:duplicate).disable - else - component.get_control(:duplicate).enable - end - # Enable/disable the Delete button - if ["User", "Target", "SE"].include?(@anim[:particles][particle_index][:name]) - component.get_control(:delete).disable - else - component.get_control(:delete).enable - end - # Set the possible foci depending on whether the animation involves a user - # and target - focus_values = { - :foreground => _INTL("Foreground"), - :midground => _INTL("Midground"), - :background => _INTL("Background"), - :user => _INTL("User"), - :target => _INTL("Target"), - :user_and_target => _INTL("User and target"), - :user_side_foreground => _INTL("In front of user's side"), - :user_side_background => _INTL("Behind user's side"), - :target_side_foreground => _INTL("In front of target's side"), - :target_side_background => _INTL("Behind target's side") - } - if @anim[:no_user] - GameData::Animation::FOCUS_TYPES_WITH_USER.each { |f| focus_values.delete(f) } - end - if @anim[:no_target] - GameData::Animation::FOCUS_TYPES_WITH_TARGET.each { |f| focus_values.delete(f) } - end - component.get_control(:focus).values = focus_values when :particle_list # Disable the "move particle up/down" buttons if the selected particle # can't move that way (or there is no selected particle) @@ -732,6 +483,42 @@ class AnimationEditor component.get_control(:move_label).text = _INTL("Common animation") end # TODO: Maybe other things as well? + else + # Side panes + if AnimationEditor::SidePanes.is_side_pane?(component_sym) + # Refresh each control's value + if AnimationEditor::SidePanes.get_pane(component_sym)[:unchanging_properties] + new_vals = AnimationEditor::ParticleDataHelper.get_all_particle_values(@anim[:particles][particle_index]) + component.controls.each do |ctrl| + next if !new_vals.include?(ctrl[0]) + ctrl[1].value = new_vals[ctrl[0]] if ctrl[1].respond_to?("value=") + end + else + new_vals = AnimationEditor::ParticleDataHelper.get_all_keyframe_particle_values(@anim[:particles][particle_index], keyframe) + component.controls.each do |ctrl| + next if !new_vals.include?(ctrl[0]) + ctrl[1].value = new_vals[ctrl[0]][0] if ctrl[1].respond_to?("value=") + # TODO: new_vals[ctrl[0]][1] is whether the value is being interpolated, + # which should be indicated somehow in ctrl[1]. + end + end + # Additional refreshing of controls + AnimationEditor::SidePanes.each_property(component_sym) do |property, hash| + next if !hash[:refresh_value] + hash[:refresh_value].call(component.get_control(property), self) + end + # Enable/disable property delete buttons + deletable_properties = AnimationEditor::SidePanes.get_pane(component_sym)[:deletable_properties] + if deletable_properties + deletable_properties.each do |property| + if AnimationEditor::ParticleDataHelper.has_command_at?(@anim[:particles][particle_index], property, keyframe) + component.get_control((property.to_s + "_delete").to_sym).enable + else + component.get_control((property.to_s + "_delete").to_sym).disable + end + end + end + end end end @@ -783,107 +570,6 @@ class AnimationEditor when :play_controls # TODO: Will the play controls ever signal themselves as changed? I don't # think so. - when :commands_pane, :color_tone_pane - case property - when :general_tab - @property_pane = :commands_pane - refresh_component(component_sym) - refresh_component(@property_pane) - when :color_tone_tab - @property_pane = :color_tone_pane - refresh_component(component_sym) - refresh_component(@property_pane) - else - particle = @anim[:particles][particle_index] - prop = property - if property.to_s[/_delete$/] - prop = property.to_s.sub(/_delete$/, "").to_sym - new_cmds = AnimationEditor::ParticleDataHelper.delete_command(particle, prop, keyframe) - else - new_cmds = AnimationEditor::ParticleDataHelper.add_command(particle, property, keyframe, value) - end - if new_cmds - particle[prop] = new_cmds - else - particle.delete(prop) - end - @components[:particle_list].change_particle_commands(particle_index) - @components[:play_controls].duration = @components[:particle_list].duration - refresh_component(component_sym) - refresh_component(:canvas) - end - when :se_pane - case property - when :list # List - refresh_component(component_sym) - when :add # Button - new_file, new_volume, new_pitch = choose_audio_file("", 100, 100) - if new_file != "" - particle = @anim[:particles][particle_index] - AnimationEditor::ParticleDataHelper.add_se_command(particle, keyframe, new_file, new_volume, new_pitch) - @components[:particle_list].change_particle_commands(particle_index) - @components[:play_controls].duration = @components[:particle_list].duration - refresh_component(component_sym) - end - when :edit # Button - particle = @anim[:particles][particle_index] - list = @components[component_sym].get_control(:list) - old_file = list.value - old_volume, old_pitch = AnimationEditor::ParticleDataHelper.get_se_values_from_filename_and_frame(particle, keyframe, old_file) - if old_file - new_file, new_volume, new_pitch = choose_audio_file(old_file, old_volume, old_pitch) - if new_file != old_file || new_volume != old_volume || new_pitch != old_pitch - AnimationEditor::ParticleDataHelper.delete_se_command(particle, keyframe, old_file) - AnimationEditor::ParticleDataHelper.add_se_command(particle, keyframe, new_file, new_volume, new_pitch) - @components[:particle_list].change_particle_commands(particle_index) - @components[:play_controls].duration = @components[:particle_list].duration - refresh_component(component_sym) - end - end - when :delete # Button - particle = @anim[:particles][particle_index] - list = @components[component_sym].get_control(:list) - old_file = list.value - if old_file - AnimationEditor::ParticleDataHelper.delete_se_command(particle, keyframe, old_file) - @components[:particle_list].change_particle_commands(particle_index) - @components[:play_controls].duration = @components[:particle_list].duration - refresh_component(component_sym) - end - end - when :particle_pane - case property - when :graphic # Button - p_index = particle_index - new_file = choose_graphic_file(@anim[:particles][p_index][:graphic]) - if @anim[:particles][p_index][:graphic] != new_file - @anim[:particles][p_index][:graphic] = new_file - refresh_component(component_sym) - refresh_component(:canvas) - end - when :duplicate - AnimationEditor::ParticleDataHelper.duplicate_particle(@anim[:particles], particle_index) - @components[:particle_list].add_particle(particle_index + 1) - @components[:particle_list].set_particles(@anim[:particles]) - @components[:particle_list].particle_index = particle_index + 1 - refresh - when :delete - if confirm_message(_INTL("Are you sure you want to delete this particle?")) - AnimationEditor::ParticleDataHelper.delete_particle(@anim[:particles], particle_index) - @components[:particle_list].delete_particle(particle_index) - @components[:particle_list].set_particles(@anim[:particles]) - @components[:particle_list].keyframe = 0 if @anim[:particles][particle_index][:name] == "SE" - refresh - end - else - particle = @anim[:particles][particle_index] - new_cmds = AnimationEditor::ParticleDataHelper.set_property(particle, property, value) - @components[:particle_list].change_particle(particle_index) - refresh_component(component_sym) - refresh_component(:canvas) - end - when :keyframe_pane - # TODO: Stuff here once I decide what controls to add. when :particle_list case property when :add_particle @@ -1000,6 +686,29 @@ class AnimationEditor else @anim[property] = value end + else + # Side panes + if AnimationEditor::SidePanes.is_side_pane?(component_sym) + if [:commands_pane, :color_tone_pane].include?(component_sym) && + [:general_tab, :color_tone_tab].include?(property) + @property_pane = { + :general_tab => :commands_pane, + :color_tone_tab => :color_tone_pane + }[property] + refresh_component(component_sym) + refresh_component(@property_pane) + else + hash = AnimationEditor::SidePanes.get_property(component_sym, property) + if hash && hash[:apply_value] + hash[:apply_value].call(value, self) + else + hash = AnimationEditor::SidePanes.get_pane(component_sym) + if hash && hash[:apply_value] + hash[:apply_value].call(property, value, self) + end + end + end + end end end diff --git a/Data/Scripts/904_Anim Editor/003_AnimationEditor_side_panes.rb b/Data/Scripts/904_Anim Editor/003_AnimationEditor_side_panes.rb new file mode 100644 index 000000000..23375bfff --- /dev/null +++ b/Data/Scripts/904_Anim Editor/003_AnimationEditor_side_panes.rb @@ -0,0 +1,597 @@ +#=============================================================================== +# +#=============================================================================== +module AnimationEditor::SidePanes + @@panes = {} + @@properties = {} + + def self.is_side_pane?(pane) + return @@panes.keys.include?(pane) + end + + def self.add_pane(symbol, hash) + @@panes[symbol] = hash + end + + def self.add_property(pane, symbol, hash) + @@properties[pane] ||= {} + @@properties[pane][symbol] = hash + end + + def self.each_pane + @@panes.each_pair { |pane, hash| yield pane, hash } + end + + def self.each_property(pane) + return if !@@properties[pane] + @@properties[pane].each_pair do |property, hash| + yield property, hash + end + end + + def self.get_pane(pane) + return @@panes[pane] + end + + def self.get_property(pane, property) + return nil if !@@properties[pane] || !@@properties[pane][property] + return @@properties[pane][property] + end + + def self.remove_pane(pane) + @@panes.remove(pane) + @@properties.remove(pane) + end + + def self.remove_property(pane, property) + @@properties[pane]&.remove(property) + end +end + +#=============================================================================== +# +#=============================================================================== +AnimationEditor::SidePanes.add_pane(:commands_pane, { + :deletable_properties => AnimationEditor::DELETABLE_COMMAND_PANE_PROPERTIES, + :set_visible => proc { |editor, anim, keyframe, particle_index| + next keyframe >= 0 && particle_index >= 0 && + anim[:particles][particle_index] && + anim[:particles][particle_index][:name] != "SE" && + editor.property_pane == :commands_pane + }, + :apply_value => proc { |property, value, editor| + particle = editor.anim[:particles][editor.particle_index] + prop = property + if property.to_s[/_delete$/] + prop = property.to_s.sub(/_delete$/, "").to_sym + new_cmds = AnimationEditor::ParticleDataHelper.delete_command(particle, prop, editor.keyframe) + else + new_cmds = AnimationEditor::ParticleDataHelper.add_command(particle, property, editor.keyframe, value) + end + if new_cmds + particle[prop] = new_cmds + else + particle.delete(prop) + end + editor.components[:particle_list].change_particle_commands(editor.particle_index) + editor.components[:play_controls].duration = editor.components[:particle_list].duration + editor.refresh_component(:commands_pane) + editor.refresh_component(:canvas) + } +}) + +AnimationEditor::SidePanes.add_pane(:color_tone_pane, { + :deletable_properties => AnimationEditor::DELETABLE_COLOR_TONE_PANE_PROPERTIES, + :set_visible => proc { |editor, anim, keyframe, particle_index| + next keyframe >= 0 && particle_index >= 0 && + anim[:particles][particle_index] && + anim[:particles][particle_index][:name] != "SE" && + editor.property_pane == :color_tone_pane + }, + :apply_value => proc { |property, value, editor| + particle = editor.anim[:particles][editor.particle_index] + prop = property + if property.to_s[/_delete$/] + prop = property.to_s.sub(/_delete$/, "").to_sym + new_cmds = AnimationEditor::ParticleDataHelper.delete_command(particle, prop, editor.keyframe) + else + new_cmds = AnimationEditor::ParticleDataHelper.add_command(particle, property, editor.keyframe, value) + end + if new_cmds + particle[prop] = new_cmds + else + particle.delete(prop) + end + editor.components[:particle_list].change_particle_commands(editor.particle_index) + editor.components[:play_controls].duration = editor.components[:particle_list].duration + editor.refresh_component(:color_tone_pane) + editor.refresh_component(:canvas) + } +}) + +# NOTE: Doesn't need an :apply_value proc. +AnimationEditor::SidePanes.add_pane(:se_pane, { + :set_visible => proc { |editor, anim, keyframe, particle_index| + next keyframe >= 0 && particle_index >= 0 && + anim[:particles][particle_index] && + anim[:particles][particle_index][:name] == "SE" + } +}) + +AnimationEditor::SidePanes.add_pane(:particle_pane, { + :unchanging_properties => true, + :set_visible => proc { |editor, anim, keyframe, particle_index| + next keyframe < 0 && particle_index >= 0 + }, + :apply_value => proc { |property, value, editor| + particle = editor.anim[:particles][editor.particle_index] + new_cmds = AnimationEditor::ParticleDataHelper.set_property(particle, property, value) + editor.components[:particle_list].change_particle(editor.particle_index) + editor.refresh_component(:particle_pane) + editor.refresh_component(:canvas) + } +}) + +# AnimationEditor::SidePanes.add_pane(:keyframe_pane, { +# :set_visible => proc { |editor, anim, keyframe, particle_index| +# next keyframe >= 0 && particle_index < 0 +# } +# }) + +#=============================================================================== +# +#=============================================================================== +AnimationEditor::SidePanes.add_property(:commands_pane, :header, { + :new => proc { |pane, editor| + pane.add_header_label(:header, _INTL("Edit particle at keyframe")) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :tab_buttons, { + :new => proc { |pane, editor| + editor.add_side_pane_tab_buttons(:commands_pane, pane) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :x, { + :new => proc { |pane, editor| + pane.add_labelled_number_text_box(:x, _INTL("X"), -999, 999, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :y, { + :new => proc { |pane, editor| + pane.add_labelled_number_text_box(:y, _INTL("Y"), -999, 999, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :z, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:z, _INTL("Priority"), -50, 50, 0) + }, + :refresh_value => proc { |control, editor| + # Set an appropriate range for the priority (z) property depending on the + # particle's focus + case editor.anim[:particles][editor.particle_index][:focus] + when :user_and_target + control.min_value = GameData::Animation::USER_AND_TARGET_SEPARATION[2] - 50 + control.max_value = 50 + else + control.min_value = -50 + control.max_value = 50 + end + } +}) + +# TODO: If the graphic is user's sprite/target's sprite, make :frame instead +# a choice of front/back/same as the main sprite/opposite of the main +# sprite. Will need two controls in the same space, which is doable. +# Will also need to change the graphic chooser to only have "user"/ +# "target" options rather than all the variants that this control +# would manage. +AnimationEditor::SidePanes.add_property(:commands_pane, :frame, { + :new => proc { |pane, editor| + pane.add_labelled_number_text_box(:frame, _INTL("Frame"), 0, 99, 0) + }, + :refresh_value => proc { |control, editor| + # Disable the "Frame" control if the particle's graphic is predefined to be + # the user's or target's sprite + # TODO: Also disable it if the particle's graphic isn't a spritesheet. + graphic = editor.anim[:particles][editor.particle_index][:graphic] + if ["USER", "USER_OPP", "USER_FRONT", "USER_BACK", + "TARGET", "TARGET_OPP", "TARGET_FRONT", "TARGET_BACK"].include?(graphic) + control.disable + else + control.enable + end + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :visible, { + :new => proc { |pane, editor| + pane.add_labelled_checkbox(:visible, _INTL("Visible"), true) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :opacity, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:opacity, _INTL("Opacity"), 0, 255, 255) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :zoom_x, { + :new => proc { |pane, editor| + pane.add_labelled_number_text_box(:zoom_x, _INTL("Zoom X"), 0, 1000, 100) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :zoom_y, { + :new => proc { |pane, editor| + pane.add_labelled_number_text_box(:zoom_y, _INTL("Zoom Y"), 0, 1000, 100) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :angle, { + :new => proc { |pane, editor| + pane.add_labelled_number_text_box(:angle, _INTL("Angle"), -1080, 1080, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :flip, { + :new => proc { |pane, editor| + pane.add_labelled_checkbox(:flip, _INTL("Flip"), false) + } +}) + +AnimationEditor::SidePanes.add_property(:commands_pane, :blending, { + :new => proc { |pane, editor| + pane.add_labelled_dropdown_list(:blending, _INTL("Blending"), { + 0 => _INTL("None"), + 1 => _INTL("Additive"), + 2 => _INTL("Subtractive") + }, 0) + } +}) + +# TODO: Add buttons that shift all commands from the current keyframe and later +# forwards/backwards in time? + +#=============================================================================== +# +#=============================================================================== +AnimationEditor::SidePanes.add_property(:color_tone_pane, :header, { + :new => proc { |pane, editor| + pane.add_header_label(:header, _INTL("Edit particle at keyframe")) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :tab_buttons, { + :new => proc { |pane, editor| + editor.add_side_pane_tab_buttons(:color_tone_pane, pane) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :color_red, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:color_red, _INTL("Color Red"), 0, 255, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :color_green, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:color_green, _INTL("Color Green"), 0, 255, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :color_blue, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:color_blue, _INTL("Color Blue"), 0, 255, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :color_alpha, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:color_alpha, _INTL("Color Alpha"), 0, 255, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :tone_red, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:tone_red, _INTL("Tone Red"), -255, 255, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :tone_green, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:tone_green, _INTL("Tone Green"), -255, 255, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :tone_blue, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:tone_blue, _INTL("Tone Blue"), -255, 255, 0) + } +}) + +AnimationEditor::SidePanes.add_property(:color_tone_pane, :tone_gray, { + :new => proc { |pane, editor| + pane.add_labelled_number_slider(:tone_gray, _INTL("Tone Gray"), 0, 255, 0) + } +}) + +#=============================================================================== +# +#=============================================================================== +AnimationEditor::SidePanes.add_property(:se_pane, :header, { + :new => proc { |pane, editor| + pane.add_header_label(:header, _INTL("Edit sound effects at keyframe")) + } +}) + +AnimationEditor::SidePanes.add_property(:se_pane, :list, { + :new => proc { |pane, editor| + size = pane.control_size + size[0] -= 10 + size[1] = UIControls::List::ROW_HEIGHT * 5 # 5 rows + list = UIControls::List.new(*size, pane.viewport, []) + pane.add_control_at(:list, list, 5, 30) + }, + :refresh_value => proc { |control, editor| + se_particle = editor.anim[:particles].select { |ptcl| ptcl[:name] == "SE" }[0] + keyframe = editor.keyframe + # Populate list of files + list = [] + se_particle.each_pair do |property, values| + next if !values.is_a?(Array) + values.each do |val| + next if val[0] != keyframe + text = AnimationEditor::ParticleDataHelper.get_se_display_text(property, val) + case property + when :user_cry then list.push(["USER", text]) + when :target_cry then list.push(["TARGET", text]) + when :se then list.push([val[2], text]) + end + end + end + list.sort! { |a, b| a[1].downcase <=> b[1].downcase } + control.values = list + }, + :apply_value => proc { |value, editor| + editor.refresh_component(:se_pane) + } +}) + +AnimationEditor::SidePanes.add_property(:se_pane, :add, { + :new => proc { |pane, editor| + button_height = UIControls::ControlsContainer::LINE_SPACING + button = UIControls::Button.new(101, button_height, pane.viewport, _INTL("Add")) + button.set_fixed_size + pane.add_control_at(:add, button, 1, 154) + }, + :apply_value => proc { |value, editor| + new_file, new_volume, new_pitch = editor.choose_audio_file("", 100, 100) + if new_file != "" + particle = editor.anim[:particles][editor.particle_index] + AnimationEditor::ParticleDataHelper.add_se_command(particle, editor.keyframe, new_file, new_volume, new_pitch) + editor.components[:particle_list].change_particle_commands(editor.particle_index) + editor.components[:play_controls].duration = editor.components[:particle_list].duration + editor.refresh_component(:se_pane) + end + } +}) + +AnimationEditor::SidePanes.add_property(:se_pane, :edit, { + :new => proc { |pane, editor| + button_height = UIControls::ControlsContainer::LINE_SPACING + button = UIControls::Button.new(100, button_height, pane.viewport, _INTL("Edit")) + button.set_fixed_size + pane.add_control_at(:edit, button, 102, 154) + }, + :refresh_value => proc { |control, editor| + has_se = AnimationEditor::ParticleDataHelper.has_se_command_at?(editor.anim[:particles], editor.keyframe) + list = editor.components[:se_pane].get_control(:list) + if has_se && list.value + control.enable + else + control.disable + end + }, + :apply_value => proc { |value, editor| + particle = editor.anim[:particles][editor.particle_index] + list = editor.components[:se_pane].get_control(:list) + old_file = list.value + old_volume, old_pitch = AnimationEditor::ParticleDataHelper.get_se_values_from_filename_and_frame(particle, editor.keyframe, old_file) + if old_file + new_file, new_volume, new_pitch = editor.choose_audio_file(old_file, old_volume, old_pitch) + if new_file != old_file || new_volume != old_volume || new_pitch != old_pitch + AnimationEditor::ParticleDataHelper.delete_se_command(particle, editor.keyframe, old_file) + AnimationEditor::ParticleDataHelper.add_se_command(particle, editor.keyframe, new_file, new_volume, new_pitch) + editor.components[:particle_list].change_particle_commands(editor.particle_index) + editor.components[:play_controls].duration = editor.components[:particle_list].duration + editor.refresh_component(:se_pane) + end + end + } +}) + +AnimationEditor::SidePanes.add_property(:se_pane, :delete, { + :new => proc { |pane, editor| + button_height = UIControls::ControlsContainer::LINE_SPACING + button = UIControls::Button.new(101, button_height, pane.viewport, _INTL("Delete")) + button.set_fixed_size + pane.add_control_at(:delete, button, 202, 154) + }, + :refresh_value => proc { |control, editor| + has_se = AnimationEditor::ParticleDataHelper.has_se_command_at?(editor.anim[:particles], editor.keyframe) + list = editor.components[:se_pane].get_control(:list) + if has_se && list.value + control.enable + else + control.disable + end + }, + :apply_value => proc { |value, editor| + particle = editor.anim[:particles][editor.particle_index] + list = editor.components[:se_pane].get_control(:list) + old_file = list.value + if old_file + AnimationEditor::ParticleDataHelper.delete_se_command(particle, editor.keyframe, old_file) + editor.components[:particle_list].change_particle_commands(editor.particle_index) + editor.components[:play_controls].duration = editor.components[:particle_list].duration + editor.refresh_component(:se_pane) + end + } +}) + +#=============================================================================== +# +#=============================================================================== +AnimationEditor::SidePanes.add_property(:particle_pane, :header, { + :new => proc { |pane, editor| + pane.add_header_label(:header, _INTL("Edit particle properties")) + } +}) + +AnimationEditor::SidePanes.add_property(:particle_pane, :name, { + :new => proc { |pane, editor| + pane.add_labelled_text_box(:name, _INTL("Name"), "") + pane.get_control(:name).set_blacklist("", "User", "Target", "SE") + } +}) + +AnimationEditor::SidePanes.add_property(:particle_pane, :graphic_name, { + :new => proc { |pane, editor| + pane.add_labelled_label(:graphic_name, _INTL("Graphic"), "") + }, + :refresh_value => proc { |control, editor| + graphic_name = editor.anim[:particles][editor.particle_index][:graphic] + graphic_override_names = { + "USER" => _INTL("[[User's sprite]]"), + "USER_OPP" => _INTL("[[User's other side sprite]]"), + "USER_FRONT" => _INTL("[[User's front sprite]]"), + "USER_BACK" => _INTL("[[User's back sprite]]"), + "TARGET" => _INTL("[[Target's sprite]]"), + "TARGET_OPP" => _INTL("[[Target's other side sprite]]"), + "TARGET_FRONT" => _INTL("[[Target's front sprite]]"), + "TARGET_BACK" => _INTL("[[Target's back sprite]]"), + } + graphic_name = graphic_override_names[graphic_name] if graphic_override_names[graphic_name] + control.text = graphic_name + } +}) + +AnimationEditor::SidePanes.add_property(:particle_pane, :graphic, { + :new => proc { |pane, editor| + pane.add_labelled_button(:graphic, "", _INTL("Change")) + }, + :refresh_value => proc { |control, editor| + if ["User", "Target"].include?(editor.anim[:particles][editor.particle_index][:name]) + control.disable + else + control.enable + end + }, + :apply_value => proc { |value, editor| + p_index = editor.particle_index + new_file = editor.choose_graphic_file(editor.anim[:particles][p_index][:graphic]) + if editor.anim[:particles][p_index][:graphic] != new_file + editor.anim[:particles][p_index][:graphic] = new_file + editor.refresh_component(:particle_pane) + editor.refresh_component(:canvas) + end + } +}) + +AnimationEditor::SidePanes.add_property(:particle_pane, :focus, { + :new => proc { |pane, editor| + pane.add_labelled_dropdown_list(:focus, _INTL("Focus"), {}, :undefined) + }, + :refresh_value => proc { |control, editor| + if ["User", "Target"].include?(editor.anim[:particles][editor.particle_index][:name]) + control.disable + else + control.enable + end + focus_values = { + :foreground => _INTL("Foreground"), + :midground => _INTL("Midground"), + :background => _INTL("Background"), + :user => _INTL("User"), + :target => _INTL("Target"), + :user_and_target => _INTL("User and target"), + :user_side_foreground => _INTL("In front of user's side"), + :user_side_background => _INTL("Behind user's side"), + :target_side_foreground => _INTL("In front of target's side"), + :target_side_background => _INTL("Behind target's side") + } + if editor.anim[:no_user] + GameData::Animation::FOCUS_TYPES_WITH_USER.each { |f| focus_values.delete(f) } + end + if editor.anim[:no_target] + GameData::Animation::FOCUS_TYPES_WITH_TARGET.each { |f| focus_values.delete(f) } + end + control.values = focus_values + } +}) + +# TODO: FlipIfFoe. +# TODO: RotateIfFoe. + +AnimationEditor::SidePanes.add_property(:particle_pane, :duplicate, { + :new => proc { |pane, editor| + pane.add_button(:duplicate, _INTL("Duplicate this particle")) + }, + :refresh_value => proc { |control, editor| + if ["SE"].include?(editor.anim[:particles][editor.particle_index][:name]) + control.disable + else + control.enable + end + }, + :apply_value => proc { |value, editor| + p_index = editor.particle_index + AnimationEditor::ParticleDataHelper.duplicate_particle(editor.anim[:particles], p_index) + editor.components[:particle_list].add_particle(p_index + 1) + editor.components[:particle_list].set_particles(editor.anim[:particles]) + editor.components[:particle_list].particle_index = p_index + 1 + editor.refresh + } +}) + +AnimationEditor::SidePanes.add_property(:particle_pane, :delete, { + :new => proc { |pane, editor| + pane.add_button(:delete, _INTL("Delete this particle")) + }, + :refresh_value => proc { |control, editor| + if ["User", "Target", "SE"].include?(editor.anim[:particles][editor.particle_index][:name]) + control.disable + else + control.enable + end + }, + :apply_value => proc { |value, editor| + if editor.confirm_message(_INTL("Are you sure you want to delete this particle?")) + p_index = editor.particle_index + AnimationEditor::ParticleDataHelper.delete_particle(editor.anim[:particles], p_index) + editor.components[:particle_list].delete_particle(p_index) + editor.components[:particle_list].set_particles(editor.anim[:particles]) + editor.components[:particle_list].keyframe = 0 if editor.anim[:particles][p_index][:name] == "SE" + editor.refresh + end + } +}) + +# TODO: Various ways to bulk shift this particle's commands earlier/later. + +#=============================================================================== +# NOTE: keyframe_pane is currently inaccessible (intentionally). If it will have +# its own commands and should be accessible again, change def +# on_mouse_release in ParticleList. +#=============================================================================== +# AnimationEditor::SidePanes.add_property(:keyframe_pane, :header, { +# :new => proc { |pane, editor| +# pane.add_header_label(:header, _INTL("Edit keyframe")) +# } +# }) + +# TODO: Various command-shifting options (insert/delete keyframe). diff --git a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb index b782057ea..544364905 100644 --- a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb +++ b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb @@ -187,6 +187,19 @@ module AnimationEditor::ParticleDataHelper return particle[property]&.any? { |cmd| (cmd[0] == frame) || (cmd[0] + cmd[1] == frame) } end + def has_se_command_at?(particles, frame) + ret = false + se_particle = particles.select { |ptcl| ptcl[:name] == "SE" }[0] + if se_particle + se_particle.each_pair do |prop, values| + next if !values.is_a?(Array) || values.length == 0 + ret = values.any? { |value| value[0] == frame } + break if ret + end + end + return ret + end + def add_command(particle, property, frame, value) # Return a new set of commands if there isn't one if !particle[property] || particle[property].empty? 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 7c92d0204..c697668fa 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 @@ -65,6 +65,7 @@ class AnimationEditor::Canvas < Sprite # Frame for other particles @frame_bitmap = Bitmap.new(64, 64) @frame_bitmap.outline_rect(1, 1, @frame_bitmap.width - 2, @frame_bitmap.height - 2, Color.new(0, 0, 0, 64)) + @battler_frame_sprites = [] @frame_sprites = [] end @@ -88,6 +89,8 @@ class AnimationEditor::Canvas < Sprite end end @particle_sprites.clear + @battler_frame_sprites.each { |s| s.dispose if s && !s.disposed? } + @battler_frame_sprites.clear @frame_sprites.each do |s| if s.is_a?(Array) s.each { |s2| s2.dispose if s2 && !s2.disposed? } @@ -213,9 +216,19 @@ class AnimationEditor::Canvas < Sprite def create_frame_sprite(index, sub_index = -1) if sub_index >= 0 - return if @frame_sprites[index] && @frame_sprites[index][sub_index] && !@frame_sprites[index][sub_index].disposed? + if @frame_sprites[index].is_a?(Array) + return if @frame_sprites[index][sub_index] && !@frame_sprites[index][sub_index].disposed? + else + @frame_sprites[index].dispose if @frame_sprites[index] && !@frame_sprites[index].disposed? + @frame_sprites[index] = [] + end else - return if @frame_sprites[index] && !@frame_sprites[index].disposed? + if @frame_sprites[index].is_a?(Array) + @frame_sprites[index].each { |s| s.dispose if s && !s.disposed? } + @frame_sprites[index] = nil + else + return if @frame_sprites[index] && !@frame_sprites[index].disposed? + end end sprite = Sprite.new(viewport) sprite.bitmap = @frame_bitmap @@ -231,27 +244,42 @@ class AnimationEditor::Canvas < Sprite end # TODO: Create shadow sprites? + # TODO: Make this also refresh if the layout of the battle changes (i.e. which + # battlers are the user/target). def ensure_battler_sprites - if !@side_size0 || @side_size0 != side_size(0) + if @sides_swapped.nil? || @sides_swapped != sides_swapped? || + !@side_size0 || @side_size0 != side_size(0) @battler_sprites.each_with_index { |s, i| s.dispose if i.even? && s && !s.disposed? } - idx_user = @anim[:particles].index { |particle| particle[:name] == "User" } + @battler_frame_sprites.each_with_index { |s, i| s.dispose if i.even? && s && !s.disposed? } @side_size0 = side_size(0) @side_size0.times do |i| - next if position_empty?(i * 2) + next if user_index != i * 2 && !target_indices.include?(i * 2) @battler_sprites[i * 2] = Sprite.new(self.viewport) - create_frame_sprite(idx_user) + frame_sprite = Sprite.new(viewport) + frame_sprite.bitmap = @frame_bitmap + frame_sprite.z = 99998 + frame_sprite.ox = @frame_bitmap.width / 2 + frame_sprite.oy = @frame_bitmap.height / 2 + @battler_frame_sprites[i * 2] = frame_sprite end end - if !@side_size1 || @side_size1 != side_size(1) + if @sides_swapped.nil? || @sides_swapped != sides_swapped? || + !@side_size1 || @side_size1 != side_size(1) @battler_sprites.each_with_index { |s, i| s.dispose if i.odd? && s && !s.disposed? } - idx_target = @anim[:particles].index { |particle| particle[:name] == "Target" } + @battler_frame_sprites.each_with_index { |s, i| s.dispose if i.odd? && s && !s.disposed? } @side_size1 = side_size(1) @side_size1.times do |i| - next if position_empty?((i * 2) + 1) + next if user_index != (i * 2) + 1 && !target_indices.include?((i * 2) + 1) @battler_sprites[(i * 2) + 1] = Sprite.new(self.viewport) - create_frame_sprite(idx_target, (i * 2) + 1) + frame_sprite = Sprite.new(viewport) + frame_sprite.bitmap = @frame_bitmap + frame_sprite.z = 99998 + frame_sprite.ox = @frame_bitmap.width / 2 + frame_sprite.oy = @frame_bitmap.height / 2 + @battler_frame_sprites[(i * 2) + 1] = frame_sprite end end + @sides_swapped = sides_swapped? end def refresh_battler_graphics @@ -300,8 +328,6 @@ 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) @@ -309,8 +335,6 @@ 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 @@ -330,14 +354,12 @@ class AnimationEditor::Canvas < Sprite spr = @battler_sprites[user_index] raise _INTL("Sprite for particle {1} not found somehow (battler index {2}).", particle[:name], user_index) if !spr - idx_user = @anim[:particles].index { |particle| particle[:name] == "User" } - frame = @frame_sprites[idx_user] + frame = @battler_frame_sprites[user_index] when "Target" spr = @battler_sprites[target_idx] raise _INTL("Sprite for particle {1} not found somehow (battler index {2}).", particle[:name], target_idx) if !spr - idx_target = @anim[:particles].index { |particle| particle[:name] == "Target" } - frame = @frame_sprites[idx_target][target_idx] + frame = @battler_frame_sprites[target_idx] else create_particle_sprite(index, target_idx) if target_idx >= 0 @@ -572,6 +594,18 @@ class AnimationEditor::Canvas < Sprite # Find closest particle to mouse nearest_index = -1 nearest_distance = -1 + @battler_frame_sprites.each_with_index do |sprite, index| + next if !sprite || !sprite.visible + next if !mouse_in_sprite?(sprite, mouse_x, mouse_y) + dist = (sprite.x - mouse_x) ** 2 + (sprite.y - mouse_y) ** 2 + next if nearest_distance >= 0 && nearest_distance < dist + if index == user_index + nearest_index = @anim[:particles].index { |particle| particle[:name] == "User" } + else + nearest_index = @anim[:particles].index { |particle| particle[:name] == "Target" } + end + nearest_distance = dist + end @frame_sprites.each_with_index do |sprite, index| sprites = (sprite.is_a?(Array)) ? sprite : [sprite] sprites.each do |spr|