Compare commits

..

No commits in common. "cd095eefcbd367920bac6c6ff2fd4d9fefae1b35" and "992954ce89983fa96e35aff1da872d3c8406ea28" have entirely different histories.

7 changed files with 138 additions and 101 deletions

View file

@ -1,4 +1,37 @@
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]).

View file

@ -38,7 +38,7 @@ class AltStyle < ApplicationRecord
swf_asset = swf_assets.first
return nil if swf_asset.nil?
swf_asset.image_url
swf_asset.html5_image_url
end
def biology=(biology)

View file

@ -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 = Appearance::Body.new(0, nil)
body = Body.new(0, nil)
return [Appearance.new(body, swf_assets_for_all_bodies)]
end

View file

@ -30,6 +30,15 @@ class PetType < ApplicationRecord
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 = []
# TODO: omg so lame :P

View file

@ -1,4 +1,3 @@
require 'addressable/template'
require 'async'
require 'async/barrier'
require 'async/semaphore'
@ -9,29 +8,98 @@ class SwfAsset < ApplicationRecord
# We use the `type` column to mean something other than what Rails means!
self.inheritance_column = nil
# Used in `item_is_body_specific?`. (TODO: Could we refactor this out?)
attr_accessor :item
IMAGE_SIZES = {
:small => [150, 150],
:medium => [300, 300],
:large => [600, 600]
}
belongs_to :zone
has_many :parent_swf_asset_relationships
has_one :contribution, :as => :contributed, :inverse_of => :contributed
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
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')
}
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}"
)
# 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
def as_json(options={})
super({
@ -44,14 +112,13 @@ 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?
@manifest ||= NeopetsMediaArchive.load_json(manifest_url)
NeopetsMediaArchive.load_json(manifest_url)
end
def preload_manifest
@ -64,89 +131,18 @@ class SwfAsset < ApplicationRecord
return {} if manifest_url.nil?
begin
# 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
# 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 }
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
@ -225,13 +221,6 @@ 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',

View file

@ -0,0 +1,3 @@
ASSET_HOSTS = {
:swf_asset_images => 'impress-asset-images.openneo.net'
}

View file

@ -33,6 +33,9 @@ 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!