2024-03-13 17:46:45 -07:00
|
|
|
|
require "addressable/template"
|
|
|
|
|
|
2010-05-15 10:47:46 -07:00
|
|
|
|
module ItemsHelper
|
2010-06-08 07:39:23 -07:00
|
|
|
|
module PetTypeImage
|
2024-05-22 17:53:52 -07:00
|
|
|
|
Template = Addressable::Template.new(
|
|
|
|
|
"https://pets.neopets.com/cp/{hash}/{emotion}/{size}.png"
|
|
|
|
|
)
|
2011-05-02 15:07:56 -07:00
|
|
|
|
|
2010-06-08 07:39:23 -07:00
|
|
|
|
Emotions = {
|
2024-05-22 17:53:52 -07:00
|
|
|
|
happy: 1,
|
|
|
|
|
sad: 2,
|
|
|
|
|
angry: 3,
|
|
|
|
|
ill: 4,
|
2010-06-08 07:39:23 -07:00
|
|
|
|
}
|
2011-05-02 15:07:56 -07:00
|
|
|
|
|
2010-06-08 07:39:23 -07:00
|
|
|
|
Sizes = {
|
2024-09-26 14:56:45 -07:00
|
|
|
|
face: 1, # 50x50
|
|
|
|
|
face_3x: 6, # 150x150
|
|
|
|
|
|
|
|
|
|
thumb: 2, # 150x150
|
|
|
|
|
full: 4, # 300x300
|
|
|
|
|
large: 5, # 500x500
|
|
|
|
|
xlarge: 7, # 640x640
|
|
|
|
|
|
|
|
|
|
zoom: 3, # 80x80
|
|
|
|
|
autocrop: 9, # <varies>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SizeUpgrades = {
|
|
|
|
|
face: :face_3x,
|
|
|
|
|
thumb: :full,
|
|
|
|
|
full: :xlarge,
|
2010-06-08 07:39:23 -07:00
|
|
|
|
}
|
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
|
2024-05-22 17:53:52 -07:00
|
|
|
|
def pet_type_image_url(pet_type, emotion: :happy, size: :face)
|
|
|
|
|
PetTypeImage::Template.expand(
|
|
|
|
|
hash: pet_type.basic_image_hash || pet_type.image_hash,
|
2024-09-26 14:56:45 -07:00
|
|
|
|
emotion: PetTypeImage::Emotions.fetch(emotion),
|
|
|
|
|
size: PetTypeImage::Sizes.fetch(size),
|
2024-05-22 17:53:52 -07:00
|
|
|
|
).to_s
|
|
|
|
|
end
|
|
|
|
|
|
2010-06-08 07:39:23 -07:00
|
|
|
|
def standard_species_search_links
|
2024-08-31 13:31:24 -07:00
|
|
|
|
all_species = Species.alphabetical.map(&:id)
|
|
|
|
|
PetType.random_basic_per_species(all_species).map do |pet_type|
|
2024-09-03 17:27:43 -07:00
|
|
|
|
human_name = pet_type.species.human_name
|
|
|
|
|
image = pet_type_image pet_type, :happy, :zoom,
|
|
|
|
|
alt: human_name, title: human_name
|
2010-06-08 07:39:23 -07:00
|
|
|
|
query = "species:#{pet_type.species.name}"
|
|
|
|
|
link_to(image, items_path(:q => query))
|
2024-08-31 13:31:24 -07:00
|
|
|
|
end.join.html_safe
|
2010-06-08 07:39:23 -07:00
|
|
|
|
end
|
2011-10-10 19:43:46 -07:00
|
|
|
|
|
|
|
|
|
def closet_list_verb(owned)
|
|
|
|
|
ClosetHanger.verb(:you, owned)
|
|
|
|
|
end
|
2012-08-06 18:15:31 -07:00
|
|
|
|
|
|
|
|
|
def owned_icon
|
2013-01-01 19:15:17 -08:00
|
|
|
|
image_tag 'owned.png', :title => t('items.item.owned.description'),
|
|
|
|
|
:alt => t('items.item.owned.abbr')
|
2012-08-06 18:15:31 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def wanted_icon
|
2013-01-01 19:15:17 -08:00
|
|
|
|
image_tag 'wanted.png', :title => t('items.item.wanted.description'),
|
|
|
|
|
:alt => t('items.item.wanted.abbr')
|
2012-08-06 18:15:31 -07:00
|
|
|
|
end
|
2011-07-12 22:21:48 -07:00
|
|
|
|
|
2011-07-22 13:18:15 -07:00
|
|
|
|
def closeted_icons_for(item)
|
|
|
|
|
content = ''.html_safe
|
|
|
|
|
|
2012-08-06 18:15:31 -07:00
|
|
|
|
content << owned_icon if item.owned?
|
|
|
|
|
content << wanted_icon if item.wanted?
|
2011-07-22 13:18:15 -07:00
|
|
|
|
|
|
|
|
|
content_tag :div, content, :class => 'closeted-icons'
|
2011-07-12 22:21:48 -07:00
|
|
|
|
end
|
2012-08-06 18:15:31 -07:00
|
|
|
|
|
2024-05-20 15:23:34 -07:00
|
|
|
|
# NOTE: Changing this requires bumping the cache at `_closet_list.html.haml`!
|
2012-08-06 18:15:31 -07:00
|
|
|
|
def nc_icon
|
2013-01-01 19:10:49 -08:00
|
|
|
|
image_tag 'nc.png', :title => t('items.item.nc.description'),
|
|
|
|
|
:alt => t('items.item.nc.abbr'), :class => 'nc-icon'
|
2012-08-06 18:15:31 -07:00
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
|
2024-05-20 15:23:34 -07:00
|
|
|
|
# NOTE: Changing this requires bumping the cache at `_closet_list.html.haml`!
|
2010-09-08 19:49:39 -07:00
|
|
|
|
def nc_icon_for(item)
|
2012-08-06 18:15:31 -07:00
|
|
|
|
nc_icon if item.nc?
|
2010-09-08 19:49:39 -07:00
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
|
2024-05-20 15:23:34 -07:00
|
|
|
|
# NOTE: Changing this requires bumping the cache at `_closet_list.html.haml`!
|
|
|
|
|
def item_thumbnail_for(item)
|
|
|
|
|
image_tag item.thumbnail_url, alt: "Thumbnail for #{item.name}",
|
|
|
|
|
title: item.description, loading: "lazy"
|
|
|
|
|
end
|
|
|
|
|
|
2024-02-20 14:32:45 -08:00
|
|
|
|
def time_with_only_month_if_old(first_seen_at)
|
|
|
|
|
# For this month and the previous month, show the full date, so people can
|
|
|
|
|
# understand *exactly* how recent it was.
|
|
|
|
|
beginning_of_prev_month = Date.today.beginning_of_month - 1.month
|
|
|
|
|
if first_seen_at >= beginning_of_prev_month
|
|
|
|
|
return first_seen_at.strftime("%b %e, %Y")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Otherwise, show just the month and the year, to be concise. (We'll offer
|
|
|
|
|
# the full date as a tooltip, too.)
|
|
|
|
|
first_seen_at.strftime("%b %Y")
|
|
|
|
|
end
|
|
|
|
|
|
2024-05-21 17:54:12 -07:00
|
|
|
|
JN_ITEMS_URL_TEMPLATE = Addressable::Template.new(
|
|
|
|
|
"https://items.jellyneo.net/search/?name_type=3{&name}"
|
|
|
|
|
)
|
2012-10-21 13:57:17 -07:00
|
|
|
|
def jn_items_url_for(item)
|
2024-05-21 17:54:12 -07:00
|
|
|
|
JN_ITEMS_URL_TEMPLATE.expand(name: item.name).to_s
|
2010-09-08 19:49:39 -07:00
|
|
|
|
end
|
2024-03-13 17:46:45 -07:00
|
|
|
|
|
|
|
|
|
IMPRESS_2020_ITEM_URL_TEMPLATE = Addressable::Template.new(
|
|
|
|
|
"#{Rails.configuration.impress_2020_origin}/items/{id}"
|
|
|
|
|
)
|
|
|
|
|
def impress_2020_url_for(item)
|
|
|
|
|
IMPRESS_2020_ITEM_URL_TEMPLATE.expand(id: item.id).to_s
|
|
|
|
|
end
|
2012-05-23 17:09:35 -07:00
|
|
|
|
|
2024-05-21 17:54:12 -07:00
|
|
|
|
SHOP_WIZARD_URL_TEMPLATE = Addressable::Template.new(
|
2024-05-21 18:00:01 -07:00
|
|
|
|
"https://www.neopets.com/shops/wizard.phtml{?string}"
|
2024-05-21 17:54:12 -07:00
|
|
|
|
)
|
2024-05-22 15:41:46 -07:00
|
|
|
|
def shop_wizard_url_for(item_or_name)
|
|
|
|
|
item_or_name = item_or_name.name if item_or_name.is_a? Item
|
|
|
|
|
SHOP_WIZARD_URL_TEMPLATE.expand(string: item_or_name).to_s
|
2012-05-23 17:09:35 -07:00
|
|
|
|
end
|
2024-08-29 22:46:59 -07:00
|
|
|
|
|
2024-05-21 17:54:12 -07:00
|
|
|
|
TRADING_POST_URL_TEMPLATE = Addressable::Template.new(
|
|
|
|
|
"https://www.neopets.com/island/tradingpost.phtml?type=browse&criteria=item_exact{&search_string}"
|
|
|
|
|
)
|
2024-05-22 15:41:46 -07:00
|
|
|
|
def trading_post_url_for(item_or_name)
|
|
|
|
|
item_or_name = item_or_name.name if item_or_name.is_a? Item
|
|
|
|
|
TRADING_POST_URL_TEMPLATE.expand(search_string: item_or_name).to_s
|
2012-05-23 17:09:35 -07:00
|
|
|
|
end
|
|
|
|
|
|
2024-05-21 17:54:12 -07:00
|
|
|
|
AUCTION_GENIE_URL_TEMPLATE = Addressable::Template.new(
|
|
|
|
|
"https://www.neopets.com/genie.phtml?type=process_genie&criteria=exact{&auctiongenie}"
|
|
|
|
|
)
|
2012-05-23 17:09:35 -07:00
|
|
|
|
def auction_genie_url_for(item)
|
2024-05-21 17:54:12 -07:00
|
|
|
|
AUCTION_GENIE_URL_TEMPLATE.expand(auctiongenie: item.name).to_s
|
2012-05-23 17:09:35 -07:00
|
|
|
|
end
|
2012-10-24 20:09:05 -07:00
|
|
|
|
|
|
|
|
|
def format_contribution_count(count)
|
2012-10-24 20:16:01 -07:00
|
|
|
|
" (×#{count})".html_safe if count > 1
|
2012-10-24 20:09:05 -07:00
|
|
|
|
end
|
2011-07-30 21:19:28 -07:00
|
|
|
|
|
2013-06-22 16:31:46 -07:00
|
|
|
|
def render_item_link(item)
|
2023-08-02 11:34:27 -07:00
|
|
|
|
render(partial: 'items/item_link', locals: {item: item})
|
2013-06-22 16:31:46 -07:00
|
|
|
|
end
|
|
|
|
|
|
2023-11-03 16:20:02 -07:00
|
|
|
|
def nc_trade_value_updated_at_text(nc_trade_value)
|
|
|
|
|
return nil if nc_trade_value.updated_at.nil?
|
|
|
|
|
|
|
|
|
|
# Render both "[X] [days] ago", and also the exact date, only including the
|
|
|
|
|
# year if it's not this same year.
|
|
|
|
|
time_ago_str = time_ago_in_words nc_trade_value.updated_at
|
|
|
|
|
date_str = nc_trade_value.updated_at.year != Date.today.year ?
|
|
|
|
|
nc_trade_value.updated_at.strftime("%b %-d") :
|
|
|
|
|
nc_trade_value.updated_at.strftime("%b %-d, %Y")
|
|
|
|
|
|
|
|
|
|
"Last updated: #{date_str} (#{time_ago_str} ago)"
|
|
|
|
|
end
|
|
|
|
|
|
2024-06-09 19:22:34 -07:00
|
|
|
|
NC_TRADE_VALUE_ESTIMATE_PATTERN = %r{
|
2024-06-28 01:32:15 -07:00
|
|
|
|
\A\s*
|
2024-06-09 19:22:34 -07:00
|
|
|
|
(?:
|
|
|
|
|
# Case 1: A single number
|
|
|
|
|
(?<single>[0-9]+)
|
|
|
|
|
|
|
|
|
|
|
# Case 2: A range from low to high
|
|
|
|
|
(?<low>[0-9]+)
|
|
|
|
|
\p{Dash_Punctuation}
|
|
|
|
|
(?<high>[0-9]+)
|
|
|
|
|
)
|
2024-06-28 01:32:15 -07:00
|
|
|
|
\s*\z
|
2024-06-09 19:22:34 -07:00
|
|
|
|
}x
|
|
|
|
|
def nc_trade_value_is_estimate(nc_trade_value)
|
|
|
|
|
nc_trade_value.value_text.match?(NC_TRADE_VALUE_ESTIMATE_PATTERN)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Try to parse the NC trade value's text into something styled a bit more
|
|
|
|
|
# nicely for our use case.
|
|
|
|
|
def nc_trade_value_estimate_text(nc_trade_value)
|
|
|
|
|
match = nc_trade_value.value_text.match(NC_TRADE_VALUE_ESTIMATE_PATTERN)
|
|
|
|
|
return nc_trade_value if match.nil?
|
|
|
|
|
|
|
|
|
|
match => {single:, low:, high:}
|
|
|
|
|
if single.present?
|
|
|
|
|
pluralize single.to_i, "capsule"
|
|
|
|
|
elsif low.present? && high.present?
|
|
|
|
|
"#{low}–#{high} capsules"
|
|
|
|
|
else
|
|
|
|
|
nc_trade_value
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-06-09 13:25:59 -07:00
|
|
|
|
def nc_total_for(items)
|
|
|
|
|
items.map(&:current_nc_price).sum
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def dyeworks_nc_total_for(items)
|
|
|
|
|
nc_total_for items.map(&:dyeworks_base_item)
|
|
|
|
|
end
|
|
|
|
|
|
2024-06-20 13:14:51 -07:00
|
|
|
|
def dyeworks_average_num_potions_for(items)
|
|
|
|
|
# Compute the number of expected potions for each (inverse of the odds),
|
|
|
|
|
# sum them, then round up.
|
|
|
|
|
items.map { |i| 1 / i.dyeworks_odds }.sum.ceil
|
2024-06-09 13:25:59 -07:00
|
|
|
|
end
|
|
|
|
|
|
2024-06-20 13:14:51 -07:00
|
|
|
|
def dyeworks_estimated_potions_cost_for(items)
|
|
|
|
|
# NOTE: You could do bundles too, but let's just keep it simple.
|
|
|
|
|
dyeworks_average_num_potions_for(items) * 125
|
2024-06-09 13:25:59 -07:00
|
|
|
|
end
|
|
|
|
|
|
2024-06-20 14:16:51 -07:00
|
|
|
|
def complexity_for(items)
|
|
|
|
|
max_name_length = items.map(&:name).map(&:length).max
|
|
|
|
|
max_name_length >= 40 ? "high" : "low"
|
|
|
|
|
end
|
|
|
|
|
|
2024-06-20 12:54:39 -07:00
|
|
|
|
def probability(p)
|
|
|
|
|
case p
|
|
|
|
|
when 1
|
|
|
|
|
"100%"
|
|
|
|
|
when 0
|
|
|
|
|
"0%"
|
|
|
|
|
else
|
|
|
|
|
"#{p.numerator} in #{p.denominator}"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-07-08 16:27:38 -07:00
|
|
|
|
def outfit_viewer_is_playing
|
|
|
|
|
cookies["DTIOutfitViewerIsPlaying"] == "true"
|
|
|
|
|
end
|
|
|
|
|
|
2024-09-03 17:27:43 -07:00
|
|
|
|
def item_fits?(item, pet_type)
|
|
|
|
|
item.appearances.any? { |a| a.fits? pet_type }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def species_face_tooltip(pet_type, item)
|
|
|
|
|
if item_fits?(item, pet_type)
|
|
|
|
|
"#{pet_type.species.human_name}"
|
|
|
|
|
else
|
|
|
|
|
"#{pet_type.species.human_name}: No data yet"
|
|
|
|
|
end
|
2024-09-03 16:42:04 -07:00
|
|
|
|
end
|
|
|
|
|
|
2024-09-03 12:55:10 -07:00
|
|
|
|
def item_zone_partial_fit?(appearances_in_zone, all_appearances)
|
|
|
|
|
appearances_in_zone.size < all_appearances.size
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def item_zone_species_list(appearances_in_zone)
|
|
|
|
|
appearances_in_zone.map(&:species).uniq.map(&:human_name).sort.join(", ")
|
|
|
|
|
end
|
|
|
|
|
|
2024-09-03 17:27:43 -07:00
|
|
|
|
def pet_type_image(pet_type, emotion, size, **options)
|
2024-05-22 17:53:52 -07:00
|
|
|
|
src = pet_type_image_url(pet_type, emotion:, size:)
|
2024-09-26 14:56:45 -07:00
|
|
|
|
|
|
|
|
|
size_2x = PetTypeImage::SizeUpgrades[size]
|
|
|
|
|
srcset = if size_2x
|
|
|
|
|
[[pet_type_image_url(pet_type, emotion:, size: size_2x), "2x"]]
|
2024-09-03 17:34:31 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
image_tag(src, srcset:, **options)
|
2010-05-16 12:01:38 -07:00
|
|
|
|
end
|
2024-01-23 04:30:23 -08:00
|
|
|
|
|
|
|
|
|
def item_header_user_lists_form_state
|
|
|
|
|
cookies.fetch("DTIItemPageUserListsFormState", "closed")
|
|
|
|
|
end
|
2010-05-15 10:47:46 -07:00
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
|