Compare commits
11 commits
992954ce89
...
cd095eefcb
Author | SHA1 | Date | |
---|---|---|---|
cd095eefcb | |||
e0b5382c3f | |||
600cb6b2ea | |||
829d960d70 | |||
cf1cdf7e7d | |||
e8737a520e | |||
7fee4e6e41 | |||
c25e015b70 | |||
f8cf8165df | |||
b7f4c5b6ac | |||
6a4f2b91c1 |
7 changed files with 101 additions and 138 deletions
|
@ -1,37 +1,4 @@
|
|||
class PetTypesController < ApplicationController
|
||||
def index
|
||||
color = Color.find params[:color_id]
|
||||
pet_types = color.pet_types.includes(pet_states: [:swf_assets]).
|
||||
includes(:species)
|
||||
|
||||
# This is a relatively big request, for relatively static data. Let's
|
||||
# consider it fresh for 10min (so new pet releases show up quickly), but
|
||||
# it's also okay for the client to quickly serve the cached copy then
|
||||
# reload in the background, if it's been less than a day.
|
||||
expires_in 10.minutes, stale_while_revalidate: 1.day, public: true
|
||||
|
||||
# We dive deep to get all the right fields for appearance data, cuz this
|
||||
# endpoint is primarily used to power the preview on the item page!
|
||||
render json: pet_types.map { |pt|
|
||||
pt.as_json(
|
||||
only: [:id, :body_id],
|
||||
include: {
|
||||
species: {only: [:id], methods: [:name, :human_name]},
|
||||
canonical_pet_state: {
|
||||
only: [:id],
|
||||
methods: [:pose],
|
||||
include: {
|
||||
swf_assets: {
|
||||
only: [:id, :known_glitches],
|
||||
methods: [:zone, :restricted_zones, :urls],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def show
|
||||
@pet_type = PetType.
|
||||
where(species_id: params[:species_id]).
|
||||
|
|
|
@ -38,7 +38,7 @@ class AltStyle < ApplicationRecord
|
|||
swf_asset = swf_assets.first
|
||||
return nil if swf_asset.nil?
|
||||
|
||||
swf_asset.html5_image_url
|
||||
swf_asset.image_url
|
||||
end
|
||||
|
||||
def biology=(biology)
|
||||
|
|
|
@ -459,7 +459,7 @@ class Item < ApplicationRecord
|
|||
|
||||
# If there are no body-specific assets, return one appearance for them all.
|
||||
if swf_assets_by_body_id.empty?
|
||||
body = Body.new(0, nil)
|
||||
body = Appearance::Body.new(0, nil)
|
||||
return [Appearance.new(body, swf_assets_for_all_bodies)]
|
||||
end
|
||||
|
||||
|
|
|
@ -29,15 +29,6 @@ class PetType < ApplicationRecord
|
|||
color_ids = special_color ? [special_color.id] : Color.basic.select([:id]).map(&:id)
|
||||
where(color_id: color_ids)
|
||||
end
|
||||
|
||||
def self.standard_body_ids
|
||||
[].tap do |body_ids|
|
||||
# TODO: the nil hack is lame :P
|
||||
special_color_or_basic(nil).group_by(&:species_id).each do |species_id, pet_types|
|
||||
body_ids.concat(pet_types.map(&:body_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.random_basic_per_species(species_ids)
|
||||
random_pet_types = []
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
require 'addressable/template'
|
||||
require 'async'
|
||||
require 'async/barrier'
|
||||
require 'async/semaphore'
|
||||
|
@ -8,98 +9,29 @@ class SwfAsset < ApplicationRecord
|
|||
# We use the `type` column to mean something other than what Rails means!
|
||||
self.inheritance_column = nil
|
||||
|
||||
IMAGE_SIZES = {
|
||||
:small => [150, 150],
|
||||
:medium => [300, 300],
|
||||
:large => [600, 600]
|
||||
}
|
||||
# Used in `item_is_body_specific?`. (TODO: Could we refactor this out?)
|
||||
attr_accessor :item
|
||||
|
||||
belongs_to :zone
|
||||
has_many :parent_swf_asset_relationships
|
||||
|
||||
scope :includes_depth, -> { includes(:zone) }
|
||||
|
||||
before_validation :normalize_manifest_url, if: :manifest_url?
|
||||
|
||||
def swf_image_dir
|
||||
@swf_image_dir ||= Rails.root.join('tmp', 'asset_images_before_upload', self.id.to_s)
|
||||
end
|
||||
|
||||
def swf_image_path(size)
|
||||
swf_image_dir.join("#{size.join 'x'}.png")
|
||||
end
|
||||
|
||||
PARTITION_COUNT = 3
|
||||
PARTITION_DIGITS = 3
|
||||
PARTITION_ID_LENGTH = PARTITION_COUNT * PARTITION_DIGITS
|
||||
def partition_path
|
||||
(remote_id / 10**PARTITION_DIGITS).to_s.rjust(PARTITION_ID_LENGTH, '0').tap do |id_str|
|
||||
PARTITION_COUNT.times do |n|
|
||||
id_str.insert(PARTITION_ID_LENGTH - (n * PARTITION_DIGITS), '/')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def image_version
|
||||
converted_at.to_i
|
||||
end
|
||||
|
||||
def image_url(size=IMAGE_SIZES[:large])
|
||||
host = ASSET_HOSTS[:swf_asset_images]
|
||||
size_key = size.join('x')
|
||||
|
||||
image_dir = "#{self['type']}/#{partition_path}#{self.remote_id}"
|
||||
"https://#{host}/#{image_dir}/#{size_key}.png?#{image_version}"
|
||||
end
|
||||
|
||||
def images
|
||||
IMAGE_SIZES.values.map { |size| {:size => size, :url => image_url(size)} }
|
||||
end
|
||||
|
||||
attr_accessor :item
|
||||
|
||||
has_one :contribution, :as => :contributed, :inverse_of => :contributed
|
||||
has_many :parent_swf_asset_relationships
|
||||
|
||||
before_validation :normalize_manifest_url, if: :manifest_url?
|
||||
|
||||
delegate :depth, :to => :zone
|
||||
|
||||
def self.body_ids_fitting_standard
|
||||
@body_ids_fitting_standard ||= PetType.standard_body_ids + [0]
|
||||
end
|
||||
|
||||
scope :fitting_body_id, ->(body_id) {
|
||||
where(arel_table[:body_id].in([body_id, 0]))
|
||||
}
|
||||
|
||||
scope :fitting_standard_body_ids, -> {
|
||||
where(arel_table[:body_id].in(body_ids_fitting_standard))
|
||||
}
|
||||
|
||||
scope :fitting_color, ->(color) {
|
||||
body_ids = PetType.select(:body_id).where(:color_id => color.id).map(&:body_id)
|
||||
body_ids << 0
|
||||
where(arel_table[:body_id].in(body_ids))
|
||||
}
|
||||
|
||||
scope :biology_assets, -> { where(:type => PetState::SwfAssetType) }
|
||||
scope :object_assets, -> { where(:type => Item::SwfAssetType) }
|
||||
scope :for_item_ids, ->(item_ids) {
|
||||
joins(:parent_swf_asset_relationships).
|
||||
where(parent_swf_asset_relationships: {
|
||||
parent_type: "Item",
|
||||
parent_id: item_ids,
|
||||
})
|
||||
}
|
||||
scope :with_parent_ids, -> {
|
||||
select('swf_assets.*, parents_swf_assets.parent_id')
|
||||
}
|
||||
|
||||
# To manually change the body ID without triggering the usual change to 0,
|
||||
# use this override method.
|
||||
def override_body_id(new_body_id)
|
||||
@body_id_overridden = true
|
||||
self.body_id = new_body_id
|
||||
end
|
||||
CANVAS_MOVIE_IMAGE_URL_TEMPLATE = Addressable::Template.new(
|
||||
Rails.configuration.impress_2020_origin +
|
||||
"/api/assetImage{?libraryUrl,size}"
|
||||
)
|
||||
LEGACY_IMAGE_URL_TEMPLATE = Addressable::Template.new(
|
||||
"https://aws.impress-asset-images.openneo.net/{type}" +
|
||||
"/{id1}/{id2}/{id3}/{id}/{size}x{size}.png?v2-{time}"
|
||||
)
|
||||
|
||||
def as_json(options={})
|
||||
super({
|
||||
|
@ -112,13 +44,14 @@ class SwfAsset < ApplicationRecord
|
|||
{
|
||||
swf: url,
|
||||
png: image_url,
|
||||
svg: manifest_asset_urls[:svg],
|
||||
manifest: manifest_url,
|
||||
}
|
||||
end
|
||||
|
||||
def manifest
|
||||
raise "manifest_url is blank" if manifest_url.blank?
|
||||
NeopetsMediaArchive.load_json(manifest_url)
|
||||
@manifest ||= NeopetsMediaArchive.load_json(manifest_url)
|
||||
end
|
||||
|
||||
def preload_manifest
|
||||
|
@ -131,18 +64,89 @@ class SwfAsset < ApplicationRecord
|
|||
return {} if manifest_url.nil?
|
||||
|
||||
begin
|
||||
# Organize the asset URLs by file extension, grab the ones we want, and
|
||||
# convert them from paths to full URLs.
|
||||
manifest["cpmanifest"]["assets"][0]["asset_data"].
|
||||
to_h { |a| [a["file_ext"].to_sym, a] }.
|
||||
slice(:png, :svg, :js)
|
||||
.transform_values { |a| (MANIFEST_BASE_URL + a["url"]).to_s }
|
||||
# Organize the asset URLs by file extension, convert them from paths to
|
||||
# full URLs, and grab the ones we want.
|
||||
assets_by_ext = manifest["cpmanifest"]["assets"][0]["asset_data"].
|
||||
group_by { |a| a["file_ext"].to_sym }.
|
||||
transform_values do |assets|
|
||||
assets.map { |a| (MANIFEST_BASE_URL + a["url"]).to_s }
|
||||
end
|
||||
|
||||
if assets_by_ext[:js].present?
|
||||
# If a JS asset is present, assume any other assets are supporting
|
||||
# assets, and skip them. (e.g. if there's a PNG, it's likely to be an
|
||||
# "atlas" file used in the animation, rather than a thumbnail.)
|
||||
#
|
||||
# NOTE: We take the last one, because sometimes there are multiple JS
|
||||
# assets in the same manifest, and earlier ones are broken and later
|
||||
# ones are fixed. I don't know the logic exactly, but that's what we've
|
||||
# seen!
|
||||
{ js: assets_by_ext[:js].last }
|
||||
else
|
||||
# Otherwise, return the last PNG and the last SVG, arbitrarily.
|
||||
# (There's probably only one of each! I'm just going by the same logic
|
||||
# we've seen in the JS library case, that later entries are more likely
|
||||
# to be correct.)
|
||||
{ png: assets_by_ext[:png].last, svg: assets_by_ext[:svg].last }
|
||||
end
|
||||
rescue StandardError => error
|
||||
Rails.logger.error "Could not read URLs from manifest: #{error.full_message}"
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
def image_url
|
||||
# Use the PNG image from the manifest, if one exists.
|
||||
return manifest_asset_urls[:png] if manifest_asset_urls[:png].present?
|
||||
|
||||
# Or, if this is a canvas movie, let Impress 2020 generate a PNG for us.
|
||||
return canvas_movie_image_url if manifest_asset_urls[:js].present?
|
||||
|
||||
# Otherwise, if we don't have the manifest or it doesn't have the files we
|
||||
# need, fall back to the Classic DTI image storage, which was generated
|
||||
# from the SWFs via an old version of gnash (or sometimes manually
|
||||
# overridden). It's less accurate, but well-tested to generally work okay,
|
||||
# and it's the only image we have for assets not yet converted to HTML5.
|
||||
#
|
||||
# NOTE: We've stopped generating these images for new assets! This is
|
||||
# mainly for old assets not yet converted to HTML5.
|
||||
#
|
||||
# NOTE: If you're modeling from a fresh development database, `has_image?`
|
||||
# might be false even though we *do* have a saved copy of the image
|
||||
# available in production. But if you're using the public modeling
|
||||
# data exported from production, then this check should be fine!
|
||||
#
|
||||
# TODO: Rename `has_image?` to `has_legacy_image?`.
|
||||
return legacy_image_url if has_image?
|
||||
|
||||
# Otherwise, there's no image URL.
|
||||
nil
|
||||
end
|
||||
|
||||
def canvas_movie_image_url
|
||||
return nil unless manifest_asset_urls[:js]
|
||||
|
||||
CANVAS_MOVIE_IMAGE_URL_TEMPLATE.expand(
|
||||
libraryUrl: manifest_asset_urls[:js],
|
||||
size: 600,
|
||||
).to_s
|
||||
end
|
||||
|
||||
def legacy_image_url
|
||||
return nil unless has_image?
|
||||
|
||||
padded_id = remote_id.to_s.rjust(12, "0")
|
||||
LEGACY_IMAGE_URL_TEMPLATE.expand(
|
||||
type: type,
|
||||
id1: padded_id[0...3],
|
||||
id2: padded_id[3...6],
|
||||
id3: padded_id[6...9],
|
||||
id: remote_id,
|
||||
size: "600",
|
||||
time: converted_at.to_i,
|
||||
).to_s
|
||||
end
|
||||
|
||||
def html5_image_url
|
||||
manifest_asset_urls[:png]
|
||||
end
|
||||
|
@ -221,6 +225,13 @@ class SwfAsset < ApplicationRecord
|
|||
self.manifest_url = parsed_manifest_url.to_s
|
||||
end
|
||||
|
||||
# To manually change the body ID without triggering the usual change to 0,
|
||||
# use this override method. (This is intended for use from the console.)
|
||||
def override_body_id(new_body_id)
|
||||
@body_id_overridden = true
|
||||
self.body_id = new_body_id
|
||||
end
|
||||
|
||||
def self.from_biology_data(body_id, data)
|
||||
remote_id = data[:part_id].to_i
|
||||
swf_asset = SwfAsset.find_or_initialize_by type: 'biology',
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
ASSET_HOSTS = {
|
||||
:swf_asset_images => 'impress-asset-images.openneo.net'
|
||||
}
|
|
@ -33,9 +33,6 @@ OpenneoImpressItems::Application.routes.draw do
|
|||
end
|
||||
resources :alt_styles, path: 'alt-styles', only: [:index]
|
||||
end
|
||||
resources :colors, only: [] do
|
||||
resources :pet_types, only: [:index]
|
||||
end
|
||||
resources :alt_styles, path: 'alt-styles', only: [:index]
|
||||
|
||||
# Loading and modeling pets!
|
||||
|
|
Loading…
Reference in a new issue