mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
Anim Editor: added changing of editor settings
This commit is contained in:
@@ -107,13 +107,13 @@ class UIControls::BaseControl < BitmapSprite
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def draw_text(this_bitmap, text_x, text_y, this_text)
|
||||
text_size = this_bitmap.text_size(this_text)
|
||||
this_bitmap.draw_text(text_x, text_y, text_size.width, text_size.height, this_text, 0)
|
||||
text_size = this_bitmap.text_size(this_text.to_s)
|
||||
this_bitmap.draw_text(text_x, text_y, text_size.width, text_size.height, this_text.to_s, 0)
|
||||
end
|
||||
|
||||
def draw_text_centered(this_bitmap, text_x, text_y, wid, this_text)
|
||||
text_size = this_bitmap.text_size(this_text)
|
||||
this_bitmap.draw_text(text_x, text_y, wid, text_size.height, this_text, 1)
|
||||
text_size = this_bitmap.text_size(this_text.to_s)
|
||||
this_bitmap.draw_text(text_x, text_y, wid, text_size.height, this_text.to_s, 1)
|
||||
end
|
||||
|
||||
# Redraws the control only if it is invalid.
|
||||
|
||||
@@ -155,7 +155,7 @@ class UIControls::List < UIControls::BaseControl
|
||||
SELECTED_ROW_COLOR
|
||||
)
|
||||
end
|
||||
txt = (val.is_a?(Array)) ? val[1] : val
|
||||
txt = (val.is_a?(Array)) ? val[1] : val.to_s
|
||||
text_color = TEXT_COLOR
|
||||
if txt[/^\\c\[([0-9]+)\]/i]
|
||||
text_colors = [
|
||||
|
||||
@@ -94,6 +94,12 @@ class AnimationEditor
|
||||
:tone_red, :tone_green, :tone_blue, :tone_gray
|
||||
]
|
||||
|
||||
DEBUG_SETTINGS_FILE_PATH = if File.directory?(System.data_directory)
|
||||
System.data_directory + "debug_settings.rxdata"
|
||||
else
|
||||
"./debug_settings.rxdata"
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def initialize(anim_id, anim)
|
||||
@@ -142,6 +148,26 @@ class AnimationEditor
|
||||
@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
|
||||
# Editor settings button bitmap
|
||||
@editor_settings_bitmap = Bitmap.new(18, 18)
|
||||
settings_array = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 1, 1, 1, 0, 0, 1, 1,
|
||||
0, 0, 1, 1, 1, 1, 0, 1, 1,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 0, 0, 0
|
||||
]
|
||||
settings_array.length.times do |i|
|
||||
next if settings_array[i] == 0
|
||||
@editor_settings_bitmap.fill_rect(i % 9, i / 9, 1, 1, Color.black)
|
||||
@editor_settings_bitmap.fill_rect(17 - (i % 9), i / 9, 1, 1, Color.black)
|
||||
@editor_settings_bitmap.fill_rect(i % 9, 17 - (i / 9), 1, 1, Color.black)
|
||||
@editor_settings_bitmap.fill_rect(17 - (i % 9), 17 - (i / 9), 1, 1, Color.black)
|
||||
end
|
||||
# Draw in these bitmaps
|
||||
draw_editor_background
|
||||
end
|
||||
@@ -174,6 +200,12 @@ class AnimationEditor
|
||||
)
|
||||
@components[:animation_properties].viewport.z = @pop_up_viewport.z + 1
|
||||
@components[:animation_properties].label_offset_x = 170
|
||||
# Editor settings pop-up window
|
||||
@components[:editor_settings] = UIControls::ControlsContainer.new(
|
||||
ANIM_PROPERTIES_X + 4, ANIM_PROPERTIES_Y, ANIM_PROPERTIES_WIDTH - 8, ANIM_PROPERTIES_HEIGHT
|
||||
)
|
||||
@components[:editor_settings].viewport.z = @pop_up_viewport.z + 1
|
||||
@components[:editor_settings].label_offset_x = 170
|
||||
# Graphic chooser pop-up window
|
||||
@components[:graphic_chooser] = UIControls::ControlsContainer.new(
|
||||
GRAPHIC_CHOOSER_X, GRAPHIC_CHOOSER_Y, GRAPHIC_CHOOSER_WINDOW_WIDTH, GRAPHIC_CHOOSER_WINDOW_HEIGHT
|
||||
@@ -191,6 +223,7 @@ class AnimationEditor
|
||||
@pop_up_bg_bitmap.dispose
|
||||
@delete_bitmap.dispose
|
||||
@delete_disabled_bitmap.dispose
|
||||
@editor_settings_bitmap.dispose
|
||||
@components.each_value { |c| c.dispose }
|
||||
@components.clear
|
||||
@viewport.dispose
|
||||
@@ -209,17 +242,26 @@ class AnimationEditor
|
||||
end
|
||||
|
||||
def load_settings
|
||||
@settings = {
|
||||
:side_sizes => [1, 1], # Player's side, opposing side
|
||||
:user_index => 0, # 0, 2, 4
|
||||
:target_indices => [1], # There must be at least one valid target
|
||||
:user_opposes => false,
|
||||
: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 => "DRAGONITE",
|
||||
:target_sprite_name => "CHARIZARD"
|
||||
}
|
||||
if File.file?(DEBUG_SETTINGS_FILE_PATH)
|
||||
@settings = SaveData.get_data_from_file(DEBUG_SETTINGS_FILE_PATH)[:anim_editor]
|
||||
else
|
||||
@settings = {
|
||||
:side_sizes => [1, 1], # Player's side, opposing side
|
||||
:user_index => 0, # 0, 2, 4
|
||||
:target_indices => [1], # There must be at least one valid target
|
||||
:user_opposes => false,
|
||||
: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 => "DRAGONITE",
|
||||
:target_sprite_name => "CHARIZARD"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def save_settings
|
||||
data = { :anim_editor => @settings }
|
||||
File.open(DEBUG_SETTINGS_FILE_PATH, "wb") { |file| Marshal.dump(data, file) }
|
||||
end
|
||||
|
||||
def save
|
||||
@@ -233,6 +275,7 @@ class AnimationEditor
|
||||
end
|
||||
@pbs_path = @anim[:pbs_path]
|
||||
end
|
||||
save_settings
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@@ -266,6 +309,7 @@ class AnimationEditor
|
||||
def set_menu_bar_contents
|
||||
@components[:menu_bar].add_button(:quit, _INTL("Quit"))
|
||||
@components[:menu_bar].add_button(:save, _INTL("Save"))
|
||||
@components[:menu_bar].add_settings_button(:settings, @editor_settings_bitmap)
|
||||
@components[:menu_bar].add_name_button(:name, get_animation_display_name)
|
||||
end
|
||||
|
||||
@@ -333,6 +377,51 @@ class AnimationEditor
|
||||
anim_properties.visible = false
|
||||
end
|
||||
|
||||
def set_editor_settings_contents
|
||||
editor_settings = @components[:editor_settings]
|
||||
editor_settings.add_header_label(:header, _INTL("Editor settings"))
|
||||
editor_settings.add_labelled_dropdown_list(:side_size_1, _INTL("Side sizes"), {
|
||||
1 => "1",
|
||||
2 => "2",
|
||||
3 => "3"
|
||||
}, 1)
|
||||
size_ctrl = editor_settings.get_control(:side_size_1)
|
||||
size_ctrl.box_width = 50
|
||||
size_ctrl.set_interactive_rects
|
||||
size_ctrl.invalidate
|
||||
ctrl = UIControls::Label.new(50, UIControls::ControlsContainer::LINE_SPACING, editor_settings.viewport, _INTL("vs."))
|
||||
editor_settings.add_control_at(:side_size_vs_label, ctrl, size_ctrl.x + 60, size_ctrl.y)
|
||||
ctrl = UIControls::DropdownList.new(54, UIControls::ControlsContainer::LINE_SPACING, editor_settings.viewport, {
|
||||
1 => "1",
|
||||
2 => "2",
|
||||
3 => "3"
|
||||
}, 1)
|
||||
ctrl.box_width = 50
|
||||
ctrl.invalidate
|
||||
editor_settings.add_control_at(:side_size_2, ctrl, size_ctrl.x + 93, size_ctrl.y)
|
||||
editor_settings.add_labelled_dropdown_list(:user_index, _INTL("User index"), {
|
||||
0 => "0",
|
||||
2 => "2",
|
||||
4 => "4"
|
||||
}, 0)
|
||||
ctrl = editor_settings.get_control(:user_index)
|
||||
ctrl.box_width = 50
|
||||
ctrl.set_interactive_rects
|
||||
ctrl.invalidate
|
||||
# TODO: I want a better control than this for choosing the target indices.
|
||||
editor_settings.add_labelled_text_box(:target_indices, _INTL("Target indices"), "")
|
||||
editor_settings.add_labelled_checkbox(:user_opposes, _INTL("User is opposing?"), false)
|
||||
editor_settings.add_labelled_dropdown_list(:canvas_bg, _INTL("Background graphic"), {}, "")
|
||||
editor_settings.add_labelled_dropdown_list(:user_sprite_name, _INTL("User graphic"), {}, "")
|
||||
ctrl = editor_settings.get_control(:user_sprite_name)
|
||||
ctrl.max_rows = 14
|
||||
editor_settings.add_labelled_dropdown_list(:target_sprite_name, _INTL("Target graphic"), {}, "")
|
||||
ctrl = editor_settings.get_control(:target_sprite_name)
|
||||
ctrl.max_rows = 14
|
||||
editor_settings.add_button(:close, _INTL("Close"))
|
||||
editor_settings.visible = false
|
||||
end
|
||||
|
||||
def set_graphic_chooser_contents
|
||||
graphic_chooser = @components[:graphic_chooser]
|
||||
graphic_chooser.add_header_label(:header, _INTL("Choose a file"))
|
||||
@@ -390,6 +479,7 @@ class AnimationEditor
|
||||
set_particle_list_contents
|
||||
set_play_controls_contents # Intentionally after set_particle_list_contents
|
||||
set_animation_properties_contents
|
||||
set_editor_settings_contents
|
||||
set_graphic_chooser_contents
|
||||
set_audio_chooser_contents
|
||||
end
|
||||
@@ -475,6 +565,28 @@ class AnimationEditor
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_editor_settings_options
|
||||
user_indices = { 0 => "0" }
|
||||
user_indices[2] = "2" if @settings[:side_sizes][0] >= 2
|
||||
user_indices[4] = "4" if @settings[:side_sizes][0] >= 3
|
||||
@components[:editor_settings].get_control(:user_index).values = user_indices
|
||||
# Canvas background graphic
|
||||
files = get_all_files_in_folder("Graphics/Battlebacks", [".png", ".jpg", ".jpeg"])
|
||||
files.map! { |file| file[0] }
|
||||
files.delete_if { |file| !file[/_bg$/] }
|
||||
files.map! { |file| file.gsub(/_bg$/, "") }
|
||||
files.delete_if { |file| !pbResolveBitmap("Graphics/Battlebacks/" + file.sub(/_eve$/, "").sub(/_night$/, "") + "_message") }
|
||||
files.map! { |file| [file, file] }
|
||||
@components[:editor_settings].get_control(:canvas_bg).values = files.to_h
|
||||
# User and target sprite graphics
|
||||
files = get_all_files_in_folder("Graphics/Pokemon/Front", [".png", ".jpg", ".jpeg"])
|
||||
files.map! { |file| file[0] }
|
||||
files.delete_if { |file| !GameData::Species.exists?(file) }
|
||||
files.map! { |file| [file, file] }
|
||||
@components[:editor_settings].get_control(:user_sprite_name).values = files.to_h
|
||||
@components[:editor_settings].get_control(:target_sprite_name).values = files.to_h
|
||||
end
|
||||
|
||||
def refresh_move_property_options
|
||||
ctrl = @components[:animation_properties].get_control(:move)
|
||||
case @anim[:type]
|
||||
@@ -492,6 +604,8 @@ class AnimationEditor
|
||||
def refresh_component_values(component_sym)
|
||||
component = @components[component_sym]
|
||||
case component_sym
|
||||
when :editor_settings
|
||||
refresh_editor_settings_options
|
||||
when :canvas
|
||||
component.keyframe = keyframe
|
||||
component.selected_particle = particle_index
|
||||
@@ -580,7 +694,51 @@ class AnimationEditor
|
||||
edit_animation_properties
|
||||
@components[:menu_bar].anim_name = get_animation_display_name
|
||||
refresh_component(:particle_list)
|
||||
when :settings
|
||||
edit_editor_settings
|
||||
end
|
||||
when :editor_settings
|
||||
case property
|
||||
when :side_size_1
|
||||
old_val = @settings[:side_sizes][0]
|
||||
@settings[:side_sizes][0] = value
|
||||
if @settings[:user_index] >= value * 2
|
||||
@settings[:user_index] = (value - 1) * 2
|
||||
@components[:editor_settings].get_control(:user_index).value = @settings[:user_index]
|
||||
@settings[:target_indices].delete_if { |val| val == @settings[:user_index] }
|
||||
end
|
||||
@settings[:target_indices].delete_if { |val| val.even? && val >= value * 2 }
|
||||
@settings[:target_indices].push(1) if @settings[:target_indices].empty?
|
||||
@components[:editor_settings].get_control(:target_indices).value = @settings[:target_indices].join(",")
|
||||
refresh_editor_settings_options if value != old_val
|
||||
when :side_size_2
|
||||
old_val = @settings[:side_sizes][1]
|
||||
@settings[:side_sizes][1] = value
|
||||
@settings[:target_indices].delete_if { |val| val == @settings[:user_index] }
|
||||
@settings[:target_indices].delete_if { |val| val.odd? && val >= value * 2 }
|
||||
@settings[:target_indices].push(1) if @settings[:target_indices].empty?
|
||||
@components[:editor_settings].get_control(:target_indices).value = @settings[:target_indices].join(",")
|
||||
refresh_editor_settings_options if value != old_val
|
||||
when :user_index
|
||||
@settings[:user_index] = value
|
||||
@settings[:target_indices].delete_if { |val| val == @settings[:user_index] }
|
||||
@settings[:target_indices].push(1) if @settings[:target_indices].empty?
|
||||
@components[:editor_settings].get_control(:target_indices).value = @settings[:target_indices].join(",")
|
||||
when :target_indices
|
||||
@settings[:target_indices] = value.split(",")
|
||||
@settings[:target_indices].map! { |val| val.to_i }
|
||||
@settings[:target_indices].sort!
|
||||
@settings[:target_indices].uniq!
|
||||
@settings[:target_indices].delete_if { |val| val == @settings[:user_index] }
|
||||
@settings[:target_indices].delete_if { |val| val.even? && val >= @settings[:side_sizes][0] * 2 }
|
||||
@settings[:target_indices].delete_if { |val| val.odd? && val >= @settings[:side_sizes][1] * 2 }
|
||||
@settings[:target_indices].push(1) if @settings[:target_indices].empty?
|
||||
@components[:editor_settings].get_control(:target_indices).value = @settings[:target_indices].join(",")
|
||||
else
|
||||
@settings[property] = value
|
||||
end
|
||||
save_settings
|
||||
refresh_component(:canvas)
|
||||
when :canvas
|
||||
case property
|
||||
when :particle_index
|
||||
|
||||
@@ -122,10 +122,51 @@ class AnimationEditor
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def edit_editor_settings
|
||||
# Show pop-up window
|
||||
@pop_up_bg_bitmap.visible = true
|
||||
bg_bitmap = create_pop_up_window(ANIM_PROPERTIES_WIDTH, ANIM_PROPERTIES_HEIGHT)
|
||||
editor_settings = @components[:editor_settings]
|
||||
editor_settings.visible = true
|
||||
# Set control values
|
||||
refresh_component(:editor_settings)
|
||||
editor_settings.get_control(:side_size_1).value = @settings[:side_sizes][0]
|
||||
editor_settings.get_control(:side_size_2).value = @settings[:side_sizes][1]
|
||||
editor_settings.get_control(:user_index).value = @settings[:user_index]
|
||||
editor_settings.get_control(:target_indices).value = @settings[:target_indices].join(",")
|
||||
editor_settings.get_control(:user_opposes).value = @settings[:user_opposes]
|
||||
editor_settings.get_control(:canvas_bg).value = @settings[:canvas_bg]
|
||||
editor_settings.get_control(:user_sprite_name).value = @settings[:user_sprite_name]
|
||||
editor_settings.get_control(:target_sprite_name).value = @settings[:target_sprite_name]
|
||||
# Interaction loop
|
||||
ret = nil
|
||||
loop do
|
||||
Graphics.update
|
||||
Input.update
|
||||
editor_settings.update
|
||||
if editor_settings.changed?
|
||||
break if editor_settings.values.keys.include?(:close)
|
||||
editor_settings.values.each_pair do |property, value|
|
||||
apply_changed_value(:editor_settings, property, value)
|
||||
end
|
||||
editor_settings.clear_changed
|
||||
end
|
||||
break if !editor_settings.busy? && Input.triggerex?(:ESCAPE)
|
||||
editor_settings.repaint
|
||||
end
|
||||
# Dispose and return
|
||||
bg_bitmap.dispose
|
||||
@pop_up_bg_bitmap.visible = false
|
||||
editor_settings.clear_changed
|
||||
editor_settings.visible = false
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Generates a list of all files in the given folder and its subfolders which
|
||||
# have a file extension that matches one in exts. Removes any files from the
|
||||
# list whose filename is the same as one in blacklist (case insensitive).
|
||||
def get_all_files_in_folder(folder, exts, blacklist)
|
||||
def get_all_files_in_folder(folder, exts, blacklist = [])
|
||||
ret = []
|
||||
Dir.all(folder).each do |f|
|
||||
next if !exts.include?(File.extname(f))
|
||||
|
||||
@@ -247,14 +247,31 @@ class AnimationEditor::Canvas < Sprite
|
||||
def refresh_bg_graphics
|
||||
return if @bg_name && @bg_name == @settings[:canvas_bg]
|
||||
@bg_name = @settings[:canvas_bg]
|
||||
self.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", @bg_name + "_bg")
|
||||
@player_base.setBitmap("Graphics/Battlebacks/" + @bg_name + "_base0")
|
||||
core_name = @bg_name.sub(/_eve$/, "").sub(/_night$/, "")
|
||||
if pbResolveBitmap("Graphics/Battlebacks/" + @bg_name + "_bg")
|
||||
self.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", @bg_name + "_bg")
|
||||
else
|
||||
self.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", core_name + "_bg")
|
||||
end
|
||||
if pbResolveBitmap("Graphics/Battlebacks/" + @bg_name + "_base0")
|
||||
@player_base.setBitmap("Graphics/Battlebacks/" + @bg_name + "_base0")
|
||||
else
|
||||
@player_base.setBitmap("Graphics/Battlebacks/" + core_name + "_base0")
|
||||
end
|
||||
@player_base.ox = @player_base.bitmap.width / 2
|
||||
@player_base.oy = @player_base.bitmap.height
|
||||
@foe_base.setBitmap("Graphics/Battlebacks/" + @bg_name + "_base1")
|
||||
if pbResolveBitmap("Graphics/Battlebacks/" + @bg_name + "_base1")
|
||||
@foe_base.setBitmap("Graphics/Battlebacks/" + @bg_name + "_base1")
|
||||
else
|
||||
@foe_base.setBitmap("Graphics/Battlebacks/" + core_name + "_base1")
|
||||
end
|
||||
@foe_base.ox = @foe_base.bitmap.width / 2
|
||||
@foe_base.oy = @foe_base.bitmap.height / 2
|
||||
@message_bar_sprite.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", @bg_name + "_message")
|
||||
if pbResolveBitmap("Graphics/Battlebacks/" + @bg_name + "_message")
|
||||
@message_bar_sprite.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", @bg_name + "_message")
|
||||
else
|
||||
@message_bar_sprite.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", core_name + "_message")
|
||||
end
|
||||
@message_bar_sprite.y = Settings::SCREEN_HEIGHT - @message_bar_sprite.height
|
||||
end
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#
|
||||
#===============================================================================
|
||||
class AnimationEditor::MenuBar < UIControls::ControlsContainer
|
||||
MENU_BUTTON_WIDTH = 80
|
||||
NAME_BUTTON_WIDTH = 400 # The animation's name
|
||||
MENU_BUTTON_WIDTH = 80
|
||||
SETTINGS_BUTTON_WIDTH = 30
|
||||
NAME_BUTTON_WIDTH = 400 # The animation's name
|
||||
|
||||
def initialize(x, y, width, height, viewport)
|
||||
super(x, y, width, height)
|
||||
@@ -18,10 +19,15 @@ class AnimationEditor::MenuBar < UIControls::ControlsContainer
|
||||
add_control(id, ctrl)
|
||||
end
|
||||
|
||||
def add_settings_button(id, bitmap)
|
||||
ctrl = UIControls::BitmapButton.new(0, 0, @viewport, bitmap)
|
||||
add_control_at(id, ctrl, @width - SETTINGS_BUTTON_WIDTH + 2, 2)
|
||||
end
|
||||
|
||||
def add_name_button(id, button_text)
|
||||
ctrl = UIControls::Button.new(NAME_BUTTON_WIDTH, @height, @viewport, button_text)
|
||||
ctrl.set_fixed_size
|
||||
add_control(id, ctrl)
|
||||
add_control_at(id, ctrl, @width - ctrl.width - SETTINGS_BUTTON_WIDTH, 0)
|
||||
end
|
||||
|
||||
def anim_name=(val)
|
||||
@@ -33,16 +39,9 @@ class AnimationEditor::MenuBar < UIControls::ControlsContainer
|
||||
|
||||
private
|
||||
|
||||
def add_control(id, control, add_offset = false)
|
||||
i = @controls.length
|
||||
control_x = (add_offset ? @row_count - 1 : @row_count) * MENU_BUTTON_WIDTH
|
||||
control_x = @width - control.width if control.width == NAME_BUTTON_WIDTH
|
||||
control_y = 0
|
||||
control.x = control_x + (add_offset ? OFFSET_FROM_LABEL_X : 0)
|
||||
control.y = control_y + (add_offset ? OFFSET_FROM_LABEL_Y : 0)
|
||||
control.set_interactive_rects
|
||||
@controls[i] = [id, control]
|
||||
@row_count += 1 if !add_offset
|
||||
repaint
|
||||
def next_control_position(add_offset = false)
|
||||
row_x = @row_count * MENU_BUTTON_WIDTH
|
||||
row_y = 0
|
||||
return row_x, row_y
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user