From f0fae4b9ecfc898d8d9c69f0524119346287a878 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Thu, 29 Feb 2024 00:54:01 +0000 Subject: [PATCH] Anim Editor: added NoUser property, added buttons to duplicate/delete particle and delete single commands --- .../801_UI controls/002_ControlsContainer.rb | 5 +- .../Control elements/006_NumberTextBox.rb | 28 ++-- .../Control elements/007b_BitmapButton.rb | 12 +- .../902_Anim GameData/001_Animation.rb | 10 +- .../903_Anim Compiler/001_Anim compiler.rb | 21 ++- .../904_Anim Editor/001_AnimationEditor.rb | 139 +++++++++++++----- .../002_AnimationEditor_popups.rb | 1 + .../904_Anim Editor/901_ParticleDataHelper.rb | 64 ++++++++ .../Anim Editor elements/001_Canvas.rb | 2 +- .../Anim Editor elements/003_ParticleList.rb | 9 +- 10 files changed, 229 insertions(+), 62 deletions(-) diff --git a/Data/Scripts/801_UI controls/002_ControlsContainer.rb b/Data/Scripts/801_UI controls/002_ControlsContainer.rb index 1978ef8fb..2151ae4dd 100644 --- a/Data/Scripts/801_UI controls/002_ControlsContainer.rb +++ b/Data/Scripts/801_UI controls/002_ControlsContainer.rb @@ -14,13 +14,14 @@ class UIControls::ControlsContainer OFFSET_FROM_LABEL_X = 90 OFFSET_FROM_LABEL_Y = 0 - def initialize(x, y, width, height) + def initialize(x, y, width, height, right_margin = 0) @viewport = Viewport.new(x, y, width, height) @viewport.z = 99999 @x = x @y = y @width = width @height = height + @right_margin = right_margin @label_offset_x = OFFSET_FROM_LABEL_X @label_offset_y = OFFSET_FROM_LABEL_Y @controls = [] @@ -193,7 +194,7 @@ class UIControls::ControlsContainer def control_size(has_label = false) if has_label - return @width - @label_offset_x, LINE_SPACING - @label_offset_y + return @width - @label_offset_x - @right_margin, LINE_SPACING - @label_offset_y end return @width, LINE_SPACING end diff --git a/Data/Scripts/801_UI controls/Control elements/006_NumberTextBox.rb b/Data/Scripts/801_UI controls/Control elements/006_NumberTextBox.rb index 6316a1c27..a4886bd43 100644 --- a/Data/Scripts/801_UI controls/Control elements/006_NumberTextBox.rb +++ b/Data/Scripts/801_UI controls/Control elements/006_NumberTextBox.rb @@ -41,9 +41,14 @@ class UIControls::NumberTextBox < UIControls::TextBox self.invalidate end - # TODO: If current value is 0, replace it with ch instead of inserting ch? - def insert_char(ch) - self.value = @value.to_s.insert(@cursor_pos, ch).to_i + def insert_char(ch, index = -1) + old_val = @value + if @value == 0 + @value = ch.to_i + else + self.value = @value.to_s.insert((index >= 0) ? index : @cursor_pos, ch).to_i + end + return if @value == old_val @cursor_pos += 1 @cursor_pos = @cursor_pos.clamp(0, @value.to_s.length) @cursor_timer = System.uptime @@ -107,17 +112,20 @@ class UIControls::NumberTextBox < UIControls::TextBox def update_text_entry ret = false Input.gets.each_char do |ch| - next if !["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-"].include?(ch) - if ch == "-" - next if @min_value >= 0 || @cursor_pos > 1 || (@cursor_pos > 0 && @value >= 0) - if @value < 0 + case ch + when "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" + insert_char(ch) + ret = true + when "-", "+" + if @value > 0 && @min_value < 0 && ch == "-" + insert_char(ch, 0) # Add a negative sign at the start + ret = true + elsif @value < 0 delete_at(0) # Remove the negative sign ret = true - next end + next end - insert_char(ch) - ret = true end return ret end diff --git a/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb b/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb index 7b3b258bf..443590016 100644 --- a/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb +++ b/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb @@ -4,11 +4,12 @@ class UIControls::BitmapButton < UIControls::Button BUTTON_PADDING = 4 - def initialize(x, y, viewport, button_bitmap) + def initialize(x, y, viewport, button_bitmap, disabled_bitmap = nil) super(button_bitmap.width + (BUTTON_PADDING * 2), button_bitmap.height + (BUTTON_PADDING * 2), viewport) self.x = x self.y = y @button_bitmap = button_bitmap + @disabled_bitmap = disabled_bitmap end def set_interactive_rects @@ -24,7 +25,12 @@ class UIControls::BitmapButton < UIControls::Button def refresh super # Draw button bitmap - self.bitmap.blt(BUTTON_PADDING, BUTTON_PADDING, @button_bitmap, - Rect.new(0, 0, @button_bitmap.width, @button_bitmap.height)) + if @disabled_bitmap && disabled? + self.bitmap.blt(BUTTON_PADDING, BUTTON_PADDING, @disabled_bitmap, + Rect.new(0, 0, @disabled_bitmap.width, @disabled_bitmap.height)) + else + self.bitmap.blt(BUTTON_PADDING, BUTTON_PADDING, @button_bitmap, + Rect.new(0, 0, @button_bitmap.width, @button_bitmap.height)) + end end end diff --git a/Data/Scripts/902_Anim GameData/001_Animation.rb b/Data/Scripts/902_Anim GameData/001_Animation.rb index 5c32f614f..c83991760 100644 --- a/Data/Scripts/902_Anim GameData/001_Animation.rb +++ b/Data/Scripts/902_Anim GameData/001_Animation.rb @@ -4,6 +4,7 @@ module GameData attr_reader :move # Either the move's ID or the common animation's name (both are strings) attr_reader :version # Hit number attr_reader :name # Shown in the sublist; cosmetic only + attr_reader :no_user # Whether there is no "User" particle (false by default) attr_reader :no_target # Whether there is no "Target" particle (false by default) attr_reader :ignore # Whether the animation can't be played in battle attr_reader :flags @@ -14,7 +15,7 @@ module GameData DATA_FILENAME = "animations.dat" OPTIONAL = true - # TODO: All mentions of focus types can be found by searching for + # NOTE: All mentions of focus types can be found by searching for # :user_and_target, plus there's :foreground in PARTICLE_DEFAULT_VALUES # below. # TODO: Add :user_ground, :target_ground? @@ -30,6 +31,9 @@ module GameData "TargetSide" => :target_side_foreground, "TargetSideBackground" => :target_side_background, } + FOCUS_TYPES_WITH_USER = [ + :user, :user_and_target, :user_side_foreground, :user_side_background + ] FOCUS_TYPES_WITH_TARGET = [ :target, :user_and_target, :target_side_foreground, :target_side_background ] @@ -48,6 +52,7 @@ module GameData "SectionName" => [:id, "esU", {"Move" => :move, "OppMove" => :opp_move, "Common" => :common, "OppCommon" => :opp_common}], "Name" => [:name, "s"], + "NoUser" => [:no_user, "b"], "NoTarget" => [:no_target, "b"], "Ignore" => [:ignore, "b"], # TODO: Boolean for whether the animation will be played if the target is @@ -184,6 +189,7 @@ module GameData ret[:move] = move if !move.nil? ret[:version] = 0 ret[:name] = _INTL("New animation") + ret[:no_user] = false ret[:no_target] = false ret[:ignore] = false ret[:particles] = [ @@ -202,6 +208,7 @@ module GameData @move = hash[:move] @version = hash[:version] || 0 @name = hash[:name] + @no_user = hash[:no_user] || false @no_target = hash[:no_target] || false @ignore = hash[:ignore] || false @particles = hash[:particles] || [] @@ -217,6 +224,7 @@ module GameData ret[:move] = @move ret[:version] = @version ret[:name] = @name + ret[:no_user] = @no_user ret[:no_target] = @no_target ret[:ignore] = @ignore ret[:particles] = [] # Clone the @particles array, which is nested hashes and arrays diff --git a/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb b/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb index aeb088893..fb61aadab 100644 --- a/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb +++ b/Data/Scripts/903_Anim Compiler/001_Anim compiler.rb @@ -94,12 +94,21 @@ module Compiler hash[:type] = hash[:id][0] hash[:move] = hash[:id][1] hash[:version] = hash[:id][2] || 0 + # Ensure there is at most one each of "User", "Target" and "SE" particles + ["User", "Target", "SE"].each do |type| + next if hash[:particles].count { |particle| particle[:name] == type } <= 1 + raise _INTL("Animation has more than 1 \"{1}\" particle, which isn't allowed.", type) + "\n" + FileLineData.linereport + end + # Ensure there is no "User" particle if "NoUser" is set + if hash[:particles].any? { |particle| particle[:name] == "User" } && hash[:no_user] + raise _INTL("Can't define a \"User\" particle and also set property \"NoUser\" to true.") + "\n" + FileLineData.linereport + end # Ensure there is no "Target" particle if "NoTarget" is set if hash[:particles].any? { |particle| particle[:name] == "Target" } && hash[:no_target] raise _INTL("Can't define a \"Target\" particle and also set property \"NoTarget\" to true.") + "\n" + FileLineData.linereport end - # Create "User", "SE" and "Target" particles if they don't exist but should - if hash[:particles].none? { |particle| particle[:name] == "User" } + # Create "User", "Target" and "SE" particles if they don't exist but should + if hash[:particles].none? { |particle| particle[:name] == "User" } && !hash[:no_user] hash[:particles].push({:name => "User"}) end if hash[:particles].none? { |particle| particle[:name] == "Target" } && !hash[:no_target] @@ -145,8 +154,12 @@ module Compiler when "Target" then particle[:graphic] = "TARGET" end end - # TODO: Ensure that particles don't have a focus involving a user if the - # animation itself doesn't involve a user. + # Ensure that particles don't have a focus involving a user if the + # animation itself doesn't involve a user + if hash[:no_user] && 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 + end # Ensure that particles don't have a focus involving a target if the # animation itself doesn't involve a target if hash[:no_target] && GameData::Animation::FOCUS_TYPES_WITH_TARGET.include?(particle[:focus]) diff --git a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb index 386e9743d..49487c746 100644 --- a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb +++ b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb @@ -2,10 +2,9 @@ # #=============================================================================== class AnimationEditor - WINDOW_WIDTH = Settings::SCREEN_WIDTH + (32 * 10) - WINDOW_HEIGHT = Settings::SCREEN_HEIGHT + (32 * 10) - BORDER_THICKNESS = 4 + WINDOW_WIDTH = Settings::SCREEN_WIDTH + 328 + (BORDER_THICKNESS * 4) + WINDOW_HEIGHT = Settings::SCREEN_HEIGHT + 320 + (BORDER_THICKNESS * 4) # Components MENU_BAR_WIDTH = WINDOW_WIDTH @@ -25,6 +24,7 @@ class AnimationEditor SIDE_PANE_Y = CANVAS_Y SIDE_PANE_WIDTH = WINDOW_WIDTH - SIDE_PANE_X - BORDER_THICKNESS SIDE_PANE_HEIGHT = CANVAS_HEIGHT + PLAY_CONTROLS_HEIGHT + (BORDER_THICKNESS * 2) + SIDE_PANE_DELETE_MARGIN = 32 PARTICLE_LIST_X = BORDER_THICKNESS PARTICLE_LIST_Y = SIDE_PANE_Y + SIDE_PANE_HEIGHT + (BORDER_THICKNESS * 2) @@ -82,6 +82,9 @@ class AnimationEditor "Swamp", "SwampOpp", "Toxic", "UseItem", "WideGuard", "Wrap" ] + DELETABLE_COMMAND_PANE_PROPERTIES = [ + :x, :y, :z, :frame, :visible, :opacity, :zoom_x, :zoom_y, :angle, :flip, :blending + ] def initialize(anim_id, anim) load_settings @@ -113,7 +116,8 @@ class AnimationEditor @components[:canvas] = AnimationEditor::Canvas.new(@canvas_viewport, @anim, @settings) # Side panes [:commands_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) + @components[pane] = UIControls::ControlsContainer.new(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT, + (pane == :commands_pane) ? SIDE_PANE_DELETE_MARGIN : 0) end # TODO: Make a side pane for colour/tone editor (accessed from # @components[:commands_pane] via a button; has Apply/Cancel buttons @@ -228,6 +232,27 @@ class AnimationEditor # commands_pane.add_labelled_button(:masking, _INTL("Masking"), _INTL("Edit")) # TODO: Add buttons that shift all commands from the current keyframe and # later forwards/backwards in time? + # Add all "delete" buttons + 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 + DELETABLE_COMMAND_PANE_PROPERTIES.each do |property| + parent = commands_pane.get_control(property) + btn = UIControls::BitmapButton.new(parent.x + parent.width + 6, parent.y + 2, + commands_pane.viewport, delete_bitmap, delete_disabled_bitmap) + btn.set_interactive_rects + commands_pane.controls.push([(property.to_s + "_delete").to_sym, btn]) + end end def set_se_pane_contents @@ -260,8 +285,10 @@ class AnimationEditor particle_pane.add_labelled_dropdown_list(:focus, _INTL("Focus"), {}, :undefined) # FlipIfFoe # RotateIfFoe - # Delete button (if not "User"/"Target"/"SE") # Duplicate button + particle_pane.add_button(:duplicate, _INTL("Duplicate this particle")) + # Delete button (if not "User"/"Target"/"SE") + 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 @@ -308,6 +335,8 @@ class AnimationEditor # Create filepath controls # TODO: Have two TextBoxes, one for folder and one for filename? anim_properties.add_labelled_text_box(:pbs_path, _INTL("PBS filepath"), "") + # Create "involves a user" control + anim_properties.add_labelled_checkbox(:has_user, _INTL("Involves a user?"), true) # Create "involves a target" control anim_properties.add_labelled_checkbox(:has_target, _INTL("Involves a target?"), true) # Create flags control @@ -499,6 +528,14 @@ class AnimationEditor 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 :se_pane se_particle = @anim[:particles].select { |p| p[:name] == "SE" }[0] kyfrm = keyframe @@ -554,32 +591,39 @@ class AnimationEditor component.get_control(:graphic).enable component.get_control(:focus).enable end - # Set the possible foci depending on whether the animation involves a - # target - # TODO: Also filter for user/no user if implemented. - if @anim[:no_target] - component.get_control(:focus).values = { - :foreground => _INTL("Foreground"), - :midground => _INTL("Midground"), - :background => _INTL("Background"), - :user => _INTL("User"), - :user_side_foreground => _INTL("In front of user's side"), - :user_side_background => _INTL("Behind user's side") - } + # Enable/disable the Duplicate button + if ["SE"].include?(@anim[:particles][particle_index][:name]) + component.get_control(:duplicate).disable else - component.get_control(: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") - } + 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) @@ -647,11 +691,17 @@ class AnimationEditor echoln "Color/Tone button clicked" else particle = @anim[:particles][particle_index] - new_cmds = AnimationEditor::ParticleDataHelper.add_command(particle, property, keyframe, value) - if new_cmds - particle[property] = new_cmds + 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 - particle.delete(property) + 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 @@ -707,6 +757,18 @@ class AnimationEditor refresh_component(:particle_pane) refresh_component(:canvas) end + when :duplicate + AnimationEditor::ParticleDataHelper.duplicate_particle(@anim[:particles], particle_index) + @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].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) @@ -762,10 +824,17 @@ class AnimationEditor when :pbs_path txt = value.gsub!(/\.txt$/, "") @anim[property] = txt + when :has_user + @anim[:no_user] = !value + # TODO: Add/delete the "User" particle accordingly, and change the foci + # of any other particle involving a user. Then refresh a lot of + # components. + refresh_component(:canvas) when :has_target @anim[:no_target] = !value - # TODO: Add/delete the "Target" particle accordingly. Then refresh a lot - # of components. + # TODO: Add/delete the "Target" particle accordingly, and change the + # foci of any other particle involving a target. Then refresh a + # lot of components. refresh_component(:canvas) when :usable @anim[:ignore] = !value diff --git a/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb b/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb index 8612e5e70..2912f2302 100644 --- a/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb +++ b/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb @@ -96,6 +96,7 @@ class AnimationEditor anim_properties.get_control(:version).value = @anim[:version] || 0 anim_properties.get_control(:name).value = @anim[:name] || "" anim_properties.get_control(:pbs_path).value = (@anim[:pbs_path] || "unsorted") + ".txt" + anim_properties.get_control(:has_user).value = !@anim[:no_user] anim_properties.get_control(:has_target).value = !@anim[:no_target] anim_properties.get_control(:usable).value = !(@anim[:ignore] || false) # TODO: Populate flags. diff --git a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb index 94ee451dd..87fc8292d 100644 --- a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb +++ b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb @@ -167,6 +167,10 @@ module AnimationEditor::ParticleDataHelper particle[property] = value end + def has_command_at?(particle, property, frame) + return particle[property]&.any? { |cmd| (cmd[0] == frame) || (cmd[0] + cmd[1] == frame) } + end + def add_command(particle, property, frame, value) # Split particle[property] into values and interpolation arrays set_points = [] # All SetXYZ commands (the values thereof) @@ -244,6 +248,44 @@ module AnimationEditor::ParticleDataHelper return (ret.empty?) ? nil : ret end + # Cases: + # * SetXYZ - delete it + # * MoveXYZ start - turn into a SetXYZ at the end point + # * MoveXYZ end - delete it (this may happen to remove the start diamond too) + # * MoveXYZ end and start - merge both together (use first's type) + # * SetXYZ and MoveXYZ start - delete SetXYZ (leave MoveXYZ alone) + # * SetXYZ and MoveXYZ end - (unlikely) delete both + # * SetXYZ and MoveXYZ start and end - (unlikely) delete SetXYZ, merge Moves together + def delete_command(particle, property, frame) + # Find all relevant commands + set_now = nil + move_ending_now = nil + move_starting_now = nil + particle[property].each do |cmd| + if cmd[1] == 0 + set_now = cmd if cmd[0] == frame + else + move_starting_now = cmd if cmd[0] == frame + move_ending_now = cmd if cmd[0] + cmd[1] == frame + end + end + # Delete SetXYZ if it is at frame + particle[property].delete(set_now) if set_now + # Edit/delete MoveXYZ commands starting/ending at frame + if move_ending_now && move_starting_now # Merge both MoveXYZ commands + move_ending_now[1] += move_starting_now[1] + particle[property].delete(move_starting_now) + elsif move_ending_now # Delete MoveXYZ ending now + particle[property].delete(move_ending_now) + elsif move_starting_now && !set_now # Turn into SetXYZ at its end point + move_starting_now[0] += move_starting_now[1] + move_starting_now[1] = 0 + move_starting_now[3] = nil + move_starting_now.compact! + end + return (particle[property].empty?) ? nil : particle[property] + end + #----------------------------------------------------------------------------- def get_se_display_text(property, value) @@ -336,7 +378,29 @@ module AnimationEditor::ParticleDataHelper particles.insert(index, new_particle) end + # Copies the particle at index and inserts the copy immediately after that + # index. + def duplicate_particle(particles, index) + new_particle = {} + particles[index].each_pair do |key, value| + if value.is_a?(Array) + new_particle[key] = [] + value.each { |cmd| new_particle[key].push(cmd.clone) } + else + new_particle[key] = value.clone + end + end + new_particle[:name] += " (copy)" + particles.insert(index + 1, new_particle) + end + def swap_particles(particles, index1, index2) particles[index1], particles[index2] = particles[index2], particles[index1] end + + # Deletes the particle at the given index + def delete_particle(particles, index) + particles[index] = nil + particles.compact! + end end 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 abec007c1..84b69755b 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 @@ -430,7 +430,7 @@ class AnimationEditor::Canvas < Sprite end @anim[:particles].each_with_index do |particle, i| if GameData::Animation::FOCUS_TYPES_WITH_TARGET.include?(particle[:focus]) - refresh_particle(i) + refresh_particle(i) # Because there can be multiple targets else refresh_sprite(i) if particle[:name] != "SE" end diff --git a/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb b/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb index a24a29bed..6ece125ee 100644 --- a/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb +++ b/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb @@ -7,7 +7,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl TIMELINE_HEIGHT = 24 - VIEWPORT_SPACING LIST_X = 0 LIST_Y = TIMELINE_HEIGHT + VIEWPORT_SPACING - LIST_WIDTH = 150 - VIEWPORT_SPACING + LIST_WIDTH = 180 - VIEWPORT_SPACING COMMANDS_X = LIST_WIDTH + VIEWPORT_SPACING COMMANDS_Y = LIST_Y @@ -83,7 +83,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl @particle_line_sprite.oy = @particle_line_sprite.height / 2 @particle_line_sprite.bitmap.fill_rect(0, 0, @particle_line_sprite.bitmap.width, @particle_line_sprite.bitmap.height, Color.red) # Buttons and button bitmaps - initialze_button_bitmaps + initialize_button_bitmaps @controls = [] add_particle_button = UIControls::BitmapButton.new(x + 1, y + 1, viewport, @add_button_bitmap) add_particle_button.set_interactive_rects @@ -113,7 +113,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl @commands = {} end - def initialze_button_bitmaps + def initialize_button_bitmaps @add_button_bitmap = Bitmap.new(12, 12) @add_button_bitmap.fill_rect(1, 5, 10, 2, TEXT_COLOR) @add_button_bitmap.fill_rect(5, 1, 2, 10, TEXT_COLOR) @@ -529,9 +529,6 @@ class AnimationEditor::ParticleList < UIControls::BaseControl end end - # TODO: Add indicator that this is selected (if so). Some kind of arrow on the - # left, or a red horizontal line (like the keyframe's vertical line), or - # fill_rect with colour instead of outline_rect? def refresh_particle_list_sprite(index) spr = @list_sprites[index] return if !spr