Adds documentation to some lib functions (#168)
* Add sigs and docs to transformers * Add sigs and docs to downloaders * Adds sigs and docs to importers
This commit is contained in:
parent
0d5d4d5f59
commit
7d164b540c
25 changed files with 1080 additions and 20 deletions
|
|
@ -2,9 +2,39 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Downloaders
|
module Downloaders
|
||||||
|
# Abstract base class for downloading game asset images in various sizes.
|
||||||
|
# Handles local and S3 storage, with support for test mode and verbose logging.
|
||||||
|
#
|
||||||
|
# @abstract Subclass must implement {#object_type}, {#base_url}, and {#directory_for_size}
|
||||||
|
#
|
||||||
|
# @example Downloading assets for a specific ID
|
||||||
|
# class MyDownloader < BaseDownloader
|
||||||
|
# def object_type; "weapon"; end
|
||||||
|
# def base_url; "http://example.com/assets"; end
|
||||||
|
# def directory_for_size(size)
|
||||||
|
# case size
|
||||||
|
# when "main" then "large"
|
||||||
|
# when "grid" then "medium"
|
||||||
|
# when "square" then "small"
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# downloader = MyDownloader.new("1234", storage: :both)
|
||||||
|
# downloader.download
|
||||||
|
#
|
||||||
|
# @note Supports three image sizes: main, grid, and square
|
||||||
|
# @note Can store images locally, in S3, or both
|
||||||
class BaseDownloader
|
class BaseDownloader
|
||||||
|
# @return [Array<String>] Available image size variants
|
||||||
SIZES = %w[main grid square].freeze
|
SIZES = %w[main grid square].freeze
|
||||||
|
|
||||||
|
# Initialize a new downloader instance
|
||||||
|
# @param id [String] ID of the object to download images for
|
||||||
|
# @param test_mode [Boolean] When true, only logs actions without downloading
|
||||||
|
# @param verbose [Boolean] When true, enables detailed logging
|
||||||
|
# @param storage [Symbol] Storage mode (:local, :s3, or :both)
|
||||||
|
# @return [void]
|
||||||
def initialize(id, test_mode: false, verbose: false, storage: :both)
|
def initialize(id, test_mode: false, verbose: false, storage: :both)
|
||||||
@id = id
|
@id = id
|
||||||
@base_url = base_url
|
@base_url = base_url
|
||||||
|
|
@ -15,6 +45,8 @@ module Granblue
|
||||||
ensure_directories_exist unless @test_mode
|
ensure_directories_exist unless @test_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Download images for all sizes
|
||||||
|
# @return [void]
|
||||||
def download
|
def download
|
||||||
log_info "-> #{@id}"
|
log_info "-> #{@id}"
|
||||||
return if @test_mode
|
return if @test_mode
|
||||||
|
|
@ -28,6 +60,12 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Process download for a specific size variant
|
||||||
|
# @param url [String] URL to download from
|
||||||
|
# @param size [String] Size variant being processed
|
||||||
|
# @param path [String] Local path for download
|
||||||
|
# @param last [Boolean] Whether this is the last size being processed
|
||||||
|
# @return [void]
|
||||||
def process_download(url, size, path, last: false)
|
def process_download(url, size, path, last: false)
|
||||||
filename = File.basename(url)
|
filename = File.basename(url)
|
||||||
s3_key = build_s3_key(size, filename)
|
s3_key = build_s3_key(size, filename)
|
||||||
|
|
@ -54,11 +92,19 @@ module Granblue
|
||||||
log_info "\t404 returned\t#{url}"
|
log_info "\t404 returned\t#{url}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Download file to local storage
|
||||||
|
# @param url [String] Source URL
|
||||||
|
# @param download_uri [String] Local destination path
|
||||||
|
# @return [void]
|
||||||
def download_to_local(url, download_uri)
|
def download_to_local(url, download_uri)
|
||||||
download = URI.parse(url).open
|
download = URI.parse(url).open
|
||||||
IO.copy_stream(download, download_uri)
|
IO.copy_stream(download, download_uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stream file directly to S3
|
||||||
|
# @param url [String] Source URL
|
||||||
|
# @param s3_key [String] S3 object key
|
||||||
|
# @return [void]
|
||||||
def stream_to_s3(url, s3_key)
|
def stream_to_s3(url, s3_key)
|
||||||
return if @aws_service.file_exists?(s3_key)
|
return if @aws_service.file_exists?(s3_key)
|
||||||
|
|
||||||
|
|
@ -67,6 +113,11 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Download file to both local storage and S3
|
||||||
|
# @param url [String] Source URL
|
||||||
|
# @param download_uri [String] Local destination path
|
||||||
|
# @param s3_key [String] S3 object key
|
||||||
|
# @return [void]
|
||||||
def download_to_both(url, download_uri, s3_key)
|
def download_to_both(url, download_uri, s3_key)
|
||||||
download = URI.parse(url).open
|
download = URI.parse(url).open
|
||||||
|
|
||||||
|
|
@ -82,17 +133,23 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if file should be downloaded based on storage mode
|
||||||
|
# @param local_path [String] Local file path
|
||||||
|
# @param s3_key [String] S3 object key
|
||||||
|
# @return [Boolean] true if file should be downloaded
|
||||||
def should_download?(local_path, s3_key)
|
def should_download?(local_path, s3_key)
|
||||||
case @storage
|
if @storage == :local
|
||||||
when :local
|
|
||||||
!File.exist?(local_path)
|
!File.exist?(local_path)
|
||||||
when :s3
|
elsif @storage == :s3
|
||||||
!@aws_service.file_exists?(s3_key)
|
!@aws_service.file_exists?(s3_key)
|
||||||
when :both
|
else
|
||||||
|
# :both
|
||||||
!File.exist?(local_path) || !@aws_service.file_exists?(s3_key)
|
!File.exist?(local_path) || !@aws_service.file_exists?(s3_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Ensure local directories exist for each size
|
||||||
|
# @return [void]
|
||||||
def ensure_directories_exist
|
def ensure_directories_exist
|
||||||
return unless store_locally?
|
return unless store_locally?
|
||||||
|
|
||||||
|
|
@ -101,45 +158,85 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if local storage is being used
|
||||||
|
# @return [Boolean] true if storing locally
|
||||||
def store_locally?
|
def store_locally?
|
||||||
%i[local both].include?(@storage)
|
%i[local both].include?(@storage)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get local download path for a size
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @return [String] Local directory path
|
||||||
def download_path(size)
|
def download_path(size)
|
||||||
"#{Rails.root}/download/#{object_type}-#{size}"
|
"#{Rails.root}/download/#{object_type}-#{size}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Build S3 key for an image
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @param filename [String] Image filename
|
||||||
|
# @return [String] Complete S3 key
|
||||||
def build_s3_key(size, filename)
|
def build_s3_key(size, filename)
|
||||||
"#{object_type}-#{size}/#{filename}"
|
"#{object_type}-#{size}/#{filename}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Log informational message if verbose
|
||||||
|
# @param message [String] Message
|
||||||
def log_info(message)
|
def log_info(message)
|
||||||
puts message if @verbose
|
puts message if @verbose
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Download elemental variant image
|
||||||
|
# @param url [String] Source URL
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @param path [String] Destination path
|
||||||
|
# @param filename [String] Image filename
|
||||||
|
# @return [void]
|
||||||
def download_elemental_image(url, size, path, filename)
|
def download_elemental_image(url, size, path, filename)
|
||||||
return if @test_mode
|
return if @test_mode
|
||||||
|
|
||||||
filepath = "#{path}/#{filename}"
|
filepath = "#{path}/#{filename}"
|
||||||
download = URI.parse(url).open
|
URI.open(url) do |file|
|
||||||
|
content = file.read
|
||||||
|
if content
|
||||||
|
File.open(filepath, 'wb') do |output|
|
||||||
|
output.write(content)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise "Failed to read content from #{url}"
|
||||||
|
end
|
||||||
|
end
|
||||||
log_info "-> #{size}:\t#{url}..."
|
log_info "-> #{size}:\t#{url}..."
|
||||||
IO.copy_stream(download, filepath)
|
|
||||||
rescue OpenURI::HTTPError
|
rescue OpenURI::HTTPError
|
||||||
log_info "\t404 returned\t#{url}"
|
log_info "\t404 returned\t#{url}"
|
||||||
|
rescue StandardError => e
|
||||||
|
log_info "\tError downloading #{url}: #{e.message}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get asset type (e.g., "weapon", "character")
|
||||||
|
# @abstract
|
||||||
|
# @return [String] Asset type name
|
||||||
def object_type
|
def object_type
|
||||||
raise NotImplementedError, 'Subclasses must define object_type'
|
raise NotImplementedError, 'Subclasses must define object_type'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get base URL for assets
|
||||||
|
# @abstract
|
||||||
|
# @return [String] Base URL
|
||||||
def base_url
|
def base_url
|
||||||
raise NotImplementedError, 'Subclasses must define base_url'
|
raise NotImplementedError, 'Subclasses must define base_url'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get directory name for a size variant
|
||||||
|
# @abstract
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @return [String] Directory name
|
||||||
def directory_for_size(size)
|
def directory_for_size(size)
|
||||||
raise NotImplementedError, 'Subclasses must define directory_for_size'
|
raise NotImplementedError, 'Subclasses must define directory_for_size'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Build complete URL for a size variant
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @return [String] Complete download URL
|
||||||
def build_url(size)
|
def build_url(size)
|
||||||
directory = directory_for_size(size)
|
directory = directory_for_size(size)
|
||||||
"#{@base_url}/#{directory}/#{@id}.jpg"
|
"#{@base_url}/#{directory}/#{@id}.jpg"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,23 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Downloaders
|
module Downloaders
|
||||||
|
# Downloads character image assets from the game server in different sizes and variants.
|
||||||
|
# Handles character-specific variants like base art, uncap art, and transcendence art.
|
||||||
|
#
|
||||||
|
# @example Download images for a specific character
|
||||||
|
# downloader = CharacterDownloader.new("3040001000", storage: :both)
|
||||||
|
# downloader.download
|
||||||
|
#
|
||||||
|
# @note Character images come in multiple variants (_01, _02, etc.) based on uncap status
|
||||||
|
# @note Supports FLB (5★) and ULB (6★) art variants when available
|
||||||
class CharacterDownloader < BaseDownloader
|
class CharacterDownloader < BaseDownloader
|
||||||
|
# Downloads images for all variants of a character based on their uncap status.
|
||||||
|
# Overrides {BaseDownloader#download} to handle character-specific variants.
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
# @note Skips download if character is not found in database
|
||||||
|
# @note Downloads FLB/ULB variants only if character has those uncaps
|
||||||
|
# @see #download_variants
|
||||||
def download
|
def download
|
||||||
character = Character.find_by(granblue_id: @id)
|
character = Character.find_by(granblue_id: @id)
|
||||||
return unless character
|
return unless character
|
||||||
|
|
@ -12,9 +28,13 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Downloads all variants of a character's images
|
||||||
|
# @param character [Character] Character model instance to download images for
|
||||||
|
# @return [void]
|
||||||
|
# @note Only downloads variants that should exist based on character uncap status
|
||||||
def download_variants(character)
|
def download_variants(character)
|
||||||
# All characters have 01 and 02 variants
|
# All characters have 01 and 02 variants
|
||||||
variants = ["#{@id}_01", "#{@id}_02"]
|
variants = %W[#{@id}_01 #{@id}_02]
|
||||||
|
|
||||||
# Add FLB variant if available
|
# Add FLB variant if available
|
||||||
variants << "#{@id}_03" if character.flb
|
variants << "#{@id}_03" if character.flb
|
||||||
|
|
@ -29,6 +49,9 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Downloads a specific variant's images in all sizes
|
||||||
|
# @param variant_id [String] Character variant ID (e.g., "3040001000_01")
|
||||||
|
# @return [void]
|
||||||
def download_variant(variant_id)
|
def download_variant(variant_id)
|
||||||
log_info "-> #{variant_id}" if @verbose
|
log_info "-> #{variant_id}" if @verbose
|
||||||
return if @test_mode
|
return if @test_mode
|
||||||
|
|
@ -40,19 +63,31 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Builds URL for a specific variant and size
|
||||||
|
# @param variant_id [String] Character variant ID
|
||||||
|
# @param size [String] Image size variant ("main", "grid", or "square")
|
||||||
|
# @return [String] Complete URL for downloading the image
|
||||||
def build_variant_url(variant_id, size)
|
def build_variant_url(variant_id, size)
|
||||||
directory = directory_for_size(size)
|
directory = directory_for_size(size)
|
||||||
"#{@base_url}/#{directory}/#{variant_id}.jpg"
|
"#{@base_url}/#{directory}/#{variant_id}.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets object type for file paths and storage keys
|
||||||
|
# @return [String] Returns "character"
|
||||||
def object_type
|
def object_type
|
||||||
'character'
|
'character'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets base URL for character assets
|
||||||
|
# @return [String] Base URL for character images
|
||||||
def base_url
|
def base_url
|
||||||
'http://gbf.game-a.mbga.jp/assets/img/sp/assets/npc'
|
'http://gbf.game-a.mbga.jp/assets/img/sp/assets/npc'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets directory name for a size variant
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @return [String] Directory name in game asset URL structure
|
||||||
|
# @note Maps "main" -> "f", "grid" -> "m", "square" -> "s"
|
||||||
def directory_for_size(size)
|
def directory_for_size(size)
|
||||||
case size.to_s
|
case size.to_s
|
||||||
when 'main' then 'f'
|
when 'main' then 'f'
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,41 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Downloaders
|
module Downloaders
|
||||||
|
# Manages downloading of game assets by coordinating different downloader types.
|
||||||
|
# Provides a single interface for downloading any type of game asset.
|
||||||
|
#
|
||||||
|
# @example Download character images
|
||||||
|
# DownloadManager.download_for_object('character', '3040001000', storage: :s3)
|
||||||
|
#
|
||||||
|
# @example Download weapon images in test mode
|
||||||
|
# DownloadManager.download_for_object('weapon', '1040001000', test_mode: true, verbose: true)
|
||||||
|
#
|
||||||
|
# @note Automatically selects the appropriate downloader based on object type
|
||||||
|
# @note Handles configuration of downloader options consistently across types
|
||||||
class DownloadManager
|
class DownloadManager
|
||||||
class << self
|
class << self
|
||||||
|
# Downloads assets for a specific game object using the appropriate downloader
|
||||||
|
#
|
||||||
|
# @param type [String] Type of game object ('character', 'weapon', or 'summon')
|
||||||
|
# @param granblue_id [String] Game ID of the object to download assets for
|
||||||
|
# @param test_mode [Boolean] When true, simulates downloads without actually downloading
|
||||||
|
# @param verbose [Boolean] When true, enables detailed logging
|
||||||
|
# @param storage [Symbol] Storage mode to use (:local, :s3, or :both)
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @example Download character images to S3
|
||||||
|
# DownloadManager.download_for_object('character', '3040001000', storage: :s3)
|
||||||
|
#
|
||||||
|
# @example Test weapon downloads with verbose logging
|
||||||
|
# DownloadManager.download_for_object('weapon', '1040001000',
|
||||||
|
# test_mode: true,
|
||||||
|
# verbose: true
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# @note Logs warning if object type is unknown
|
||||||
|
# @see CharacterDownloader
|
||||||
|
# @see WeaponDownloader
|
||||||
|
# @see SummonDownloader
|
||||||
def download_for_object(type, granblue_id, test_mode: false, verbose: false, storage: :both)
|
def download_for_object(type, granblue_id, test_mode: false, verbose: false, storage: :both)
|
||||||
downloader_options = {
|
downloader_options = {
|
||||||
test_mode: test_mode,
|
test_mode: test_mode,
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,31 @@ require_relative 'weapon_downloader'
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Downloaders
|
module Downloaders
|
||||||
|
# Specialized downloader for handling elemental weapon variants.
|
||||||
|
# Some weapons have different art for each element, requiring multiple downloads.
|
||||||
|
#
|
||||||
|
# @example Download all elemental variants
|
||||||
|
# downloader = ElementalWeaponDownloader.new(1040001000)
|
||||||
|
# downloader.download
|
||||||
|
#
|
||||||
|
# @note Handles weapons that have variants for all six elements
|
||||||
|
# @note Uses specific suffix mappings for element art variants
|
||||||
class ElementalWeaponDownloader < WeaponDownloader
|
class ElementalWeaponDownloader < WeaponDownloader
|
||||||
|
# Element variant suffix mapping
|
||||||
|
# @return [Array<Integer>] Ordered list of suffixes for element variants
|
||||||
SUFFIXES = [2, 3, 4, 1, 6, 5].freeze
|
SUFFIXES = [2, 3, 4, 1, 6, 5].freeze
|
||||||
|
|
||||||
|
# Initialize downloader with base weapon ID
|
||||||
|
# @param id_base [Integer] Base ID for the elemental weapon series
|
||||||
|
# @return [void]
|
||||||
def initialize(id_base)
|
def initialize(id_base)
|
||||||
@id_base = id_base.to_i
|
@id_base = id_base.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Downloads all elemental variants of the weapon
|
||||||
|
# @return [void]
|
||||||
|
# @note Downloads variants for all six elements
|
||||||
|
# @note Uses progress reporter to show download status
|
||||||
def download
|
def download
|
||||||
(1..6).each do |i|
|
(1..6).each do |i|
|
||||||
id = @id_base + (i - 1) * 100
|
id = @id_base + (i - 1) * 100
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,23 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Downloaders
|
module Downloaders
|
||||||
|
# Downloads summon image assets from the game server in different sizes and variants.
|
||||||
|
# Handles summon-specific variants like base art, ULB art, and transcendence art.
|
||||||
|
#
|
||||||
|
# @example Download images for a specific summon
|
||||||
|
# downloader = SummonDownloader.new("2040001000", storage: :both)
|
||||||
|
# downloader.download
|
||||||
|
#
|
||||||
|
# @note Summon images come in multiple variants based on uncap status
|
||||||
|
# @note Supports ULB (5★) and transcendence variants when available
|
||||||
class SummonDownloader < BaseDownloader
|
class SummonDownloader < BaseDownloader
|
||||||
|
# Downloads images for all variants of a summon based on their uncap status.
|
||||||
|
# Overrides {BaseDownloader#download} to handle summon-specific variants.
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
# @note Skips download if summon is not found in database
|
||||||
|
# @note Downloads ULB and transcendence variants only if summon has those uncaps
|
||||||
|
# @see #download_variants
|
||||||
def download
|
def download
|
||||||
summon = Summon.find_by(granblue_id: @id)
|
summon = Summon.find_by(granblue_id: @id)
|
||||||
return unless summon
|
return unless summon
|
||||||
|
|
@ -12,6 +28,11 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Downloads all variants of a summon's images
|
||||||
|
# @param summon [Summon] Summon model instance to download images for
|
||||||
|
# @return [void]
|
||||||
|
# @note Only downloads variants that should exist based on summon uncap status
|
||||||
|
# @note Handles special transcendence art variants for 6★ summons
|
||||||
def download_variants(summon)
|
def download_variants(summon)
|
||||||
# All summons have base variant
|
# All summons have base variant
|
||||||
variants = [@id]
|
variants = [@id]
|
||||||
|
|
@ -31,6 +52,10 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Downloads a specific variant's images in all sizes
|
||||||
|
# @param variant_id [String] Summon variant ID (e.g., "2040001000_02")
|
||||||
|
# @return [void]
|
||||||
|
# @note Downloads all size variants (main/grid/square) for the given variant
|
||||||
def download_variant(variant_id)
|
def download_variant(variant_id)
|
||||||
log_info "-> #{variant_id}" if @verbose
|
log_info "-> #{variant_id}" if @verbose
|
||||||
return if @test_mode
|
return if @test_mode
|
||||||
|
|
@ -42,19 +67,31 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Builds URL for a specific variant and size
|
||||||
|
# @param variant_id [String] Summon variant ID
|
||||||
|
# @param size [String] Image size variant ("main", "grid", or "square")
|
||||||
|
# @return [String] Complete URL for downloading the image
|
||||||
def build_variant_url(variant_id, size)
|
def build_variant_url(variant_id, size)
|
||||||
directory = directory_for_size(size)
|
directory = directory_for_size(size)
|
||||||
"#{@base_url}/#{directory}/#{variant_id}.jpg"
|
"#{@base_url}/#{directory}/#{variant_id}.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets object type for file paths and storage keys
|
||||||
|
# @return [String] Returns "summon"
|
||||||
def object_type
|
def object_type
|
||||||
'summon'
|
'summon'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets base URL for summon assets
|
||||||
|
# @return [String] Base URL for summon images
|
||||||
def base_url
|
def base_url
|
||||||
'http://gbf.game-a.mbga.jp/assets/img/sp/assets/summon'
|
'http://gbf.game-a.mbga.jp/assets/img/sp/assets/summon'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets directory name for a size variant
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @return [String] Directory name in game asset URL structure
|
||||||
|
# @note Maps "main" -> "party_main", "grid" -> "party_sub", "square" -> "s"
|
||||||
def directory_for_size(size)
|
def directory_for_size(size)
|
||||||
case size.to_s
|
case size.to_s
|
||||||
when 'main' then 'party_main'
|
when 'main' then 'party_main'
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,24 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Downloaders
|
module Downloaders
|
||||||
|
# Downloads weapon image assets from the game server in different sizes and variants.
|
||||||
|
# Handles weapon-specific variants like base art, transcendence art, and elemental variants.
|
||||||
|
#
|
||||||
|
# @example Download images for a specific weapon
|
||||||
|
# downloader = WeaponDownloader.new("1040001000", storage: :both)
|
||||||
|
# downloader.download
|
||||||
|
#
|
||||||
|
# @note Weapon images come in multiple variants based on uncap and element status
|
||||||
|
# @note Supports transcendence variants and element-specific variants
|
||||||
|
# @see ElementalWeaponDownloader for handling multi-element weapons
|
||||||
class WeaponDownloader < BaseDownloader
|
class WeaponDownloader < BaseDownloader
|
||||||
|
# Downloads images for all variants of a weapon based on their uncap status.
|
||||||
|
# Overrides {BaseDownloader#download} to handle weapon-specific variants.
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
# @note Skips download if weapon is not found in database
|
||||||
|
# @note Downloads transcendence variants only if weapon has those uncaps
|
||||||
|
# @see #download_variants
|
||||||
def download
|
def download
|
||||||
weapon = Weapon.find_by(granblue_id: @id)
|
weapon = Weapon.find_by(granblue_id: @id)
|
||||||
return unless weapon
|
return unless weapon
|
||||||
|
|
@ -12,6 +29,11 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Downloads all variants of a weapon's images
|
||||||
|
# @param weapon [Weapon] Weapon model instance to download images for
|
||||||
|
# @return [void]
|
||||||
|
# @note Only downloads variants that should exist based on weapon uncap status
|
||||||
|
# @note Handles special transcendence art variants for transcendable weapons
|
||||||
def download_variants(weapon)
|
def download_variants(weapon)
|
||||||
# All weapons have base variant
|
# All weapons have base variant
|
||||||
variants = [@id]
|
variants = [@id]
|
||||||
|
|
@ -28,6 +50,10 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Downloads a specific variant's images in all sizes
|
||||||
|
# @param variant_id [String] Weapon variant ID (e.g., "1040001000_02")
|
||||||
|
# @return [void]
|
||||||
|
# @note Downloads all size variants (main/grid/square) for the given variant
|
||||||
def download_variant(variant_id)
|
def download_variant(variant_id)
|
||||||
log_info "-> #{variant_id}" if @verbose
|
log_info "-> #{variant_id}" if @verbose
|
||||||
return if @test_mode
|
return if @test_mode
|
||||||
|
|
@ -39,19 +65,31 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Builds URL for a specific variant and size
|
||||||
|
# @param variant_id [String] Weapon variant ID
|
||||||
|
# @param size [String] Image size variant ("main", "grid", or "square")
|
||||||
|
# @return [String] Complete URL for downloading the image
|
||||||
def build_variant_url(variant_id, size)
|
def build_variant_url(variant_id, size)
|
||||||
directory = directory_for_size(size)
|
directory = directory_for_size(size)
|
||||||
"#{@base_url}/#{directory}/#{variant_id}.jpg"
|
"#{@base_url}/#{directory}/#{variant_id}.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets object type for file paths and storage keys
|
||||||
|
# @return [String] Returns "weapon"
|
||||||
def object_type
|
def object_type
|
||||||
'weapon'
|
'weapon'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets base URL for weapon assets
|
||||||
|
# @return [String] Base URL for weapon images
|
||||||
def base_url
|
def base_url
|
||||||
'http://gbf.game-a.mbga.jp/assets/img/sp/assets/weapon'
|
'http://gbf.game-a.mbga.jp/assets/img/sp/assets/weapon'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets directory name for a size variant
|
||||||
|
# @param size [String] Image size variant
|
||||||
|
# @return [String] Directory name in game asset URL structure
|
||||||
|
# @note Maps "main" -> "ls", "grid" -> "m", "square" -> "s"
|
||||||
def directory_for_size(size)
|
def directory_for_size(size)
|
||||||
case size.to_s
|
case size.to_s
|
||||||
when 'main' then 'ls'
|
when 'main' then 'ls'
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,40 @@ require_relative 'import_error'
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Importers
|
module Importers
|
||||||
|
# Abstract base class for importing game data from CSV files.
|
||||||
|
# Handles the core import logic including test mode, validation, and error handling.
|
||||||
|
#
|
||||||
|
# @abstract Subclass must implement {#model_class} and {#build_attributes}
|
||||||
|
#
|
||||||
|
# @example Implementing a subclass
|
||||||
|
# class WeaponImporter < BaseImporter
|
||||||
|
# private
|
||||||
|
# def model_class
|
||||||
|
# Weapon
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def build_attributes(row)
|
||||||
|
# {
|
||||||
|
# name_en: parse_value(row['name_en']),
|
||||||
|
# granblue_id: parse_value(row['granblue_id'])
|
||||||
|
# }
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @note Tracks both new and updated records during import
|
||||||
|
# @note Supports test mode for simulating imports
|
||||||
class BaseImporter
|
class BaseImporter
|
||||||
attr_reader :new_records, :updated_records
|
# @return [Hash<String, Array<Hash>>] New records created during import
|
||||||
|
attr_reader :new_records
|
||||||
|
|
||||||
|
# @return [Hash<String, Array<Hash>>] Existing records updated during import
|
||||||
|
attr_reader :updated_records
|
||||||
|
|
||||||
|
# Initialize a new importer instance
|
||||||
|
# @param file_path [String] Path to CSV file to import
|
||||||
|
# @param test_mode [Boolean] When true, simulates import without making changes
|
||||||
|
# @param verbose [Boolean] When true, enables detailed logging
|
||||||
|
# @param logger [#log_step, #log_verbose] Optional logger instance
|
||||||
def initialize(file_path, test_mode: false, verbose: false, logger: nil)
|
def initialize(file_path, test_mode: false, verbose: false, logger: nil)
|
||||||
@file_path = file_path
|
@file_path = file_path
|
||||||
@test_mode = test_mode
|
@test_mode = test_mode
|
||||||
|
|
@ -16,6 +47,12 @@ module Granblue
|
||||||
@updated_records = Hash.new { |h, k| h[k] = [] }
|
@updated_records = Hash.new { |h, k| h[k] = [] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Perform the actual import of CSV data
|
||||||
|
# @return [Hash] Results containing new and updated records
|
||||||
|
# @example
|
||||||
|
# importer = WeaponImporter.new("weapons.csv")
|
||||||
|
# results = importer.import
|
||||||
|
# results[:new][:weapon].each { |w| puts w[:name_en] }
|
||||||
def import
|
def import
|
||||||
CSV.foreach(@file_path, headers: true) do |row|
|
CSV.foreach(@file_path, headers: true) do |row|
|
||||||
import_row(row)
|
import_row(row)
|
||||||
|
|
@ -23,6 +60,9 @@ module Granblue
|
||||||
{ new: @new_records, updated: @updated_records }
|
{ new: @new_records, updated: @updated_records }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Simulate an import without making changes
|
||||||
|
# @return [Hash] Results that would be created/updated
|
||||||
|
# @raise [ImportError] If validation fails
|
||||||
def simulate_import
|
def simulate_import
|
||||||
simulated_new = Hash.new { |h, k| h[k] = [] }
|
simulated_new = Hash.new { |h, k| h[k] = [] }
|
||||||
simulated_updated = Hash.new { |h, k| h[k] = [] }
|
simulated_updated = Hash.new { |h, k| h[k] = [] }
|
||||||
|
|
@ -49,12 +89,18 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Import a single row from the CSV
|
||||||
|
# @param row [CSV::Row] Row to import
|
||||||
|
# @return [void]
|
||||||
def import_row(row)
|
def import_row(row)
|
||||||
attributes = build_attributes(row)
|
attributes = build_attributes(row)
|
||||||
record = find_or_create_record(attributes)
|
record = find_or_create_record(attributes)
|
||||||
track_record(record) if record
|
track_record(record) if record
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Find existing record or create new one
|
||||||
|
# @param attributes [Hash] Attributes for the record
|
||||||
|
# @return [Array<ActiveRecord::Base, Boolean>, nil] Record and update flag, or nil in test mode
|
||||||
def find_or_create_record(attributes)
|
def find_or_create_record(attributes)
|
||||||
existing_record = model_class.find_by(granblue_id: attributes[:granblue_id])
|
existing_record = model_class.find_by(granblue_id: attributes[:granblue_id])
|
||||||
|
|
||||||
|
|
@ -78,6 +124,11 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Simulate creating a new record
|
||||||
|
# @param attributes [Hash] Attributes for the record
|
||||||
|
# @param simulated_new [Hash] Collection of simulated new records
|
||||||
|
# @param type [String] Model type being simulated
|
||||||
|
# @return [void]
|
||||||
def simulate_create(attributes, simulated_new, type)
|
def simulate_create(attributes, simulated_new, type)
|
||||||
test_record = model_class.new(attributes)
|
test_record = model_class.new(attributes)
|
||||||
validate_record(test_record)
|
validate_record(test_record)
|
||||||
|
|
@ -91,6 +142,12 @@ module Granblue
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Simulate updating an existing record
|
||||||
|
# @param existing_record [ActiveRecord::Base] Record to update
|
||||||
|
# @param attributes [Hash] New attributes
|
||||||
|
# @param simulated_updated [Hash] Collection of simulated updates
|
||||||
|
# @param type [String] Model type being simulated
|
||||||
|
# @return [void]
|
||||||
def simulate_update(existing_record, attributes, simulated_updated, type)
|
def simulate_update(existing_record, attributes, simulated_updated, type)
|
||||||
update_attributes = attributes.compact
|
update_attributes = attributes.compact
|
||||||
would_update = update_attributes.any? { |key, value| existing_record[key] != value }
|
would_update = update_attributes.any? { |key, value| existing_record[key] != value }
|
||||||
|
|
@ -116,6 +173,9 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Validate that all required attributes are present
|
||||||
|
# @param attributes [Hash] Attributes to validate
|
||||||
|
# @raise [ImportError] If required attributes are missing
|
||||||
def validate_required_attributes(attributes)
|
def validate_required_attributes(attributes)
|
||||||
required_columns = model_class.columns.select { |c| !c.null }.map(&:name)
|
required_columns = model_class.columns.select { |c| !c.null }.map(&:name)
|
||||||
|
|
||||||
|
|
@ -140,6 +200,9 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Validate attributes for an update
|
||||||
|
# @param update_attributes [Hash] Attributes being updated
|
||||||
|
# @raise [ImportError] If required attributes are missing
|
||||||
def validate_update_attributes(update_attributes)
|
def validate_update_attributes(update_attributes)
|
||||||
# Get the list of columns that cannot be null in the database
|
# Get the list of columns that cannot be null in the database
|
||||||
required_columns = model_class.columns.select { |c| !c.null }.map(&:name)
|
required_columns = model_class.columns.select { |c| !c.null }.map(&:name)
|
||||||
|
|
@ -170,6 +233,9 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Validate a record can be saved
|
||||||
|
# @param record [ActiveRecord::Base] Record to validate
|
||||||
|
# @raise [ImportError] If validation fails
|
||||||
def validate_record(record)
|
def validate_record(record)
|
||||||
unless record.valid?
|
unless record.valid?
|
||||||
raise ImportError.new(
|
raise ImportError.new(
|
||||||
|
|
@ -205,6 +271,9 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Track a processed record in results
|
||||||
|
# @param result [Array(ActiveRecord::Base, Boolean)] Record and whether it was updated
|
||||||
|
# @return [void]
|
||||||
def track_record(result)
|
def track_record(result)
|
||||||
record, was_updated = result
|
record, was_updated = result
|
||||||
type = model_class.name.demodulize.downcase
|
type = model_class.name.demodulize.downcase
|
||||||
|
|
@ -223,6 +292,9 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Format attributes for logging
|
||||||
|
# @param attributes [Hash] Attributes to format
|
||||||
|
# @return [String] Formatted attribute string
|
||||||
def format_attributes(attributes)
|
def format_attributes(attributes)
|
||||||
attributes.map do |key, value|
|
attributes.map do |key, value|
|
||||||
formatted_value = case value
|
formatted_value = case value
|
||||||
|
|
@ -235,6 +307,10 @@ module Granblue
|
||||||
end.join("\n")
|
end.join("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Log a test mode update
|
||||||
|
# @param record [ActiveRecord::Base] Record being updated
|
||||||
|
# @param attributes [Hash] New attributes
|
||||||
|
# @return [void]
|
||||||
def log_test_update(record, attributes)
|
def log_test_update(record, attributes)
|
||||||
update_attributes = attributes.compact
|
update_attributes = attributes.compact
|
||||||
@logger&.log_step("\nUpdate #{model_class.name} #{record.granblue_id}:")
|
@logger&.log_step("\nUpdate #{model_class.name} #{record.granblue_id}:")
|
||||||
|
|
@ -245,68 +321,108 @@ module Granblue
|
||||||
@logger&.log_step("\n")
|
@logger&.log_step("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Log a test mode creation
|
||||||
|
# @param attributes [Hash] Attributes for new record
|
||||||
|
# @return [void]
|
||||||
def log_test_creation(attributes)
|
def log_test_creation(attributes)
|
||||||
@logger&.log_step("\nCreate #{model_class.name}:")
|
@logger&.log_step("\nCreate #{model_class.name}:")
|
||||||
@logger&.log_verbose(format_attributes(attributes))
|
@logger&.log_verbose(format_attributes(attributes))
|
||||||
@logger&.log_step("\n")
|
@logger&.log_step("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Log creation of a new record
|
||||||
|
# @param record [ActiveRecord::Base] Created record
|
||||||
|
# @return [void]
|
||||||
def log_new_record(record)
|
def log_new_record(record)
|
||||||
@logger&.log_verbose("Created #{model_class.name} with ID: #{record.granblue_id}\n")
|
@logger&.log_verbose("Created #{model_class.name} with ID: #{record.granblue_id}\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Log update of existing record
|
||||||
|
# @param record [ActiveRecord::Base] Updated record
|
||||||
|
# @return [void]
|
||||||
def log_updated_record(record)
|
def log_updated_record(record)
|
||||||
@logger&.log_verbose("Updated #{model_class.name} with ID: #{record.granblue_id}\n")
|
@logger&.log_verbose("Updated #{model_class.name} with ID: #{record.granblue_id}\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse a string value, returning nil if empty
|
||||||
|
# @param value [String, nil] Value to parse
|
||||||
|
# @return [String, nil] Parsed value
|
||||||
def parse_value(value)
|
def parse_value(value)
|
||||||
return nil if value.nil? || value.strip.empty?
|
return nil if value.nil? || value.strip.empty?
|
||||||
|
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse an integer value
|
||||||
|
# @param value [String, nil] Value to parse
|
||||||
|
# @return [Integer, nil] Parsed value
|
||||||
def parse_integer(value)
|
def parse_integer(value)
|
||||||
return nil if value.nil? || value.strip.empty?
|
return nil if value.nil? || value.strip.empty?
|
||||||
|
|
||||||
value.to_i
|
value.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse a float value
|
||||||
|
# @param value [String, nil] Value to parse
|
||||||
|
# @return [Float, nil] Parsed value
|
||||||
def parse_float(value)
|
def parse_float(value)
|
||||||
return nil if value.nil? || value.strip.empty?
|
return nil if value.nil? || value.strip.empty?
|
||||||
|
|
||||||
value.to_f
|
value.to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse a boolean value
|
||||||
|
# @param value [String, nil] Value to parse
|
||||||
|
# @return [Boolean, nil] Parsed value
|
||||||
def parse_boolean(value)
|
def parse_boolean(value)
|
||||||
return nil if value.nil? || value.strip.empty?
|
return nil if value.nil? || value.strip.empty?
|
||||||
|
|
||||||
value == 'true'
|
value == 'true'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse a date string
|
||||||
|
# @param date_str [String, nil] Date string to parse
|
||||||
|
# @return [Date, nil] Parsed date
|
||||||
def parse_date(date_str)
|
def parse_date(date_str)
|
||||||
return nil if date_str.nil? || date_str.strip.empty?
|
return nil if date_str.nil? || date_str.strip.empty?
|
||||||
|
|
||||||
Date.parse(date_str) rescue nil
|
Date.parse(date_str) rescue nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse a string array
|
||||||
|
# @param array_str [String, nil] Array string to parse
|
||||||
|
# @return [Array<String>] Parsed array
|
||||||
def parse_array(array_str)
|
def parse_array(array_str)
|
||||||
return [] if array_str.nil? || array_str.strip.empty?
|
return [] if array_str.nil? || array_str.strip.empty?
|
||||||
|
|
||||||
array_str.tr('{}', '').split(',')
|
array_str.tr('{}', '').split(',')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse an integer array
|
||||||
|
# @param array_str [String, nil] Array string to parse
|
||||||
|
# @return [Array<Integer>] Parsed array
|
||||||
def parse_integer_array(array_str)
|
def parse_integer_array(array_str)
|
||||||
parse_array(array_str).map(&:to_i)
|
parse_array(array_str).map(&:to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the model class for this importer
|
||||||
|
# @abstract Implement in subclass
|
||||||
|
# @return [Class] ActiveRecord model class
|
||||||
def model_class
|
def model_class
|
||||||
raise NotImplementedError, 'Subclasses must define model_class'
|
raise NotImplementedError, 'Subclasses must define model_class'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Build attributes hash from CSV row
|
||||||
|
# @abstract Implement in subclass
|
||||||
|
# @param row [CSV::Row] Row to build attributes from
|
||||||
|
# @return [Hash] Attributes for record
|
||||||
def build_attributes(row)
|
def build_attributes(row)
|
||||||
raise NotImplementedError, 'Subclasses must define build_attributes'
|
raise NotImplementedError, 'Subclasses must define build_attributes'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Handle an import error
|
||||||
|
# @param error [StandardError] Error that occurred
|
||||||
|
# @raise [ImportError] Wrapped error with details
|
||||||
def handle_error(error)
|
def handle_error(error)
|
||||||
details = case error
|
details = case error
|
||||||
when ActiveRecord::RecordInvalid
|
when ActiveRecord::RecordInvalid
|
||||||
|
|
@ -321,6 +437,9 @@ module Granblue
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Format a validation error for display
|
||||||
|
# @param error [ActiveRecord::RecordInvalid] Validation error
|
||||||
|
# @return [String] Formatted error message
|
||||||
def format_validation_error(error)
|
def format_validation_error(error)
|
||||||
[
|
[
|
||||||
"Validation failed:",
|
"Validation failed:",
|
||||||
|
|
@ -331,6 +450,9 @@ module Granblue
|
||||||
].flatten.join("\n")
|
].flatten.join("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Format a standard error for display
|
||||||
|
# @param error [StandardError] Error to format
|
||||||
|
# @return [String] Formatted error message
|
||||||
def format_standard_error(error)
|
def format_standard_error(error)
|
||||||
if @verbose && error.respond_to?(:backtrace)
|
if @verbose && error.respond_to?(:backtrace)
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,65 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Importers
|
module Importers
|
||||||
|
# Imports character data from CSV files into the Character model
|
||||||
|
#
|
||||||
|
# @example Importing character data
|
||||||
|
# importer = CharacterImporter.new("characters.csv")
|
||||||
|
# results = importer.import
|
||||||
|
#
|
||||||
|
# @see BaseImporter Base class with core import logic
|
||||||
class CharacterImporter < BaseImporter
|
class CharacterImporter < BaseImporter
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Returns the model class for character records
|
||||||
|
#
|
||||||
|
# @return [Class] The Character model class
|
||||||
|
# @note Overrides the abstract method from BaseImporter
|
||||||
def model_class
|
def model_class
|
||||||
Character
|
Character
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Builds attribute hash from a CSV row for character import
|
||||||
|
#
|
||||||
|
# @param row [CSV::Row] A single row from the character CSV file
|
||||||
|
# @return [Hash] A hash of attributes ready for model creation/update
|
||||||
|
# @option attributes [String] :name_en English name of the character
|
||||||
|
# @option attributes [String] :name_jp Japanese name of the character
|
||||||
|
# @option attributes [String] :granblue_id Unique identifier for the character
|
||||||
|
# @option attributes [Array<Integer>] :character_id Array of character IDs
|
||||||
|
# @option attributes [Integer] :rarity Character's rarity level
|
||||||
|
# @option attributes [Integer] :element Character's elemental affinity
|
||||||
|
# @option attributes [Integer] :proficiency1 First weapon proficiency
|
||||||
|
# @option attributes [Integer] :proficiency2 Second weapon proficiency
|
||||||
|
# @option attributes [Integer] :gender Character's gender
|
||||||
|
# @option attributes [Integer] :race1 First character race
|
||||||
|
# @option attributes [Integer] :race2 Second character race
|
||||||
|
# @option attributes [Boolean] :flb Flag for FLB
|
||||||
|
# @option attributes [Boolean] :ulb Flag for ULB
|
||||||
|
# @option attributes [Boolean] :special Flag for characters with special uncap patterns
|
||||||
|
# @option attributes [Integer] :min_hp Minimum HP
|
||||||
|
# @option attributes [Integer] :max_hp Maximum HP
|
||||||
|
# @option attributes [Integer] :max_hp_flb Maximum HP after FLB
|
||||||
|
# @option attributes [Integer] :max_hp_ulb Maximum HP after ULB
|
||||||
|
# @option attributes [Integer] :min_atk Minimum attack
|
||||||
|
# @option attributes [Integer] :max_atk Maximum attack
|
||||||
|
# @option attributes [Integer] :max_atk_flb Maximum attack after FLB
|
||||||
|
# @option attributes [Integer] :max_atk_ulb Maximum attack after ULB
|
||||||
|
# @option attributes [Integer] :base_da Base double attack rate
|
||||||
|
# @option attributes [Integer] :base_ta Base triple attack rate
|
||||||
|
# @option attributes [Float] :ougi_ratio Original ougi (charge attack) ratio
|
||||||
|
# @option attributes [Float] :ougi_ratio_flb Ougi ratio after FLB
|
||||||
|
# @option attributes [String] :release_date Character release date
|
||||||
|
# @option attributes [String] :flb_date Date FLB was implemented
|
||||||
|
# @option attributes [String] :ulb_date Date ULB was implemented
|
||||||
|
# @option attributes [String] :wiki_en English wiki link
|
||||||
|
# @option attributes [String] :wiki_ja Japanese wiki link
|
||||||
|
# @option attributes [String] :gamewith Gamewith link
|
||||||
|
# @option attributes [String] :kamigame Kamigame link
|
||||||
|
# @option attributes [Array<String>] :nicknames_en English nicknames
|
||||||
|
# @option attributes [Array<String>] :nicknames_jp Japanese nicknames
|
||||||
|
#
|
||||||
|
# @raise [ImportError] If required attributes are missing or invalid
|
||||||
def build_attributes(row)
|
def build_attributes(row)
|
||||||
{
|
{
|
||||||
name_en: parse_value(row['name_en']),
|
name_en: parse_value(row['name_en']),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,30 @@
|
||||||
module Granblue
|
module Granblue
|
||||||
module Importers
|
module Importers
|
||||||
|
# Custom error class for handling import-related exceptions
|
||||||
|
#
|
||||||
|
# @example Raising an import error
|
||||||
|
# raise ImportError.new(
|
||||||
|
# file_name: 'characters.csv',
|
||||||
|
# details: 'Missing required column: name_en'
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# @note This error provides detailed information about import failures
|
||||||
class ImportError < StandardError
|
class ImportError < StandardError
|
||||||
attr_reader :file_name, :details
|
# @return [String] The name of the file that caused the import error
|
||||||
|
attr_reader :file_name
|
||||||
|
|
||||||
|
# @return [String] Detailed information about the error
|
||||||
|
attr_reader :details
|
||||||
|
|
||||||
|
# Create a new ImportError instance
|
||||||
|
#
|
||||||
|
# @param file_name [String] The name of the file that caused the import error
|
||||||
|
# @param details [String] Detailed information about the error
|
||||||
|
# @example
|
||||||
|
# ImportError.new(
|
||||||
|
# file_name: 'weapons.csv',
|
||||||
|
# details: 'Invalid data in rarity column'
|
||||||
|
# )
|
||||||
def initialize(file_name:, details:)
|
def initialize(file_name:, details:)
|
||||||
@file_name = file_name
|
@file_name = file_name
|
||||||
@details = details
|
@details = details
|
||||||
|
|
@ -11,11 +33,33 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Constructs a comprehensive error message
|
||||||
|
#
|
||||||
|
# @return [String] Formatted error message combining file name and details
|
||||||
|
# @example
|
||||||
|
# # Returns "Error importing weapons.csv: Invalid data in rarity column"
|
||||||
|
# build_message
|
||||||
def build_message
|
def build_message
|
||||||
"Error importing #{file_name}: #{details}"
|
"Error importing #{file_name}: #{details}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Formats attributes into a human-readable string representation
|
||||||
|
#
|
||||||
|
# @param attributes [Hash] A hash of attributes to format
|
||||||
|
# @return [String] A formatted string with each attribute on a new line
|
||||||
|
# @example
|
||||||
|
# attributes = {
|
||||||
|
# name: 'Example Weapon',
|
||||||
|
# rarity: 5,
|
||||||
|
# elements: ['fire', 'water']
|
||||||
|
# }
|
||||||
|
# format_attributes(attributes)
|
||||||
|
# # Returns:
|
||||||
|
# # name: "Example Weapon"
|
||||||
|
# # rarity: 5
|
||||||
|
# # elements: ["fire", "water"]
|
||||||
|
# @note Handles various attribute types including arrays and nil values
|
||||||
def format_attributes(attributes)
|
def format_attributes(attributes)
|
||||||
attributes.map do |key, value|
|
attributes.map do |key, value|
|
||||||
formatted_value = case value
|
formatted_value = case value
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,63 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Importers
|
module Importers
|
||||||
|
# Imports summon data from CSV files into the Summon model
|
||||||
|
#
|
||||||
|
# @example Importing summon data
|
||||||
|
# importer = SummonImporter.new("summons.csv")
|
||||||
|
# results = importer.import
|
||||||
|
#
|
||||||
|
# @see BaseImporter Base class with core import logic
|
||||||
class SummonImporter < BaseImporter
|
class SummonImporter < BaseImporter
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Returns the model class for summon records
|
||||||
|
#
|
||||||
|
# @return [Class] The Summon model class
|
||||||
|
# @note Overrides the abstract method from BaseImporter
|
||||||
def model_class
|
def model_class
|
||||||
Summon
|
Summon
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Builds attribute hash from a CSV row for summon import
|
||||||
|
#
|
||||||
|
# @param row [CSV::Row] A single row from the summon CSV file
|
||||||
|
# @return [Hash] A hash of attributes ready for model creation/update
|
||||||
|
# @option attributes [String] :name_en English name of the summon
|
||||||
|
# @option attributes [String] :name_jp Japanese name of the summon
|
||||||
|
# @option attributes [String] :granblue_id Unique identifier for the summon
|
||||||
|
# @option attributes [Integer] :summon_id Specific summon identifier
|
||||||
|
# @option attributes [Integer] :rarity Summon's rarity level
|
||||||
|
# @option attributes [Integer] :element Summon's elemental affinity
|
||||||
|
# @option attributes [String] :series Summon's series or collection
|
||||||
|
# @option attributes [Boolean] :flb Flag for FLB
|
||||||
|
# @option attributes [Boolean] :ulb Flag for ULB
|
||||||
|
# @option attributes [Boolean] :subaura Flag indicating the presence of a subaura effect
|
||||||
|
# @option attributes [Boolean] :limit Flag indicating only one of this summon can be equipped at once
|
||||||
|
# @option attributes [Boolean] :transcendence Flag for transcendence status
|
||||||
|
# @option attributes [Integer] :max_level Maximum level of the summon
|
||||||
|
# @option attributes [Integer] :min_hp Minimum HP
|
||||||
|
# @option attributes [Integer] :max_hp Maximum HP
|
||||||
|
# @option attributes [Integer] :max_hp_flb Maximum HP after FLB
|
||||||
|
# @option attributes [Integer] :max_hp_ulb Maximum HP after ULB
|
||||||
|
# @option attributes [Integer] :max_hp_xlb Maximum HP after Transcendence
|
||||||
|
# @option attributes [Integer] :min_atk Minimum attack
|
||||||
|
# @option attributes [Integer] :max_atk Maximum attack
|
||||||
|
# @option attributes [Integer] :max_atk_flb Maximum attack after FLB
|
||||||
|
# @option attributes [Integer] :max_atk_ulb Maximum attack after ULB
|
||||||
|
# @option attributes [Integer] :max_atk_xlb Maximum attack after Transcendence
|
||||||
|
# @option attributes [String] :release_date Summon release date
|
||||||
|
# @option attributes [String] :flb_date Date FLB was implemented
|
||||||
|
# @option attributes [String] :ulb_date Date ULB was implemented
|
||||||
|
# @option attributes [String] :transcendence_date Date Transcendence was implemented
|
||||||
|
# @option attributes [String] :wiki_en English wiki link
|
||||||
|
# @option attributes [String] :wiki_ja Japanese wiki link
|
||||||
|
# @option attributes [String] :gamewith Gamewith link
|
||||||
|
# @option attributes [String] :kamigame Kamigame link
|
||||||
|
# @option attributes [Array<String>] :nicknames_en English nicknames
|
||||||
|
# @option attributes [Array<String>] :nicknames_jp Japanese nicknames
|
||||||
|
#
|
||||||
|
# @raise [ImportError] If required attributes are missing or invalid
|
||||||
def build_attributes(row)
|
def build_attributes(row)
|
||||||
{
|
{
|
||||||
name_en: parse_value(row['name_en']),
|
name_en: parse_value(row['name_en']),
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,66 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Importers
|
module Importers
|
||||||
|
# Imports weapon data from CSV files into the Weapon model
|
||||||
|
#
|
||||||
|
# @example Importing weapon data
|
||||||
|
# importer = WeaponImporter.new("weapons.csv")
|
||||||
|
# results = importer.import
|
||||||
|
#
|
||||||
|
# @see BaseImporter Base class with core import logic
|
||||||
class WeaponImporter < BaseImporter
|
class WeaponImporter < BaseImporter
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Returns the model class for weapon records
|
||||||
|
#
|
||||||
|
# @return [Class] The Weapon model class
|
||||||
|
# @note Overrides the abstract method from BaseImporter
|
||||||
def model_class
|
def model_class
|
||||||
Weapon
|
Weapon
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Builds attribute hash from a CSV row for weapon import
|
||||||
|
#
|
||||||
|
# @param row [CSV::Row] A single row from the weapon CSV file
|
||||||
|
# @return [Hash] A hash of attributes ready for model creation/update
|
||||||
|
# @option attributes [String] :name_en English name of the weapon
|
||||||
|
# @option attributes [String] :name_jp Japanese name of the weapon
|
||||||
|
# @option attributes [String] :granblue_id Unique identifier for the weapon
|
||||||
|
# @option attributes [Integer] :rarity Weapon's rarity level
|
||||||
|
# @option attributes [Integer] :element Weapon's elemental affinity
|
||||||
|
# @option attributes [Integer] :proficiency Weapon proficiency type
|
||||||
|
# @option attributes [Integer] :series Weapon series or collection
|
||||||
|
# @option attributes [Boolean] :flb Flag for FLB status
|
||||||
|
# @option attributes [Boolean] :ulb Flag for ULB status
|
||||||
|
# @option attributes [Boolean] :extra Flag indicating whether weapon can be slotted in Extra slots
|
||||||
|
# @option attributes [Boolean] :limit Flag indicating only one of this weapon can be equipped at once
|
||||||
|
# @option attributes [Boolean] :ax Flag indicating whether weapon supports AX skills
|
||||||
|
# @option attributes [Boolean] :transcendence Flag for transcendence status
|
||||||
|
# @option attributes [Integer] :max_level Maximum level of the weapon
|
||||||
|
# @option attributes [Integer] :max_skill_level Maximum skill level
|
||||||
|
# @option attributes [Integer] :max_awakening_level Maximum awakening level
|
||||||
|
# @option attributes [Integer] :ax_type AX type classification
|
||||||
|
# @option attributes [Integer] :min_hp Minimum HP
|
||||||
|
# @option attributes [Integer] :max_hp Maximum HP
|
||||||
|
# @option attributes [Integer] :max_hp_flb Maximum HP after FLB
|
||||||
|
# @option attributes [Integer] :max_hp_ulb Maximum HP after ULB
|
||||||
|
# @option attributes [Integer] :min_atk Minimum attack
|
||||||
|
# @option attributes [Integer] :max_atk Maximum attack
|
||||||
|
# @option attributes [Integer] :max_atk_flb Maximum attack after FLB
|
||||||
|
# @option attributes [Integer] :max_atk_ulb Maximum attack after ULB
|
||||||
|
# @option attributes [String] :recruits The granblue_id of the character this weapon recruits, if any
|
||||||
|
# @option attributes [String] :release_date Weapon release date
|
||||||
|
# @option attributes [String] :flb_date Date FLB was implemented
|
||||||
|
# @option attributes [String] :ulb_date Date ULB was implemented
|
||||||
|
# @option attributes [String] :transcendence_date Date transcendence was implemented
|
||||||
|
# @option attributes [String] :wiki_en English wiki link
|
||||||
|
# @option attributes [String] :wiki_ja Japanese wiki link
|
||||||
|
# @option attributes [String] :gamewith Gamewith link
|
||||||
|
# @option attributes [String] :kamigame Kamigame link
|
||||||
|
# @option attributes [Array<String>] :nicknames_en English nicknames
|
||||||
|
# @option attributes [Array<String>] :nicknames_jp Japanese nicknames
|
||||||
|
#
|
||||||
|
# @raise [ImportError] If required attributes are missing or invalid
|
||||||
def build_attributes(row)
|
def build_attributes(row)
|
||||||
{
|
{
|
||||||
name_en: parse_value(row['name_en']),
|
name_en: parse_value(row['name_en']),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ module Granblue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Base class for transforming game data into standardized format
|
||||||
|
# @abstract
|
||||||
class BaseTransformer
|
class BaseTransformer
|
||||||
|
# Mapping of game element IDs to internal element IDs
|
||||||
|
# @return [Hash<Integer, Integer?>]
|
||||||
ELEMENT_MAPPING = {
|
ELEMENT_MAPPING = {
|
||||||
0 => nil,
|
0 => nil,
|
||||||
1 => 4, # Wind -> Earth
|
1 => 4, # Wind -> Earth
|
||||||
|
|
@ -22,6 +26,12 @@ module Granblue
|
||||||
6 => 5 # Light -> Dark
|
6 => 5 # Light -> Dark
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
# Initialize a new transformer
|
||||||
|
# @param data [Object] Raw game data to transform
|
||||||
|
# @param options [Hash<Symbol, Object>] Optional configuration settings
|
||||||
|
# @option options [String] :language ('en') Language code for transformations
|
||||||
|
# @option options [Boolean] :debug (false) Enable debug logging
|
||||||
|
# @return [void]
|
||||||
def initialize(data, options = {})
|
def initialize(data, options = {})
|
||||||
@data = data
|
@data = data
|
||||||
@options = options
|
@options = options
|
||||||
|
|
@ -30,6 +40,9 @@ module Granblue
|
||||||
validate_data
|
validate_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Transform the raw data into standardized format
|
||||||
|
# @abstract Subclasses must implement this method
|
||||||
|
# @return [Object] Transformed data
|
||||||
def transform
|
def transform
|
||||||
raise NotImplementedError, "#{self.class} must implement #transform"
|
raise NotImplementedError, "#{self.class} must implement #transform"
|
||||||
end
|
end
|
||||||
|
|
@ -38,6 +51,8 @@ module Granblue
|
||||||
|
|
||||||
attr_reader :data, :options, :language
|
attr_reader :data, :options, :language
|
||||||
|
|
||||||
|
# Validate the input data structure
|
||||||
|
# @return [Boolean] true if valid, false otherwise
|
||||||
def validate_data
|
def validate_data
|
||||||
Rails.logger.info "[TRANSFORM] Validating data: #{data.inspect[0..100]}..."
|
Rails.logger.info "[TRANSFORM] Validating data: #{data.inspect[0..100]}..."
|
||||||
|
|
||||||
|
|
@ -55,6 +70,9 @@ module Granblue
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Extract master and parameter data from an object
|
||||||
|
# @param obj [Hash<String, Object>] Object containing master/param data
|
||||||
|
# @return [Array<(Hash?, Hash?)>] Array containing master and param data
|
||||||
def get_master_param(obj)
|
def get_master_param(obj)
|
||||||
return [nil, nil] unless obj.is_a?(Hash)
|
return [nil, nil] unless obj.is_a?(Hash)
|
||||||
|
|
||||||
|
|
@ -65,6 +83,9 @@ module Granblue
|
||||||
[master, param]
|
[master, param]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Log a debug message if debug mode is enabled
|
||||||
|
# @param message [String] Message to log
|
||||||
|
# @return [void]
|
||||||
def log_debug(message)
|
def log_debug(message)
|
||||||
return unless options[:debug]
|
return unless options[:debug]
|
||||||
Rails.logger.debug "[TRANSFORM-DEBUG] #{self.class.name}: #{message}"
|
Rails.logger.debug "[TRANSFORM-DEBUG] #{self.class.name}: #{message}"
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,41 @@
|
||||||
module Granblue
|
module Granblue
|
||||||
module Transformers
|
module Transformers
|
||||||
|
# Transforms raw game character data into standardized format for database import.
|
||||||
|
# Handles character stats, uncap levels, transcendence, and perpetuity rings.
|
||||||
|
#
|
||||||
|
# @example Transforming character data
|
||||||
|
# data = { "master" => { "name" => "Katalina", "id" => "3040001000" },
|
||||||
|
# "param" => { "evolution" => 3, "phase" => 1 } }
|
||||||
|
# transformer = CharacterTransformer.new(data)
|
||||||
|
# result = transformer.transform
|
||||||
|
# # => [{ name: "Katalina", id: "3040001000", uncap: 3, transcend: 1 }]
|
||||||
|
#
|
||||||
|
# @note Expects data with "master" and "param" nested objects for each character
|
||||||
|
# @note Will filter out characters with missing or invalid required attributes
|
||||||
|
#
|
||||||
|
# @see BaseTransformer For base transformation functionality
|
||||||
class CharacterTransformer < BaseTransformer
|
class CharacterTransformer < BaseTransformer
|
||||||
|
# Transforms raw game character data into a standardized format
|
||||||
|
# @return [Array<Hash>] Array of character hashes with standardized attributes:
|
||||||
|
# @option character [String] :name Character's name
|
||||||
|
# @option character [String] :id Character's ID
|
||||||
|
# @option character [Integer] :uncap Character's uncap level
|
||||||
|
# @option character [Boolean] :ringed Whether character has perpetuity rings
|
||||||
|
# @option character [Integer] :transcend Character's transcendence phase level
|
||||||
def transform
|
def transform
|
||||||
|
# Log start of transformation process
|
||||||
Rails.logger.info "[TRANSFORM] Starting CharacterTransformer#transform"
|
Rails.logger.info "[TRANSFORM] Starting CharacterTransformer#transform"
|
||||||
|
|
||||||
|
# Validate that the input data is a Hash
|
||||||
unless data.is_a?(Hash)
|
unless data.is_a?(Hash)
|
||||||
Rails.logger.error "[TRANSFORM] Invalid character data structure"
|
Rails.logger.error "[TRANSFORM] Invalid character data structure"
|
||||||
return []
|
return []
|
||||||
end
|
end
|
||||||
|
|
||||||
characters = []
|
characters = []
|
||||||
|
# Iterate through each character data entry
|
||||||
data.each_value do |char_data|
|
data.each_value do |char_data|
|
||||||
|
# Skip entries missing required master/param data
|
||||||
next unless char_data['master'] && char_data['param']
|
next unless char_data['master'] && char_data['param']
|
||||||
|
|
||||||
master = char_data['master']
|
master = char_data['master']
|
||||||
|
|
@ -18,27 +43,29 @@ module Granblue
|
||||||
|
|
||||||
Rails.logger.debug "[TRANSFORM] Processing character: #{master['name']}"
|
Rails.logger.debug "[TRANSFORM] Processing character: #{master['name']}"
|
||||||
|
|
||||||
|
# Build base character hash with required attributes
|
||||||
character = {
|
character = {
|
||||||
name: master['name'],
|
name: master['name'], # Character's display name
|
||||||
id: master['id'],
|
id: master['id'], # Unique identifier
|
||||||
uncap: param['evolution'].to_i
|
uncap: param['evolution'].to_i # Current uncap level
|
||||||
}
|
}
|
||||||
|
|
||||||
Rails.logger.debug "[TRANSFORM] Base character data: #{character}"
|
Rails.logger.debug "[TRANSFORM] Base character data: #{character}"
|
||||||
|
|
||||||
# Add perpetuity (rings) if present
|
# Add perpetuity ring status if present
|
||||||
if param['has_npcaugment_constant']
|
if param['has_npcaugment_constant']
|
||||||
character[:ringed] = true
|
character[:ringed] = true
|
||||||
Rails.logger.debug "[TRANSFORM] Character is ringed"
|
Rails.logger.debug "[TRANSFORM] Character is ringed"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add transcendence if present
|
# Add transcendence level if present (stored as 'phase' in raw data)
|
||||||
phase = param['phase'].to_i
|
phase = param['phase'].to_i
|
||||||
if phase && phase.positive?
|
if phase&.positive?
|
||||||
character[:transcend] = phase
|
character[:transcend] = phase
|
||||||
Rails.logger.debug "[TRANSFORM] Character has transcendence: #{phase}"
|
Rails.logger.debug "[TRANSFORM] Character has transcendence: #{phase}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Only add characters with valid IDs to result set
|
||||||
characters << character unless master['id'].nil?
|
characters << character unless master['id'].nil?
|
||||||
Rails.logger.info "[TRANSFORM] Successfully processed character #{character[:name]}"
|
Rails.logger.info "[TRANSFORM] Successfully processed character #{character[:name]}"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,45 @@
|
||||||
|
|
||||||
module Granblue
|
module Granblue
|
||||||
module Transformers
|
module Transformers
|
||||||
|
# Transforms raw game summon data into standardized format for database import.
|
||||||
|
# Handles summon stats, uncap levels, transcendence, and quick summon status.
|
||||||
|
#
|
||||||
|
# @example Transforming summon data
|
||||||
|
# data = {
|
||||||
|
# "master" => { "name" => "Bahamut", "id" => "2040003000" },
|
||||||
|
# "param" => { "evolution" => 5, "level" => 200 }
|
||||||
|
# }
|
||||||
|
# transformer = SummonTransformer.new(data, "2040003000")
|
||||||
|
# result = transformer.transform
|
||||||
|
# # => [{ name: "Bahamut", id: "2040003000", uncap: 5, transcend: 1, qs: true }]
|
||||||
|
#
|
||||||
|
# @note Expects data with "master" and "param" nested objects for each summon
|
||||||
|
# @note Handles quick summon status if ID matches provided quick_summon_id
|
||||||
|
#
|
||||||
|
# @see BaseTransformer For base transformation functionality
|
||||||
class SummonTransformer < BaseTransformer
|
class SummonTransformer < BaseTransformer
|
||||||
|
# @return [Array<Integer>] Level thresholds for determining transcendence level
|
||||||
TRANSCENDENCE_LEVELS = [210, 220, 230, 240].freeze
|
TRANSCENDENCE_LEVELS = [210, 220, 230, 240].freeze
|
||||||
|
|
||||||
|
# Creates a new summon transformer
|
||||||
|
# @param data [Object] Raw summon data to transform
|
||||||
|
# @param quick_summon_id [String, nil] ID of the current quick summon
|
||||||
|
# @param options [Hash] Additional transformation options
|
||||||
|
# @option options [String] :language ('en') Language for names
|
||||||
|
# @option options [Boolean] :debug (false) Enable debug logging
|
||||||
|
# @return [void]
|
||||||
def initialize(data, quick_summon_id = nil, options = {})
|
def initialize(data, quick_summon_id = nil, options = {})
|
||||||
super(data, options)
|
super(data, options)
|
||||||
@quick_summon_id = quick_summon_id
|
@quick_summon_id = quick_summon_id
|
||||||
Rails.logger.info "[TRANSFORM] Initializing SummonTransformer with quick_summon_id: #{quick_summon_id}"
|
Rails.logger.info "[TRANSFORM] Initializing SummonTransformer with quick_summon_id: #{quick_summon_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Transform raw summon data into standardized format
|
||||||
|
# @return [Array<Hash>] Array of transformed summon data
|
||||||
def transform
|
def transform
|
||||||
Rails.logger.info "[TRANSFORM] Starting SummonTransformer#transform"
|
Rails.logger.info "[TRANSFORM] Starting SummonTransformer#transform"
|
||||||
|
|
||||||
|
# Validate that input data is a Hash
|
||||||
unless data.is_a?(Hash)
|
unless data.is_a?(Hash)
|
||||||
Rails.logger.error "[TRANSFORM] Invalid summon data structure"
|
Rails.logger.error "[TRANSFORM] Invalid summon data structure"
|
||||||
Rails.logger.error "[TRANSFORM] Data class: #{data.class}"
|
Rails.logger.error "[TRANSFORM] Data class: #{data.class}"
|
||||||
|
|
@ -21,24 +48,27 @@ module Granblue
|
||||||
end
|
end
|
||||||
|
|
||||||
summons = []
|
summons = []
|
||||||
|
# Process each summon in the data
|
||||||
data.each_value do |summon_data|
|
data.each_value do |summon_data|
|
||||||
Rails.logger.debug "[TRANSFORM] Processing summon: #{summon_data['master']['name'] if summon_data['master']}"
|
Rails.logger.debug "[TRANSFORM] Processing summon: #{summon_data['master']['name'] if summon_data['master']}"
|
||||||
|
|
||||||
|
# Extract master and parameter data
|
||||||
master, param = get_master_param(summon_data)
|
master, param = get_master_param(summon_data)
|
||||||
unless master && param
|
unless master && param
|
||||||
Rails.logger.debug "[TRANSFORM] Skipping summon - missing master or param data"
|
Rails.logger.debug "[TRANSFORM] Skipping summon - missing master or param data"
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Build base summon hash with required attributes
|
||||||
summon = {
|
summon = {
|
||||||
name: master['name'],
|
name: master['name'], # Summon's display name
|
||||||
id: master['id'],
|
id: master['id'], # Unique identifier
|
||||||
uncap: param['evolution'].to_i
|
uncap: param['evolution'].to_i # Current uncap level
|
||||||
}
|
}
|
||||||
|
|
||||||
Rails.logger.debug "[TRANSFORM] Base summon data: #{summon}"
|
Rails.logger.debug "[TRANSFORM] Base summon data: #{summon}"
|
||||||
|
|
||||||
# Add transcendence if applicable
|
# Add transcendence level for highly uncapped summons
|
||||||
if summon[:uncap] > 5
|
if summon[:uncap] > 5
|
||||||
level = param['level'].to_i
|
level = param['level'].to_i
|
||||||
trans = calculate_transcendence_level(level)
|
trans = calculate_transcendence_level(level)
|
||||||
|
|
@ -46,7 +76,7 @@ module Granblue
|
||||||
Rails.logger.debug "[TRANSFORM] Added transcendence level: #{trans}"
|
Rails.logger.debug "[TRANSFORM] Added transcendence level: #{trans}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Mark quick summon if applicable
|
# Mark quick summon status if this summon matches quick_summon_id
|
||||||
if @quick_summon_id && param['id'].to_s == @quick_summon_id.to_s
|
if @quick_summon_id && param['id'].to_s == @quick_summon_id.to_s
|
||||||
summon[:qs] = true
|
summon[:qs] = true
|
||||||
Rails.logger.debug "[TRANSFORM] Marked as quick summon"
|
Rails.logger.debug "[TRANSFORM] Marked as quick summon"
|
||||||
|
|
@ -62,6 +92,9 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Calculates transcendence level based on summon level
|
||||||
|
# @param level [Integer, nil] Current summon level
|
||||||
|
# @return [Integer] Calculated transcendence level (1-5)
|
||||||
def calculate_transcendence_level(level)
|
def calculate_transcendence_level(level)
|
||||||
return 1 unless level
|
return 1 unless level
|
||||||
level = 1 + TRANSCENDENCE_LEVELS.count { |cutoff| level > cutoff }
|
level = 1 + TRANSCENDENCE_LEVELS.count { |cutoff| level > cutoff }
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,47 @@
|
||||||
module Granblue
|
module Granblue
|
||||||
module Transformers
|
module Transformers
|
||||||
|
# Transforms raw game weapon data into standardized format for database import.
|
||||||
|
# Handles weapon stats, uncap levels, transcendence, awakening, AX skills, and weapon keys.
|
||||||
|
#
|
||||||
|
# @example Transforming weapon data
|
||||||
|
# data = {
|
||||||
|
# "master" => { "name" => "Luminiera Sword Omega", "id" => "1040007100", "series_id" => 1 },
|
||||||
|
# "param" => { "level" => 150, "arousal" => { "is_arousal_weapon" => true } }
|
||||||
|
# }
|
||||||
|
# transformer = WeaponTransformer.new(data)
|
||||||
|
# result = transformer.transform
|
||||||
|
# # => [{ name: "Luminiera Sword Omega", id: "1040007100", uncap: 4, ... }]
|
||||||
|
#
|
||||||
|
# @note Expects data with "master" and "param" nested objects for each weapon
|
||||||
|
# @note Special handling for multi-element weapons from specific series
|
||||||
|
#
|
||||||
|
# @see BaseTransformer For base transformation functionality
|
||||||
class WeaponTransformer < BaseTransformer
|
class WeaponTransformer < BaseTransformer
|
||||||
|
# @return [Array<Integer>] Level thresholds for determining uncap level
|
||||||
UNCAP_LEVELS = [40, 60, 80, 100, 150, 200].freeze
|
UNCAP_LEVELS = [40, 60, 80, 100, 150, 200].freeze
|
||||||
|
|
||||||
|
# @return [Array<Integer>] Level thresholds for determining transcendence level
|
||||||
TRANSCENDENCE_LEVELS = [210, 220, 230, 240].freeze
|
TRANSCENDENCE_LEVELS = [210, 220, 230, 240].freeze
|
||||||
|
|
||||||
|
# @return [Array<Integer>] Weapon series IDs that can have multiple elements
|
||||||
MULTIELEMENT_SERIES = [13, 17, 19].freeze
|
MULTIELEMENT_SERIES = [13, 17, 19].freeze
|
||||||
|
|
||||||
|
# Transform raw weapon data into standardized format
|
||||||
|
# @return [Array<Hash>] Array of transformed weapon data
|
||||||
def transform
|
def transform
|
||||||
|
# Log start of transformation process
|
||||||
Rails.logger.info "[TRANSFORM] Starting WeaponTransformer#transform"
|
Rails.logger.info "[TRANSFORM] Starting WeaponTransformer#transform"
|
||||||
|
|
||||||
|
# Validate that the input data is a Hash
|
||||||
unless data.is_a?(Hash)
|
unless data.is_a?(Hash)
|
||||||
Rails.logger.error "[TRANSFORM] Invalid weapon data structure"
|
Rails.logger.error "[TRANSFORM] Invalid weapon data structure"
|
||||||
return []
|
return []
|
||||||
end
|
end
|
||||||
|
|
||||||
weapons = []
|
weapons = []
|
||||||
|
# Iterate through each weapon entry in the data
|
||||||
data.each_value do |weapon_data|
|
data.each_value do |weapon_data|
|
||||||
|
# Skip entries missing required master/param data
|
||||||
next unless weapon_data['master'] && weapon_data['param']
|
next unless weapon_data['master'] && weapon_data['param']
|
||||||
|
|
||||||
master = weapon_data['master']
|
master = weapon_data['master']
|
||||||
|
|
@ -22,18 +49,23 @@ module Granblue
|
||||||
|
|
||||||
Rails.logger.debug "[TRANSFORM] Processing weapon: #{master['name']}"
|
Rails.logger.debug "[TRANSFORM] Processing weapon: #{master['name']}"
|
||||||
|
|
||||||
|
# Transform base weapon attributes (ID, name, uncap level, etc)
|
||||||
weapon = transform_base_attributes(master, param)
|
weapon = transform_base_attributes(master, param)
|
||||||
Rails.logger.debug "[TRANSFORM] Base weapon attributes: #{weapon}"
|
Rails.logger.debug "[TRANSFORM] Base weapon attributes: #{weapon}"
|
||||||
|
|
||||||
|
# Add awakening data if present
|
||||||
weapon.merge!(transform_awakening(param))
|
weapon.merge!(transform_awakening(param))
|
||||||
Rails.logger.debug "[TRANSFORM] After awakening: #{weapon[:awakening] if weapon[:awakening]}"
|
Rails.logger.debug "[TRANSFORM] After awakening: #{weapon[:awakening] if weapon[:awakening]}"
|
||||||
|
|
||||||
|
# Add AX skills if present
|
||||||
weapon.merge!(transform_ax_skills(param))
|
weapon.merge!(transform_ax_skills(param))
|
||||||
Rails.logger.debug "[TRANSFORM] After AX skills: #{weapon[:ax] if weapon[:ax]}"
|
Rails.logger.debug "[TRANSFORM] After AX skills: #{weapon[:ax] if weapon[:ax]}"
|
||||||
|
|
||||||
|
# Add weapon keys if present
|
||||||
weapon.merge!(transform_weapon_keys(weapon_data))
|
weapon.merge!(transform_weapon_keys(weapon_data))
|
||||||
Rails.logger.debug "[TRANSFORM] After weapon keys: #{weapon[:keys] if weapon[:keys]}"
|
Rails.logger.debug "[TRANSFORM] After weapon keys: #{weapon[:keys] if weapon[:keys]}"
|
||||||
|
|
||||||
|
# Only add weapons with valid IDs
|
||||||
weapons << weapon unless master['id'].nil?
|
weapons << weapon unless master['id'].nil?
|
||||||
Rails.logger.info "[TRANSFORM] Successfully processed weapon #{weapon[:name]}"
|
Rails.logger.info "[TRANSFORM] Successfully processed weapon #{weapon[:name]}"
|
||||||
end
|
end
|
||||||
|
|
@ -44,6 +76,10 @@ module Granblue
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Transforms the core weapon attributes from master and param data
|
||||||
|
# @param master [Hash] Master data containing basic weapon information
|
||||||
|
# @param param [Hash] Parameter data containing weapon's current state
|
||||||
|
# @return [Hash] Base weapon attributes including ID, name, uncap level, etc
|
||||||
def transform_base_attributes(master, param)
|
def transform_base_attributes(master, param)
|
||||||
Rails.logger.debug "[TRANSFORM] Processing base attributes for weapon"
|
Rails.logger.debug "[TRANSFORM] Processing base attributes for weapon"
|
||||||
|
|
||||||
|
|
@ -77,6 +113,9 @@ module Granblue
|
||||||
weapon
|
weapon
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Transforms weapon awakening data if present
|
||||||
|
# @param param [Hash] Parameter data containing awakening information
|
||||||
|
# @return [Hash] Awakening type and level if weapon is awakened
|
||||||
def transform_awakening(param)
|
def transform_awakening(param)
|
||||||
return {} unless param['arousal']&.[]('is_arousal_weapon')
|
return {} unless param['arousal']&.[]('is_arousal_weapon')
|
||||||
|
|
||||||
|
|
@ -89,6 +128,9 @@ module Granblue
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Transforms AX skill data if present
|
||||||
|
# @param param [Hash] Parameter data containing AX skill information
|
||||||
|
# @return [Hash] Array of AX skills with IDs and values
|
||||||
def transform_ax_skills(param)
|
def transform_ax_skills(param)
|
||||||
augments = param['augment_skill_info']
|
augments = param['augment_skill_info']
|
||||||
return {} unless augments&.first&.any?
|
return {} unless augments&.first&.any?
|
||||||
|
|
@ -107,6 +149,9 @@ module Granblue
|
||||||
{ ax: ax }
|
{ ax: ax }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Transforms weapon key data if present
|
||||||
|
# @param weapon_data [Hash] Full weapon data containing key information
|
||||||
|
# @return [Hash] Array of weapon key IDs
|
||||||
def transform_weapon_keys(weapon_data)
|
def transform_weapon_keys(weapon_data)
|
||||||
Rails.logger.debug "[TRANSFORM] Processing weapon keys"
|
Rails.logger.debug "[TRANSFORM] Processing weapon keys"
|
||||||
keys = []
|
keys = []
|
||||||
|
|
@ -122,11 +167,17 @@ module Granblue
|
||||||
keys.any? ? { keys: keys } : {}
|
keys.any? ? { keys: keys } : {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Calculates uncap level based on weapon level
|
||||||
|
# @param level [Integer, nil] Current weapon level
|
||||||
|
# @return [Integer] Calculated uncap level
|
||||||
def calculate_uncap_level(level)
|
def calculate_uncap_level(level)
|
||||||
return 0 unless level
|
return 0 unless level
|
||||||
UNCAP_LEVELS.count { |cutoff| level.to_i > cutoff }
|
UNCAP_LEVELS.count { |cutoff| level.to_i > cutoff }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Calculates transcendence level based on weapon level
|
||||||
|
# @param level [Integer, nil] Current weapon level
|
||||||
|
# @return [Integer] Calculated transcendence level
|
||||||
def calculate_transcendence_level(level)
|
def calculate_transcendence_level(level)
|
||||||
return 1 unless level
|
return 1 unless level
|
||||||
1 + TRANSCENDENCE_LEVELS.count { |cutoff| level.to_i > cutoff }
|
1 + TRANSCENDENCE_LEVELS.count { |cutoff| level.to_i > cutoff }
|
||||||
|
|
|
||||||
53
sig/granblue/downloaders/base_downloader.rbs
Normal file
53
sig/granblue/downloaders/base_downloader.rbs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
module Granblue
|
||||||
|
module Downloaders
|
||||||
|
class BaseDownloader
|
||||||
|
SIZES: Array[String]
|
||||||
|
|
||||||
|
# Define allowed storage types
|
||||||
|
type storage = :local | :s3 | :both
|
||||||
|
|
||||||
|
@id: String
|
||||||
|
@base_url: String
|
||||||
|
@test_mode: bool
|
||||||
|
@verbose: bool
|
||||||
|
@storage: storage
|
||||||
|
@aws_service: AwsService
|
||||||
|
|
||||||
|
def initialize: (String id, ?test_mode: bool, ?verbose: bool, ?storage: storage) -> void
|
||||||
|
|
||||||
|
def download: -> void
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def process_download: (String url, String size, String path, ?last: bool) -> void
|
||||||
|
|
||||||
|
def download_to_local: (String url, String download_uri) -> void
|
||||||
|
|
||||||
|
def stream_to_s3: (String url, String s3_key) -> void
|
||||||
|
|
||||||
|
def download_to_both: (String url, String download_uri, String s3_key) -> void
|
||||||
|
|
||||||
|
def should_download?: (String local_path, String s3_key) -> bool
|
||||||
|
|
||||||
|
def ensure_directories_exist: -> void
|
||||||
|
|
||||||
|
def store_locally?: -> bool
|
||||||
|
|
||||||
|
def download_path: (String size) -> String
|
||||||
|
|
||||||
|
def build_s3_key: (String size, String filename) -> String
|
||||||
|
|
||||||
|
def log_info: (String message) -> void
|
||||||
|
|
||||||
|
def download_elemental_image: (String url, String size, String path, String filename) -> void
|
||||||
|
|
||||||
|
def object_type: -> String
|
||||||
|
|
||||||
|
def base_url: -> String
|
||||||
|
|
||||||
|
def directory_for_size: (String size) -> String
|
||||||
|
|
||||||
|
def build_url: (String size) -> String
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
28
sig/granblue/downloaders/character_downloader.rbs
Normal file
28
sig/granblue/downloaders/character_downloader.rbs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
module Granblue
|
||||||
|
module Downloaders
|
||||||
|
class CharacterDownloader < BaseDownloader
|
||||||
|
private
|
||||||
|
|
||||||
|
def download_variants: (Character character) -> void
|
||||||
|
|
||||||
|
def download_variant: (String variant_id) -> void
|
||||||
|
|
||||||
|
def build_variant_url: (String variant_id, String size) -> String
|
||||||
|
|
||||||
|
def object_type: -> String
|
||||||
|
|
||||||
|
def base_url: -> String
|
||||||
|
|
||||||
|
def directory_for_size: (String size) -> String
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
@id: String
|
||||||
|
@base_url: String
|
||||||
|
@test_mode: bool
|
||||||
|
@verbose: bool
|
||||||
|
@storage: Symbol
|
||||||
|
@aws_service: AwsService
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
15
sig/granblue/downloaders/download_manager.rbs
Normal file
15
sig/granblue/downloaders/download_manager.rbs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
module Granblue
|
||||||
|
module Downloaders
|
||||||
|
class DownloadManager
|
||||||
|
def self.download_for_object: (
|
||||||
|
String type,
|
||||||
|
String granblue_id,
|
||||||
|
?test_mode: bool,
|
||||||
|
?verbose: bool,
|
||||||
|
?storage: Symbol
|
||||||
|
) -> void
|
||||||
|
|
||||||
|
private
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
30
sig/granblue/downloaders/summon_downloader.rbs
Normal file
30
sig/granblue/downloaders/summon_downloader.rbs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
module Granblue
|
||||||
|
module Downloaders
|
||||||
|
class SummonDownloader < BaseDownloader
|
||||||
|
def download: -> void
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def download_variants: (Summon summon) -> void
|
||||||
|
|
||||||
|
def download_variant: (String variant_id) -> void
|
||||||
|
|
||||||
|
def build_variant_url: (String variant_id, String size) -> String
|
||||||
|
|
||||||
|
def object_type: -> String
|
||||||
|
|
||||||
|
def base_url: -> String
|
||||||
|
|
||||||
|
def directory_for_size: (String size) -> String
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
@id: String
|
||||||
|
@base_url: String
|
||||||
|
@test_mode: bool
|
||||||
|
@verbose: bool
|
||||||
|
@storage: Symbol
|
||||||
|
@aws_service: AwsService
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
48
sig/granblue/downloaders/weapon_downloader.rbs
Normal file
48
sig/granblue/downloaders/weapon_downloader.rbs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
module Granblue
|
||||||
|
module Downloaders
|
||||||
|
class WeaponDownloader < BaseDownloader
|
||||||
|
def download: -> void
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def download_variants: (Weapon weapon) -> void
|
||||||
|
|
||||||
|
def download_variant: (String variant_id) -> void
|
||||||
|
|
||||||
|
def build_variant_url: (String variant_id, String size) -> String
|
||||||
|
|
||||||
|
def object_type: -> String
|
||||||
|
|
||||||
|
def base_url: -> String
|
||||||
|
|
||||||
|
def directory_for_size: (String size) -> String
|
||||||
|
|
||||||
|
def build_url_for_id: (String id, String size) -> String
|
||||||
|
|
||||||
|
# Track progress of elemental weapon downloads
|
||||||
|
def progress_reporter: (count: Integer, total: Integer, result: String, ?bar_len: Integer) -> void
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
@id: String
|
||||||
|
@base_url: String
|
||||||
|
@test_mode: bool
|
||||||
|
@verbose: bool
|
||||||
|
@storage: Symbol
|
||||||
|
@aws_service: AwsService
|
||||||
|
end
|
||||||
|
|
||||||
|
# Special downloader for handling elemental weapon variants
|
||||||
|
class ElementalWeaponDownloader < WeaponDownloader
|
||||||
|
SUFFIXES: Array[Integer]
|
||||||
|
|
||||||
|
def initialize: (Integer id_base) -> void
|
||||||
|
|
||||||
|
def download: -> void
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
@id_base: Integer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
80
sig/granblue/importers/base_importer.rbs
Normal file
80
sig/granblue/importers/base_importer.rbs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
module Granblue
|
||||||
|
module Importers
|
||||||
|
class BaseImporter
|
||||||
|
attr_reader new_records: Hash[String, Array[Hash[Symbol, untyped]]]
|
||||||
|
attr_reader updated_records: Hash[String, Array[Hash[Symbol, untyped]]]
|
||||||
|
|
||||||
|
def initialize: (
|
||||||
|
String file_path,
|
||||||
|
?test_mode: bool,
|
||||||
|
?verbose: bool,
|
||||||
|
?logger: untyped
|
||||||
|
) -> void
|
||||||
|
|
||||||
|
def import: -> Hash[Symbol, Hash[String, Array[Hash[Symbol, untyped]]]]
|
||||||
|
|
||||||
|
def simulate_import: -> Hash[Symbol, Hash[String, Array[Hash[Symbol, untyped]]]]
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def import_row: (CSV::Row row) -> void
|
||||||
|
|
||||||
|
def find_or_create_record: (Hash[Symbol, untyped] attributes) -> [untyped, bool]?
|
||||||
|
|
||||||
|
def simulate_create: (
|
||||||
|
Hash[Symbol, untyped] attributes,
|
||||||
|
Hash[String, Array[Hash[Symbol, untyped]]] simulated_new,
|
||||||
|
String type
|
||||||
|
) -> void
|
||||||
|
|
||||||
|
def simulate_update: (
|
||||||
|
untyped existing_record,
|
||||||
|
Hash[Symbol, untyped] attributes,
|
||||||
|
Hash[String, Array[Hash[Symbol, untyped]]] simulated_updated,
|
||||||
|
String type
|
||||||
|
) -> void
|
||||||
|
|
||||||
|
def validate_required_attributes: (Hash[Symbol, untyped] attributes) -> void
|
||||||
|
|
||||||
|
def validate_update_attributes: (Hash[Symbol, untyped] update_attributes) -> void
|
||||||
|
|
||||||
|
def validate_record: (untyped record) -> void
|
||||||
|
|
||||||
|
def track_record: ([untyped, bool] result) -> void
|
||||||
|
|
||||||
|
def format_attributes: (Hash[Symbol, untyped] attributes) -> String
|
||||||
|
|
||||||
|
def log_test_update: (untyped record, Hash[Symbol, untyped] attributes) -> void
|
||||||
|
|
||||||
|
def log_test_creation: (Hash[Symbol, untyped] attributes) -> void
|
||||||
|
|
||||||
|
def log_new_record: (untyped record) -> void
|
||||||
|
|
||||||
|
def log_updated_record: (untyped record) -> void
|
||||||
|
|
||||||
|
def parse_value: (String? value) -> String?
|
||||||
|
|
||||||
|
def parse_integer: (String? value) -> Integer?
|
||||||
|
|
||||||
|
def parse_float: (String? value) -> Float?
|
||||||
|
|
||||||
|
def parse_boolean: (String? value) -> bool?
|
||||||
|
|
||||||
|
def parse_date: (String? date_str) -> Date?
|
||||||
|
|
||||||
|
def parse_array: (String? array_str) -> Array[String]
|
||||||
|
|
||||||
|
def parse_integer_array: (String? array_str) -> Array[Integer]
|
||||||
|
|
||||||
|
def model_class: -> singleton(ActiveRecord::Base)
|
||||||
|
|
||||||
|
def build_attributes: (CSV::Row row) -> Hash[Symbol, untyped]
|
||||||
|
|
||||||
|
def handle_error: (StandardError error) -> void
|
||||||
|
|
||||||
|
def format_validation_error: (ActiveRecord::RecordInvalid error) -> String
|
||||||
|
|
||||||
|
def format_standard_error: (StandardError error) -> String
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
20
sig/granblue/importers/import_error.rbs
Normal file
20
sig/granblue/importers/import_error.rbs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Granblue
|
||||||
|
module Importers
|
||||||
|
class ImportError
|
||||||
|
attr_reader file_name: String
|
||||||
|
attr_reader details: String
|
||||||
|
|
||||||
|
def initialize: (file_name: String, details: String) -> void
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_message: () -> String
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_attributes: (
|
||||||
|
attributes: Hash[Symbol, String | Integer | Float | Boolean | Array[untyped] | nil]
|
||||||
|
) -> String
|
||||||
|
end
|
||||||
|
end
|
||||||
31
sig/granblue/transformers/base_transformer.rbs
Normal file
31
sig/granblue/transformers/base_transformer.rbs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
module Granblue
|
||||||
|
module Transformers
|
||||||
|
class TransformerError < StandardError
|
||||||
|
attr_reader details: untyped
|
||||||
|
|
||||||
|
def initialize: (String message, ?untyped details) -> void
|
||||||
|
end
|
||||||
|
|
||||||
|
class BaseTransformer
|
||||||
|
ELEMENT_MAPPING: Hash[Integer, Integer?]
|
||||||
|
|
||||||
|
@data: untyped
|
||||||
|
@options: Hash[Symbol, untyped]
|
||||||
|
@language: String
|
||||||
|
|
||||||
|
attr_reader data: untyped
|
||||||
|
attr_reader options: Hash[Symbol, untyped]
|
||||||
|
attr_reader language: String
|
||||||
|
|
||||||
|
def initialize: (untyped data, ?Hash[Symbol, untyped] options) -> void
|
||||||
|
|
||||||
|
def transform: -> untyped
|
||||||
|
|
||||||
|
def validate_data: -> bool
|
||||||
|
|
||||||
|
def get_master_param: (Hash[String, untyped] obj) -> [Hash[String, untyped]?, Hash[String, untyped]?]
|
||||||
|
|
||||||
|
def log_debug: (String message) -> void
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
17
sig/granblue/transformers/summon_transformer.rbs
Normal file
17
sig/granblue/transformers/summon_transformer.rbs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
module Granblue
|
||||||
|
module Transformers
|
||||||
|
class SummonTransformer < BaseTransformer
|
||||||
|
TRANSCENDENCE_LEVELS: Array[Integer]
|
||||||
|
|
||||||
|
@quick_summon_id: String?
|
||||||
|
|
||||||
|
def initialize: (untyped data, ?String? quick_summon_id, ?Hash[Symbol, untyped] options) -> void
|
||||||
|
|
||||||
|
def transform: -> Array[Hash[Symbol, untyped]]
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def calculate_transcendence_level: (Integer? level) -> Integer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
27
sig/granblue/transformers/weapon_transformer.rbs
Normal file
27
sig/granblue/transformers/weapon_transformer.rbs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
module Granblue
|
||||||
|
module Transformers
|
||||||
|
class WeaponTransformer < BaseTransformer
|
||||||
|
# Constants for level calculations
|
||||||
|
UNCAP_LEVELS: Array[Integer]
|
||||||
|
TRANSCENDENCE_LEVELS: Array[Integer]
|
||||||
|
MULTIELEMENT_SERIES: Array[Integer]
|
||||||
|
|
||||||
|
# Implements abstract method from BaseTransformer
|
||||||
|
def transform: -> Array[Hash[Symbol, untyped]]
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def transform_base_attributes: (Hash[String, untyped] master, Hash[String, untyped] param) -> Hash[Symbol, untyped]
|
||||||
|
|
||||||
|
def transform_awakening: (Hash[String, untyped] param) -> Hash[Symbol, Hash[Symbol, untyped]]
|
||||||
|
|
||||||
|
def transform_ax_skills: (Hash[String, untyped] param) -> Hash[Symbol, Array[Hash[Symbol, untyped]]]
|
||||||
|
|
||||||
|
def transform_weapon_keys: (Hash[String, untyped] weapon_data) -> Hash[Symbol, Array[String]]
|
||||||
|
|
||||||
|
def calculate_uncap_level: (Integer? level) -> Integer
|
||||||
|
|
||||||
|
def calculate_transcendence_level: (Integer? level) -> Integer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue