mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-07 13:15:01 +00:00
Anim Editor: added NoUser property, added buttons to duplicate/delete particle and delete single commands
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user