Anim Editor: added filter text box to selection screen, disabled animations are listed in red

This commit is contained in:
Maruno17
2024-03-31 23:19:31 +01:00
parent 76e2b5a4fb
commit 1977bd866c
5 changed files with 208 additions and 103 deletions

View File

@@ -146,10 +146,32 @@ class UIControls::List < UIControls::BaseControl
)
end
txt = (val.is_a?(Array)) ? val[1] : val
text_color = TEXT_COLOR
if txt[/^\\c\[([0-9]+)\]/i]
text_colors = [
[ 0, 112, 248], [120, 184, 232], # 1 Blue
[232, 32, 16], [248, 168, 184], # 2 Red
[ 96, 176, 72], [174, 208, 144], # 3 Green
[ 72, 216, 216], [168, 224, 224], # 4 Cyan
[208, 56, 184], [232, 160, 224], # 5 Magenta
[232, 208, 32], [248, 232, 136], # 6 Yellow
[160, 160, 168], [208, 208, 216], # 7 Gray
[240, 240, 248], [200, 200, 208], # 8 White
[114, 64, 232], [184, 168, 224], # 9 Purple
[248, 152, 24], [248, 200, 152], # 10 Orange
MessageConfig::DARK_TEXT_MAIN_COLOR,
MessageConfig::DARK_TEXT_SHADOW_COLOR, # 11 Dark default
MessageConfig::LIGHT_TEXT_MAIN_COLOR,
MessageConfig::LIGHT_TEXT_SHADOW_COLOR # 12 Light default
]
self.bitmap.font.color = Color.new(*text_colors[2 * ($1.to_i - 1)])
txt = txt.gsub(/^\\c\[[0-9]+\]/i, "")
end
draw_text(self.bitmap,
@interactions[i].x + TEXT_PADDING_X,
@interactions[i].y + TEXT_OFFSET_Y - (@top_row * ROW_HEIGHT),
txt)
self.bitmap.font.color = TEXT_COLOR
end
end

View File

@@ -154,26 +154,34 @@ module Compiler
when "Target" then particle[:graphic] = "TARGET"
end
end
# Ensure that particles don't have a focus involving a user, and the
# animation doesn't play a user's cry, if the animation itself doesn't
# involve a user
# If the animation doesn't involve a user, ensure that particles don't
# have a focus/graphic that involves a user, and that the animation
# doesn't play a user's cry
if hash[:no_user]
if GameData::Animation::FOCUS_TYPES_WITH_USER.include?(particle[:focus])
raise _INTL("Particle \"{1}\" can't have a \"Focus\" that involves a user if property \"NoUser\" is set to true.",
particle[:name]) + "\n" + FileLineData.linereport
end
if ["USER", "USER_OPP", "USER_FRONT", "USER_BACK"].include?(particle[:graphic])
raise _INTL("Particle \"{1}\" can't have a \"Graphic\" that involves a user if property \"NoUser\" is set to true.",
particle[:name]) + "\n" + FileLineData.linereport
end
if particle[:name] == "SE" && particle[:user_cry] && !particle[:user_cry].empty?
raise _INTL("Animation can't play the user's cry if property \"NoUser\" is set to true.") + "\n" + FileLineData.linereport
end
end
# Ensure that particles don't have a focus involving a target, and the
# animation doesn't play a target's cry, if the animation itself doesn't
# involve a target
# If the animation doesn't involve a target, ensure that particles don't
# have a focus/graphic that involves a target, and that the animation
# doesn't play a target's cry
if hash[:no_target]
if GameData::Animation::FOCUS_TYPES_WITH_TARGET.include?(particle[:focus])
raise _INTL("Particle \"{1}\" can't have a \"Focus\" that involves a target if property \"NoTarget\" is set to true.",
particle[:name]) + "\n" + FileLineData.linereport
end
if ["TARGET", "TARGET_OPP", "TARGET_FRONT", "TARGET_BACK"].include?(particle[:graphic])
raise _INTL("Particle \"{1}\" can't have a \"Graphic\" that involves a target if property \"NoTarget\" is set to true.",
particle[:name]) + "\n" + FileLineData.linereport
end
if particle[:name] == "SE" && particle[:target_cry] && !particle[:target_cry].empty?
raise _INTL("Animation can't play the target's cry if property \"NoTarget\" is set to true.") + "\n" + FileLineData.linereport
end

View File

@@ -191,6 +191,38 @@ class AnimationEditor
return @components[:particle_list].particle_index
end
def load_settings
# TODO: Load these from a saved file.
@settings = {
:side_sizes => [1, 1],
:user_index => 0,
:target_indices => [1],
:user_opposes => false,
# TODO: Ideally be able to independently choose base graphics, which will
# be a separate setting here.
:canvas_bg => "indoor1",
# NOTE: These sprite names are also used in Pokemon.play_cry and so should
# be a species ID (being a string is fine).
:user_sprite_name => "ARCANINE",
:target_sprite_name => "CHARIZARD"
}
end
def save
GameData::Animation.register(@anim, @anim_id)
Compiler.write_battle_animation_file(@anim[:pbs_path])
if @anim[:pbs_path] != @pbs_path
if GameData::Animation::DATA.any? { |_key, anim| anim.pbs_path == @pbs_path }
Compiler.write_battle_animation_file(@pbs_path)
elsif FileTest.exist?("PBS/Animations/" + @pbs_path + ".txt")
File.delete("PBS/Animations/" + @pbs_path + ".txt")
end
@pbs_path = @anim[:pbs_path]
end
end
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Returns the animation's name for display in the menu bar and elsewhere.
@@ -474,38 +506,6 @@ class AnimationEditor
#-----------------------------------------------------------------------------
def load_settings
# TODO: Load these from a saved file.
@settings = {
:side_sizes => [1, 1],
:user_index => 0,
:target_indices => [1],
:user_opposes => false,
# TODO: Ideally be able to independently choose base graphics, which will
# be a separate setting here.
:canvas_bg => "indoor1",
# NOTE: These sprite names are also used in Pokemon.play_cry and so should
# be a species ID (being a string is fine).
:user_sprite_name => "ARCANINE",
:target_sprite_name => "CHARIZARD"
}
end
def save
GameData::Animation.register(@anim, @anim_id)
Compiler.write_battle_animation_file(@anim[:pbs_path])
if @anim[:pbs_path] != @pbs_path
if GameData::Animation::DATA.any? { |_key, anim| anim.pbs_path == @pbs_path }
Compiler.write_battle_animation_file(@pbs_path)
elsif FileTest.exist?("PBS/Animations/" + @pbs_path + ".txt")
File.delete("PBS/Animations/" + @pbs_path + ".txt")
end
@pbs_path = @anim[:pbs_path]
end
end
#-----------------------------------------------------------------------------
def draw_editor_background
# Fill the whole screen with white
@screen_bitmap.bitmap.fill_rect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, Color.white)

View File

@@ -3,6 +3,8 @@
#===============================================================================
class AnimationEditor::AnimationSelector
BORDER_THICKNESS = 4
LABEL_OFFSET_X = -4 # Position of label relative to what they're labelling
LABEL_OFFSET_Y = -32
QUIT_BUTTON_WIDTH = 80
QUIT_BUTTON_HEIGHT = 30
@@ -27,6 +29,11 @@ class AnimationEditor::AnimationSelector
ACTION_BUTTON_X = ANIMATIONS_LIST_X + ANIMATIONS_LIST_WIDTH + 4
ACTION_BUTTON_Y = TYPE_BUTTONS_Y + ((ANIMATIONS_LIST_HEIGHT - (ACTION_BUTTON_HEIGHT * 3)) / 2) + 4
FILTER_BOX_WIDTH = ACTION_BUTTON_WIDTH
FILTER_BOX_HEIGHT = UIControls::TextBox::TEXT_BOX_HEIGHT
FILTER_BOX_X = ACTION_BUTTON_X
FILTER_BOX_Y = MOVES_LIST_Y
# Pop-up window
MESSAGE_BOX_WIDTH = AnimationEditor::WINDOW_WIDTH * 3 / 4
MESSAGE_BOX_HEIGHT = 160
@@ -35,34 +42,35 @@ class AnimationEditor::AnimationSelector
MESSAGE_BOX_SPACING = 16
def initialize
generate_lists
@animation_type = 0 # 0=move, 1=common
@filter_text = ""
@quit = false
generate_full_lists
initialize_viewports
initialize_bitmaps
initialize_controls
refresh
end
def initialize_viewports
@viewport = Viewport.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT)
@viewport.z = 99999
@pop_up_viewport = Viewport.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT)
@pop_up_viewport.z = @viewport.z + 50
end
def initialize_bitmaps
# Background
@screen_bitmap = BitmapSprite.new(AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, @viewport)
# Semi-transparent black overlay to dim the screen while a pop-up window is open
@pop_up_bg_bitmap = BitmapSprite.new(AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, @pop_up_viewport)
@pop_up_bg_bitmap.z = -100
@pop_up_bg_bitmap.visible = false
# Draw in these bitmaps
draw_editor_background
@animation_type = 0 # 0=move, 1=common
@quit = false
create_controls
refresh
end
def dispose
@screen_bitmap.dispose
@pop_up_bg_bitmap.dispose
@components.dispose
@viewport.dispose
@pop_up_viewport.dispose
end
LABEL_OFFSET_X = -4
LABEL_OFFSET_Y = -32
def create_controls
def initialize_controls
@components = UIControls::ControlsContainer.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT)
# Quit button
btn = UIControls::Button.new(QUIT_BUTTON_WIDTH, QUIT_BUTTON_HEIGHT, @viewport, _INTL("Quit"))
@@ -82,11 +90,6 @@ class AnimationEditor::AnimationSelector
btn.set_fixed_size
@components.add_control_at(val[0], btn, TYPE_BUTTONS_X, TYPE_BUTTONS_Y + (i * TYPE_BUTTON_HEIGHT))
end
# TODO: Add filter text box for :moves_list's contents. Applies the filter
# upon every change to the text box's value. Perhaps it should only do
# so after 0.5 seconds of non-typing. What exactly should the filter
# be applied to? Animation's name, move's name (if there is one), what
# else? Flags?
# Moves list label
label = UIControls::Label.new(MOVES_LIST_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Moves"))
label.header = true
@@ -107,8 +110,25 @@ class AnimationEditor::AnimationSelector
btn.set_fixed_size
@components.add_control_at(val[0], btn, ACTION_BUTTON_X, ACTION_BUTTON_Y + (i * ACTION_BUTTON_HEIGHT))
end
# Filter text box
text_box = UIControls::TextBox.new(FILTER_BOX_WIDTH, FILTER_BOX_HEIGHT, @viewport, "")
@components.add_control_at(:filter, text_box, FILTER_BOX_X, FILTER_BOX_Y)
# Filter text box label
label = UIControls::Label.new(FILTER_BOX_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Filter text"))
label.header = true
@components.add_control_at(:filter_label, label, FILTER_BOX_X + LABEL_OFFSET_X, FILTER_BOX_Y + LABEL_OFFSET_Y)
end
def dispose
@screen_bitmap.dispose
@pop_up_bg_bitmap.dispose
@components.dispose
@viewport.dispose
@pop_up_viewport.dispose
end
#-----------------------------------------------------------------------------
def draw_editor_background
# Fill the whole screen with white
@screen_bitmap.bitmap.fill_rect(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, Color.white)
@@ -120,6 +140,8 @@ class AnimationEditor::AnimationSelector
areas.each do |area|
@screen_bitmap.bitmap.outline_rect(area[0] - 2, area[1] - 2, area[2] + 4, area[3] + 4, Color.black)
end
# Make the pop-up background semi-transparent
@pop_up_bg_bitmap.bitmap.fill_rect(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, Color.new(0, 0, 0, 128))
end
#-----------------------------------------------------------------------------
@@ -198,36 +220,72 @@ class AnimationEditor::AnimationSelector
#-----------------------------------------------------------------------------
def generate_lists
@move_list = []
@common_list = []
@move_animations = {}
@common_animations = {}
def generate_full_lists
@full_move_animations = {}
@full_common_animations = {}
GameData::Animation.keys.each do |id|
anim = GameData::Animation.get(id)
name = ""
name += "\\c[2]" if anim.ignore
name += _INTL("[Foe]") + " " if anim.opposing_animation?
name += "[#{anim.version}]" + " " if anim.version > 0
name += (anim.name || anim.move)
if anim.move_animation?
move_name = GameData::Move.try_get(anim.move)&.name || anim.move
@move_list.push([anim.move, move_name]) if !@move_animations[anim.move]
@move_animations[anim.move] ||= []
@move_animations[anim.move].push([id, name])
@full_move_animations[anim.move] ||= []
@full_move_animations[anim.move].push([id, name, move_name])
elsif anim.common_animation?
@common_list.push([anim.move, anim.move]) if !@common_animations[anim.move]
@common_animations[anim.move] ||= []
@common_animations[anim.move].push([id, name])
@full_common_animations[anim.move] ||= []
@full_common_animations[anim.move].push([id, name])
end
end
@full_move_animations.values.each do |val|
val.sort! { |a, b| a[1] <=> b[1] }
end
@full_common_animations.values.each do |val|
val.sort! { |a, b| a[1] <=> b[1] }
end
apply_list_filter
end
def apply_list_filter
# Apply filter
if @filter_text == ""
@move_animations = @full_move_animations.clone
@common_animations = @full_common_animations.clone
else
filter = @filter_text.downcase
@move_animations.clear
@full_move_animations.each_pair do |move, anims|
anims.each do |anim|
next if !anim[1].downcase.include?(filter) && !anim[2].downcase.include?(filter)
@move_animations[move] ||= []
@move_animations[move].push(anim)
end
end
@common_animations.clear
@full_common_animations.each_pair do |common, anims|
anims.each do |anim|
next if !anim[1].downcase.include?(filter) && !common.downcase.include?(filter)
@common_animations[common] ||= []
@common_animations[common].push(anim)
end
end
end
# Create move list from the filtered results
@move_list = []
@move_animations.each_pair do |move_id, anims|
@move_list.push([move_id, anims[0][2]])
end
@move_list.uniq!
@move_list.sort!
# Create common list from the filtered results
@common_list = []
@common_animations.each_pair do |move_id, anims|
@common_list.push([move_id, move_id])
end
@common_list.uniq!
@common_list.sort!
@move_animations.values.each do |val|
val.sort! { |a, b| a[1] <=> b[1] }
end
@common_animations.values.each do |val|
val.sort! { |a, b| a[1] <=> b[1] }
end
end
def selected_move_animations
@@ -284,7 +342,7 @@ class AnimationEditor::AnimationSelector
new_id = GameData::Animation.keys.max + 1
screen = AnimationEditor.new(new_id, new_anim)
screen.run
generate_lists
generate_full_lists
when :moves
@animation_type = 0
@components.get_control(:moves_list).selected = -1
@@ -298,7 +356,7 @@ class AnimationEditor::AnimationSelector
if anim_id
screen = AnimationEditor.new(anim_id, GameData::Animation.get(anim_id).clone_as_hash)
screen.run
generate_lists
generate_full_lists
end
when :copy
anim_id = selected_animation_id
@@ -308,7 +366,7 @@ class AnimationEditor::AnimationSelector
new_id = GameData::Animation.keys.max + 1
screen = AnimationEditor.new(new_id, new_anim)
screen.run
generate_lists
generate_full_lists
end
when :delete
anim_id = selected_animation_id
@@ -320,7 +378,7 @@ class AnimationEditor::AnimationSelector
elsif FileTest.exist?("PBS/Animations/" + pbs_path + ".txt")
File.delete("PBS/Animations/" + pbs_path + ".txt")
end
generate_lists
generate_full_lists
end
end
refresh
@@ -334,6 +392,13 @@ class AnimationEditor::AnimationSelector
end
@components.clear_changed
end
# Detect change to filter text
filter_ctrl = @components.get_control(:filter)
if filter_ctrl.value != @filter_text
@filter_text = filter_ctrl.value
apply_list_filter
refresh
end
end
def run

View File

@@ -300,6 +300,8 @@ class AnimationEditor::Canvas < Sprite
else
@particle_sprites[index].dispose if @particle_sprites[index] && !@particle_sprites[index].disposed?
@particle_sprites[index] = []
@frame_sprites[index].dispose if @frame_sprites[index] && !@frame_sprites[index].disposed?
@frame_sprites[index] = []
end
@particle_sprites[index][target_idx] = Sprite.new(self.viewport)
create_frame_sprite(index, target_idx)
@@ -307,6 +309,8 @@ class AnimationEditor::Canvas < Sprite
if @particle_sprites[index].is_a?(Array)
@particle_sprites[index].each { |s| s.dispose if s && !s.disposed? }
@particle_sprites[index] = nil
@frame_sprites[index].each { |s| s.dispose if s && !s.disposed? }
@frame_sprites[index] = nil
else
return if @particle_sprites[index] && !@particle_sprites[index].disposed?
end
@@ -502,7 +506,13 @@ class AnimationEditor::Canvas < Sprite
end
def refresh_particle(index)
target_indices.each { |target_idx| refresh_sprite(index, target_idx) }
one_per_side = [:target_side_foreground, :target_side_background].include?(@anim[:particles][index][:focus])
sides_covered = []
target_indices.each do |target_idx|
next if one_per_side && sides_covered.include?(target_idx % 2)
refresh_sprite(index, target_idx)
sides_covered.push(target_idx % 2)
end
end
def refresh_particle_frame