From 648649f5cc6c3d45df047ce170c808203c685494 Mon Sep 17 00:00:00 2001 From: Matchu Date: Mon, 2 May 2011 18:07:56 -0400 Subject: [PATCH] support special colors in the infinite closet --- app/controllers/swf_assets_controller.rb | 10 +- app/helpers/items_helper.rb | 35 +++-- app/models/item.rb | 169 +++++++++++++---------- app/models/pet_type.rb | 45 +++--- app/models/swf_asset.rb | 44 +++--- app/views/items/show.html.haml | 3 +- config/basic_type_hashes.yml | 142 +++++++++++++++++++ config/colors_with_unique_bodies.txt | 5 + 8 files changed, 322 insertions(+), 131 deletions(-) create mode 100644 config/colors_with_unique_bodies.txt diff --git a/app/controllers/swf_assets_controller.rb b/app/controllers/swf_assets_controller.rb index e00587d8..60fa7195 100644 --- a/app/controllers/swf_assets_controller.rb +++ b/app/controllers/swf_assets_controller.rb @@ -1,11 +1,16 @@ class SwfAssetsController < ApplicationController def index if params[:item_id] - @swf_assets = Item.find(params[:item_id]).swf_assets + item = Item.find(params[:item_id]) + @swf_assets = item.swf_assets if params[:body_id] @swf_assets = @swf_assets.fitting_body_id(params[:body_id]) else - @swf_assets = @swf_assets.fitting_standard_body_ids + if item.special_color + @swf_assets = @swf_assets.fitting_color(item.special_color) + else + @swf_assets = @swf_assets.fitting_standard_body_ids + end json = @swf_assets.all.group_by(&:body_id) end elsif params[:body_id] && params[:item_ids] @@ -26,3 +31,4 @@ class SwfAssetsController < ApplicationController render :json => json end end + diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb index 73d45455..cf586268 100644 --- a/app/helpers/items_helper.rb +++ b/app/helpers/items_helper.rb @@ -2,14 +2,14 @@ module ItemsHelper NeoitemsURLFormat = 'http://neoitems.net/search2.php?Name=%s&AndOr=and&Category=All&Special=0&Status=Active&Sort=ItemID&results=15&SearchType=8' module PetTypeImage Format = 'http://pets.neopets.com/cp/%s/%i/%i.png' - + Emotions = { :happy => 1, :sad => 2, :angry => 3, :ill => 4 } - + Sizes = { :face => 1, :thumb => 2, @@ -17,17 +17,17 @@ module ItemsHelper :full => 4 } end - + def standard_species_search_links - build_on_random_standard_pet_types(Species.all) do |pet_type| + build_on_pet_types(Species.all) do |pet_type| image = pet_type_image(pet_type, :happy, :zoom) query = "species:#{pet_type.species.name}" link_to(image, items_path(:q => query)) end end - - def standard_species_images(species) - build_on_random_standard_pet_types(species) do |pet_type| + + def standard_species_images_for(item) + build_on_pet_types(item.supported_species, item.special_color) do |pet_type| image = pet_type_image(pet_type, :happy, :face) attributes = { 'data-id' => pet_type.id, @@ -47,25 +47,29 @@ module ItemsHelper ) end end - + def list_zones(zones, method=:label) zones.sort { |x,y| x.label <=> y.label }.map(&method).join(', ') end - + def nc_icon_for(item) image_tag 'nc.png', :title => 'NC Mall Item', :alt => 'NC', :class => 'nc-icon' if item.nc? end - + def neoitems_url_for(item) sprintf(NeoitemsURLFormat, CGI::escape(item.name)) end - + private - - def build_on_random_standard_pet_types(species, &block) - raw(PetType.random_basic_per_species(species.map(&:id)).map(&block).join) + + def build_on_pet_types(species, special_color=nil, &block) + species_ids = species.map(&:id) + pet_types = special_color ? + PetType.where(:color_id => special_color.id, :species_id => species_ids).order(:species_id) : + PetType.random_basic_per_species(species.map(&:id)) + pet_types.map(&block).join.html_safe end - + def pet_type_image(pet_type, emotion, size) emotion_id = PetTypeImage::Emotions[emotion] size_id = PetTypeImage::Sizes[size] @@ -74,3 +78,4 @@ module ItemsHelper image_tag(src, :alt => human_name, :title => human_name) end end + diff --git a/app/models/item.rb b/app/models/item.rb index 85c0cbb3..b5e65e94 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -2,46 +2,50 @@ class Item < ActiveRecord::Base SwfAssetType = 'object' - + has_one :contribution, :as => :contributed has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id', :conditions => {:swf_asset_type => SwfAssetType} has_many :swf_assets, :through => :parent_swf_asset_relationships, :source => :object_asset, :conditions => {:type => SwfAssetType} - + attr_writer :current_body_id - + NCRarities = [0, 500] - PaintbrushSetDescription = 'This item is part of a deluxe paint brush set!' - + PAINTBRUSH_SET_DESCRIPTION = 'This item is part of a deluxe paint brush set!' + SPECIAL_COLOR_DESCRIPTION_REGEX = /This item is only wearable by Neopets painted ([a-zA-Z]+)\./ + + SPECIAL_PAINTBRUSH_COLORS_PATH = Rails.root.join('config', 'colors_with_unique_bodies.txt') + SPECIAL_PAINTBRUSH_COLORS = File.read(SPECIAL_PAINTBRUSH_COLORS_PATH).split("\n").map { |name| Color.find_by_name(name) } + set_table_name 'objects' # Neo & PHP Impress call them objects, but the class name is a conflict (duh!) set_inheritance_column 'inheritance_type' # PHP Impress used "type" to describe category - + cattr_reader :per_page @@per_page = 30 - + scope :alphabetize, order('name ASC') - + scope :join_swf_assets, joins("INNER JOIN #{ParentSwfAssetRelationship.table_name} psa ON psa.swf_asset_type = 'object' AND psa.parent_id = objects.id"). joins("INNER JOIN #{SwfAsset.table_name} swf_assets ON swf_assets.id = psa.swf_asset_id"). group('objects.id') - + scope :without_swf_assets, joins( "LEFT JOIN #{ParentSwfAssetRelationship.table_name} psa ON psa.swf_asset_type = 'object' AND psa.parent_id = #{table_name}.id " + "LEFT JOIN #{SwfAsset.table_name} sa ON sa.type = 'object' AND sa.id = psa.swf_asset_id" ).where('sa.id IS NULL') - + scope :spidered_longest_ago, order(["(#{Item.arel_table[:last_spidered].eq(nil).to_sql}) DESC", arel_table[:last_spidered].desc]) - + scope :sold_in_mall, where(:sold_in_mall => true) scope :not_sold_in_mall, where(:sold_in_mall => false) - + # Not defining validations, since this app is currently read-only - + def nc? NCRarities.include?(rarity_index) end - + def restricted_zones unless @restricted_zones @restricted_zones = [] @@ -51,7 +55,7 @@ class Item < ActiveRecord::Base end @restricted_zones end - + def occupied_zones all_body_ids = [] zone_body_ids = {} @@ -70,25 +74,45 @@ class Item < ActiveRecord::Base end zones end - + def affected_zones restricted_zones + occupied_zones end - + + def special_color + @special_color ||= determine_special_color + end + + protected + def determine_special_color + if description.include?(PAINTBRUSH_SET_DESCRIPTION) + downcased_name = name.downcase + SPECIAL_PAINTBRUSH_COLORS.each do |color| + return color if downcased_name.include?(color.name) + end + end + + match = description.match(SPECIAL_COLOR_DESCRIPTION_REGEX) + if match + return Color.find_by_name(match[1].downcase) + end + end + public + def species_support_ids @species_support_ids_array ||= read_attribute('species_support_ids').split(',').map(&:to_i) rescue nil end - + def species_support_ids=(replacement) @species_support_ids_array = nil replacement = replacement.join(',') if replacement.is_a?(Array) write_attribute('species_support_ids', replacement) end - + def supported_species @supported_species ||= species_support_ids.blank? ? Species.all : species_support_ids.sort.map { |id| Species.find(id) } end - + def self.search(query) raise SearchError, "Please provide a search query" unless query query = query.strip @@ -120,7 +144,7 @@ class Item < ActiveRecord::Base condition.narrow(scope) end end - + def as_json(options = {}) { :description => description, @@ -131,12 +155,12 @@ class Item < ActiveRecord::Base :rarity_index => rarity_index } end - + before_create do self.sold_in_mall ||= false true end - + def handle_assets! if @parent_swf_asset_relationships_to_update && @current_body_id new_swf_asset_ids = @parent_swf_asset_relationships_to_update.map(&:swf_asset_id) @@ -158,7 +182,7 @@ class Item < ActiveRecord::Base self.parent_swf_asset_relationships += @parent_swf_asset_relationships_to_update end end - + def origin_registry_info=(info) # bear in mind that numbers from registries are floats self.species_support_ids = info[:species_support].map(&:to_i) @@ -170,17 +194,17 @@ class Item < ActiveRecord::Base end end end - + def pending_swf_assets @parent_swf_asset_relationships_to_update.inject([]) do |all_swf_assets, relationship| all_swf_assets << relationship.swf_asset end end - + def parent_swf_asset_relationships_to_update=(rels) @parent_swf_asset_relationships_to_update = rels end - + def self.all_by_ids_or_children(ids, swf_assets) swf_asset_ids = [] swf_assets_by_id = {} @@ -208,7 +232,7 @@ class Item < ActiveRecord::Base end end end - + def self.collection_from_pet_type_and_registries(pet_type, info_registry, asset_registry) # bear in mind that registries are arrays with many nil elements, # due to how the parser works @@ -277,7 +301,7 @@ class Item < ActiveRecord::Base end items.values end - + class << self MALL_HOST = 'ncmall.neopets.com' MALL_MAIN_PATH = '/mall/shop.phtml' @@ -286,14 +310,14 @@ class Item < ActiveRecord::Base MALL_CATEGORY_TRIGGER = /load_items_pane\("browse", ([0-9]+)\);/ MALL_JSON_ITEM_DATA_KEY = 'object_data' MALL_ITEM_URL_TEMPLATE = 'http://images.neopets.com/items/%s.gif' - + MALL_MAIN_URI = Addressable::URI.new :scheme => 'http', :host => MALL_HOST, :path => MALL_MAIN_PATH MALL_CATEGORY_URI = Addressable::URI.new :scheme => 'http', :host => MALL_HOST, :path => MALL_CATEGORY_PATH, :query => MALL_CATEGORY_QUERY MALL_CATEGORY_TEMPLATE = Addressable::Template.new MALL_CATEGORY_URI - + def spider_mall! # Load the mall HTML, scan it for category onclicks items = {} @@ -340,7 +364,7 @@ class Item < ActiveRecord::Base end items end - + def spider_mall_assets!(limit) items = self.select([arel_table[:id], arel_table[:name]]).sold_in_mall.spidered_longest_ago.limit(limit).all puts "- #{items.size} items need asset spidering" @@ -349,7 +373,7 @@ class Item < ActiveRecord::Base AssetStrategy.spider item end end - + def spider_request(uri) begin response = Net::HTTP.get_response uri @@ -361,26 +385,26 @@ class Item < ActiveRecord::Base end response.body end - + private - + class AssetStrategy Strategies = {} - + MALL_ASSET_PATH = '/mall/ajax/get_item_assets.phtml' MALL_ASSET_QUERY = 'pet={pet_name}&oii={item_id}' MALL_ASSET_URI = Addressable::URI.new :scheme => 'http', :host => MALL_HOST, :path => MALL_ASSET_PATH, :query => MALL_ASSET_QUERY MALL_ASSET_TEMPLATE = Addressable::Template.new MALL_ASSET_URI - + def initialize(name, options) @name = name @pass = options[:pass] @complete = options[:complete] @pet_types = options[:pet_types] end - + def spider(item) puts " - Using #{@name} strategy" exit = false @@ -412,9 +436,9 @@ class Item < ActiveRecord::Base Strategies[@complete].spider(item) end end - + private - + def load_for_pet_type(item, pet_type, banned_pet_ids=[]) pet_id = pet_type.pet_id pet_name = pet_type.pet_name @@ -442,7 +466,7 @@ class Item < ActiveRecord::Base end end end - + def load_for_pet_name(item, pet_type, pet_name) uri = MALL_ASSET_TEMPLATE. expand( @@ -468,12 +492,12 @@ class Item < ActiveRecord::Base nil end end - + class << self def add_strategy(name, options) Strategies[name] = new(name, options) end - + def add_cascading_strategy(name, options) pet_type_groups = options[:pet_types] pet_type_group_names = pet_type_groups.keys @@ -494,7 +518,7 @@ class Item < ActiveRecord::Base name = next_name end end - + def spider(item) puts "- Spidering for #{item.name}" Strategies[:start].spider(item) @@ -502,7 +526,7 @@ class Item < ActiveRecord::Base item.save puts "- #{item.name} done spidering, saved last spidered timestamp" end - + def build_strategies if Strategies.empty? pet_type_t = PetType.arel_table @@ -512,20 +536,20 @@ class Item < ActiveRecord::Base joins(:pets).group(pet_type_t[:id]) remaining_standard_pet_types = pet_types.single_standard_color.order(:species_id) first_standard_pet_type = [remaining_standard_pet_types.slice!(0)] - + add_strategy :start, :pass => :remaining_standard, :complete => :first_nonstandard_color, :pet_types => first_standard_pet_type - + add_strategy :remaining_standard, :complete => :exit, :pet_types => remaining_standard_pet_types - + add_cascading_strategy :first_nonstandard_color, :complete => :remaining_standard, :pet_types => pet_types.select(pet_type_t[:color_id]).nonstandard_colors.all.group_by(&:color_id) end end end end - + def spider_mall_category(json) begin items_data = JSON.parse(json)[MALL_JSON_ITEM_DATA_KEY] @@ -549,17 +573,17 @@ class Item < ActiveRecord::Base end items end - + class SpiderError < RuntimeError;end class SpiderHTTPError < SpiderError;end class SpiderJSONError < SpiderError;end end - + private - + SearchFilterScopes = [] LimitedSearchFilters = [] - + def self.search_filter(name, options={}, &block) assume_complement = options.delete(:assume_complement) || true name = name.to_s @@ -576,12 +600,12 @@ class Item < ActiveRecord::Base end end end - + def self.single_search_filter(name, options={}, &block) options[:assume_complement] = false search_filter name, options, &block end - + def self.search_filter_block(options, positive) Proc.new { |str, scope| condition = yield(str) @@ -590,18 +614,18 @@ class Item < ActiveRecord::Base scope.where(condition) } end - + search_filter :name do |name| arel_table[:name].matches("%#{name}%") end - + search_filter :description do |description| arel_table[:description].matches("%#{description}%") end - + ADJECTIVE_FILTERS = { 'nc' => arel_table[:rarity_index].in(NCRarities), - 'pb' => arel_table[:description].eq(PaintbrushSetDescription) + 'pb' => arel_table[:description].eq(PAINTBRUSH_SET_DESCRIPTION) } search_filter :is do |adjective| filter = ADJECTIVE_FILTERS[adjective] @@ -612,7 +636,7 @@ class Item < ActiveRecord::Base end filter end - + search_filter :only do |species_name| begin id = Species.require_by_name(species_name).id @@ -621,7 +645,7 @@ class Item < ActiveRecord::Base end arel_table[:species_support_ids].eq(id.to_s) end - + search_filter :species do |species_name| begin id = Species.require_by_name(species_name).id @@ -636,13 +660,13 @@ class Item < ActiveRecord::Base "%,#{id}" ])) end - + single_search_filter :type, {:limit => true, :scope => :join_swf_assets} do |zone_set_name| zone_set = Zone::ItemZoneSets[zone_set_name] raise SearchError, "Type \"#{zone_set_name}\" does not exist" unless zone_set SwfAsset.arel_table[:zone_id].in(zone_set.map(&:id)) end - + single_search_filter :not_type, :full => lambda { |zone_set_name, scope| zone_set = Zone::ItemZoneSets[zone_set_name] raise SearchError, "Type \"#{zone_set_name}\" does not exist" unless zone_set @@ -670,27 +694,27 @@ class Item < ActiveRecord::Base map(&:id) scope.where(arel_table[:id].in(item_ids)) } - + class Condition < String attr_accessor :filter - + def initialize @positive = true end - + def filter? !@filter.nil? end - + def to_filter! @filter = self.clone self.replace '' end - + def negate! @positive = !@positive end - + def narrow(scope) if SearchFilterScopes.include?(filter) polarized_filter = @positive ? filter : "not_#{filter}" @@ -699,17 +723,18 @@ class Item < ActiveRecord::Base raise SearchError, "Filter #{filter} does not exist" end end - + def filter @filter || 'name' end - + def inspect @filter ? "#{@filter}:#{super}" : super end end - + class SearchError < ArgumentError;end end require 'item_sweeper' + diff --git a/app/models/pet_type.rb b/app/models/pet_type.rb index 0767fddc..f2087e8b 100644 --- a/app/models/pet_type.rb +++ b/app/models/pet_type.rb @@ -2,27 +2,27 @@ class PetType < ActiveRecord::Base IMAGE_CPN_FORMAT = 'http://pets.neopets.com/cpn/%s/1/1.png'; IMAGE_CP_LOCATION_REGEX = %r{^/cp/(.+?)/1/1\.png$}; IMAGE_CPN_ACCEPTABLE_NAME = /^[a-z0-9_]+$/ - + has_one :contribution, :as => :contributed has_many :pet_states has_many :pets - + attr_writer :origin_pet - + BasicHashes = YAML::load_file(Rails.root.join('config', 'basic_type_hashes.yml')) - + StandardPetTypesBySpeciesId = PetType.where(arel_table[:color_id].in(Color::BasicIds)).group_by(&:species_id) StandardBodyIds = [] StandardPetTypesBySpeciesId.each do |species_id, pet_types| StandardBodyIds += pet_types.map(&:body_id) end - + # Returns all pet types of a single standard color. The caller shouldn't care # which, though, in this implemention, it's always Blue. Don't depend on that. scope :single_standard_color, where(:color_id => Color::BasicIds[0]) - + scope :nonstandard_colors, where(:color_id => Color.nonstandard_ids) - + def self.random_basic_per_species(species_ids) random_pet_types = [] species_ids.each do |species_id| @@ -31,7 +31,7 @@ class PetType < ActiveRecord::Base end random_pet_types end - + def as_json(options={}) if options[:for] == 'wardrobe' {:id => id, :body_id => body_id, :pet_state_ids => pet_states.select([:id]).emotion_order.map(&:id)} @@ -39,47 +39,47 @@ class PetType < ActiveRecord::Base {:image_hash => image_hash} end end - + def color_id=(new_color_id) @color = nil write_attribute('color_id', new_color_id) end - + def color=(new_color) @color = new_color write_attribute('color_id', @color.id) end - + def color @color ||= Color.find(color_id) end - + def species_id=(new_species_id) @species = nil write_attribute('species_id', new_species_id) end - + def species=(new_species) @species = new_species write_attribute('species_id', @species.id) end - + def species @species ||= Species.find(species_id) end - + def image_hash self['image_hash'] || basic_image_hash end - + def basic_image_hash BasicHashes[species.name][color.name] end - + def human_name self.color.human_name + ' ' + self.species.human_name end - + def needed_items items = Item.arel_table species_matchers = [ @@ -101,12 +101,12 @@ class PetType < ActiveRecord::Base Item.where(items[:id].not_in(unneeded_item_ids)). where(species_condition) end - + def add_pet_state_from_biology!(biology) pet_state = PetState.from_pet_type_and_biology_info(self, biology) pet_state end - + before_save do if @origin_pet && @origin_pet.name =~ IMAGE_CPN_ACCEPTABLE_NAME cpn_uri = URI.parse sprintf(IMAGE_CPN_FORMAT, CGI.escape(@origin_pet.name)); @@ -134,7 +134,7 @@ class PetType < ActiveRecord::Base end end end - + def self.all_by_ids_or_children(ids, pet_states) pet_states_by_pet_type_id = {} pet_states.each do |pet_state| @@ -154,6 +154,7 @@ class PetType < ActiveRecord::Base end end end - + class DownloadError < Exception;end end + diff --git a/app/models/swf_asset.rb b/app/models/swf_asset.rb index b9b68ab5..4455e8df 100644 --- a/app/models/swf_asset.rb +++ b/app/models/swf_asset.rb @@ -3,35 +3,40 @@ class SwfAsset < ActiveRecord::Base LOCAL_ASSET_DIR = Rails.root.join('public', PUBLIC_ASSET_DIR) NEOPETS_ASSET_SERVER = 'http://images.neopets.com' set_inheritance_column 'inheritance_type' - + attr_accessor :item - + has_one :contribution, :as => :contributed has_many :object_asset_relationships, :class_name => 'ParentSwfAssetRelationship', :conditions => {:swf_asset_type => 'object'} - + delegate :depth, :to => :zone - + scope :fitting_body_id, lambda { |body_id| where(arel_table[:body_id].in([body_id, 0])) } - + BodyIdsFittingStandard = PetType::StandardBodyIds + [0] scope :fitting_standard_body_ids, lambda { where(arel_table[:body_id].in(BodyIdsFittingStandard)) } - + + scope :fitting_color, lambda { |color| + body_ids = PetType.select(:body_id).where(:color_id => color.id).map(&:body_id) + where(arel_table[:body_id].in(body_ids)) + } + scope :biology_assets, where(arel_table[:type].eq(PetState::SwfAssetType)) scope :object_assets, where(arel_table[:type].eq(Item::SwfAssetType)) scope :for_item_ids, lambda { |item_ids| joins(:object_asset_relationships). where(ParentSwfAssetRelationship.arel_table[:parent_id].in(item_ids)) } - + def local_url '/' + File.join(PUBLIC_ASSET_DIR, local_path_within_outfit_swfs) end - + def as_json(options={}) json = { :id => id, @@ -49,37 +54,37 @@ class SwfAsset < ActiveRecord::Base json[:parent_id] = options[:parent_id] if options[:parent_id] json end - + def body_specific? self.zone.type_id < 3 end - + def zone Zone.find(zone_id) end - + def origin_pet_type=(pet_type) self.body_id = pet_type.body_id end - + def origin_biology_data=(data) self.type = 'biology' self.zone_id = data[:zone_id].to_i self.url = data[:asset_url] self.zones_restrict = data[:zones_restrict] end - + def origin_object_data=(data) self.type = 'object' self.zone_id = data[:zone_id].to_i self.url = data[:asset_url] end - + def mall_data=(data) self.zone_id = data['zone'].to_i self.url = "#{NEOPETS_ASSET_SERVER}/#{data['url']}" end - + before_create do uri = URI.parse url begin @@ -105,17 +110,17 @@ class SwfAsset < ActiveRecord::Base end end end - + before_save do # If an asset body ID changes, that means more than one body ID has been # linked to it, meaning that it's probably wearable by all bodies. self.body_id = 0 if !self.body_specific? || (!self.new_record? && self.body_id_changed?) end - + class DownloadError < Exception;end - + private - + def local_path_within_outfit_swfs uri = URI.parse(url) pieces = uri.path.split('/') @@ -124,3 +129,4 @@ class SwfAsset < ActiveRecord::Base File.join(relevant_pieces) end end + diff --git a/app/views/items/show.html.haml b/app/views/items/show.html.haml index 6c3286c9..83eb219f 100644 --- a/app/views/items/show.html.haml +++ b/app/views/items/show.html.haml @@ -25,7 +25,7 @@ %a#customize-more.button{:href => '/'} Customize more #item-preview - #item-preview-species= standard_species_images(@item.supported_species) + #item-preview-species= standard_species_images_for(@item) #item-preview-error #item-preview-swf Javascript and Flash are required to preview wearables. Sorry! @@ -36,3 +36,4 @@ = include_javascript_libraries :jquery, :swfobject = javascript_include_tag 'items/show' + diff --git a/config/basic_type_hashes.yml b/config/basic_type_hashes.yml index 51923a06..cbd741f5 100644 --- a/config/basic_type_hashes.yml +++ b/config/basic_type_hashes.yml @@ -1,270 +1,412 @@ +--- acara: blue: mnbztxxn green: obxdjm88 red: 7njwqlq8 yellow: o3wj6s77 + baby: sk5z9r4n + maraquan: ntvxx7xc + mutant: zmofvhdx aisha: blue: n9ozx4z5 green: vw3j5rnt red: r27dkxdc yellow: t4osnjxq + baby: bon8m2o4 + maraquan: 37wgjb6s + mutant: c4wrb4vd blumaroo: blue: 8dng4bvh green: xmqmg3m9 red: j77lzlmx yellow: kfonqhdc + baby: md7bjrss + maraquan: 82fzq4kh + mutant: jcxf8fwx bori: blue: cdtgf7jo green: 6c8dvw76 red: tg7hx55j yellow: sc2hhvhn + baby: fofhnsqj + maraquan: rln8zot7 + mutant: 7lqvol7n bruce: blue: w3hljbx7 green: gwgc67tt red: 72d26x2b yellow: wqz8xn4t + baby: kkkqd9hd + maraquan: oddhtbgs + mutant: b3jrdol9 buzz: blue: rkvgtzv3 green: schfx5c4 red: gddkkb5j yellow: jc9klfxm + baby: kvxjk22f + maraquan: cm66xwqk + mutant: j9brxg4n chia: blue: d65mc9gf green: 8d4949sr red: 4lrb4n3f yellow: oqs3kzmf + baby: kvm8jr9k + mutant: mnf38rsr chomby: blue: h3qctoxg green: 552mzx7r red: 9swsd8ln yellow: bdml26md + baby: v2l4d2nf + 8-bit: 96lgkwnj + maraquan: v5x466sl cybunny: blue: v8shwcoq green: xl6msllv red: lfkscvgd yellow: cwlv6zz9 + baby: h3lx4sw2 + maraquan: 6zlzft6t + mutant: zzsonrg8 draik: blue: 2g4jtrfh green: 438o4dv5 red: vd2joxxq yellow: bob39shq + baby: h7g2lzvw + maraquan: mtxqzrjz + mutant: gq3dmvcf elephante: blue: 4ckkjxjj green: r22hl9oh red: jhhhbrww yellow: jf9ww4om + baby: gvj8c7rn + maraquan: gh3bofrd + mutant: fsvsz87k eyrie: blue: m5mvxv23 green: s42o6oon red: 6kngmhvs yellow: d6b8fqnm + baby: gqxnnsc4 + maraquan: 4qxob8gs + mutant: dvxtocr7 flotsam: blue: hwdo7rlb green: 47vt32x2 red: xhob45wn yellow: r8r7jmvr + baby: 84jvz79s + mutant: wj83k9kh gelert: blue: 4g7n9mh5 green: jfmwlmk8 red: l95vq4w2 yellow: 5nrd2lvd + baby: c473f4o7 + maraquan: 3dzw2l8c + mutant: 9ldnhtw5 gnorbu: blue: 6c275jcg green: ghfgo5tn red: qozzjgmg yellow: 5lrvftfb + baby: fzjt34mc + mutant: bqgwjvq2 grarrl: blue: j7q65fv4 green: t3mstkl6 red: r946sq53 yellow: jtg67z98 + baby: g66gs7m7 + maraquan: 3dldqlj2 + mutant: 8jtbfojx grundo: blue: nwx8v2rb green: 5xn4kjf8 red: qjcb6t8x yellow: n2g9d94f + baby: 4sqhoksn + maraquan: 8coxbn7v + mutant: 9tfsvdk7 hissi: blue: 7ls55f33 green: dz5dwsbx red: jsfvcqwt yellow: z24m7h7l + baby: fwd63758 + maraquan: z5dw7ln7 + mutant: xx8g8jjb ixi: blue: ro3qcd6s green: w32r74vo red: zs2m6862 yellow: oggkzvq7 + baby: r6djxvxw + maraquan: xd73hdoz + mutant: scd2f73q jetsam: blue: w3rl2k9n green: cwss4w3s red: ghddf93g yellow: kz43rnld + baby: zhz7wlxg + mutant: m8n64bsr jubjub: blue: 8mf5gzov green: m267j935 red: szckt2tj yellow: 78hdsnmw + baby: 92x2bdtq + maraquan: gj7dmvsd + mutant: 783vj5nt kacheek: blue: tvnq2l5s green: fkzfcb47 red: 9hbtocjh yellow: 4gsrb59g + baby: nw424vgn + mutant: fq6nnvwh kau: blue: ktlxmrtr green: x9ww69qo red: mrdtwkto yellow: 78w49w7x + baby: qtxmt64l + maraquan: r5fzvmtd + mutant: 39thxh84 kiko: blue: 2qlfqqnd green: 42j5q3zx red: mcodrwlt yellow: 86b5xdn6 + baby: d54hfwc7 + mutant: wdqnjz2z koi: blue: tjvv5cq8 green: ncfn87wk red: f3wvzz6r yellow: 4f6cvzgh + baby: 59lh68wg + mutant: zoc4ntrg korbat: blue: 4qxwv7w7 green: d2ow9co8 red: omx9c876 yellow: nsxodd8z + baby: kgmnvlx8 + maraquan: 6g73dr7l + mutant: lf98r4sb kougra: blue: rfsbh59t green: otvq569n red: x8hsckbh yellow: x7bbg59h + baby: 45dwtg2f + 8-bit: 5g6fj4g8 + maraquan: 4snffcf5 + mutant: 5qt72kjo krawk: blue: hxgsm5d4 green: 7ngw8z4f red: kzwxb3dn yellow: m2gq59r5 + baby: zgscmthd + maraquan: kbmht6zh + mutant: 8m94bk5w kyrii: blue: ojmo7qgg green: t3b8s9nz red: 6fvg9ngs yellow: blxmjgbk + baby: 42fokwff + maraquan: s2do4jhq + mutant: x25ml223 lenny: blue: jfc75n44 green: 242k4kdq red: kc4w238d yellow: 8r94jhfq + baby: k2qnttxo + mutant: 89zgj87k lupe: blue: r552fqj8 green: ohmx4lc9 red: 6jwlghsg yellow: z42535zh + baby: b4xfn6xv + maraquan: fhlqw4bf + mutant: 7ggn4k6x lutari: blue: qgg6z8s7 green: wtjv7hw4 red: lgws33oo yellow: 8x37fgjf + baby: jnbjxvf5 meerca: blue: wwwhjf88 green: lg9bb6tf red: v7g8w8w6 yellow: kk2nn2jr + baby: 446k2fln + maraquan: kgw5wv42 + mutant: n4nd3c4v moehog: blue: moot839l green: jgkoro5z red: dk47mfk8 yellow: g84ovct7 + baby: 5r3oq2k4 + maraquan: 5cgov2km + mutant: wdc9gwtq mynci: blue: xwlo9657 green: s33qzokw red: f828tkrc yellow: tdfhsndr + baby: 9d354534 + maraquan: 7d2ll99r + mutant: bqtb9hsz nimmo: blue: bx7fho8x green: mb4mcwj5 red: mok2fcl4 yellow: 9559onds + baby: nwq55xx5 ogrin: blue: 26v2sx49 green: lg5bzqkq red: 3xgoz429 yellow: rjzmx24v + baby: bfrh323n + mutant: t6s8hrzx peophin: blue: 996t7zfg green: c9qo7zzt red: kokc52kh yellow: 4khvfnwl + baby: 4jbvhghx + mutant: bjdggzwd poogle: blue: 5n2vx7v2 green: fw6lvf3c red: xhcwbdd5 yellow: xozlhd68 + baby: b2467jkz + maraquan: z44rk3xg pteri: blue: 82d369sm green: slqmqr78 red: tjhwbro3 yellow: 4gkkb57f + baby: snd77sqq + maraquan: sktvv8t7 + mutant: 57tg8jb8 quiggle: blue: 4zs6b32m green: vbrsgqrl red: xrmt72wr yellow: jdto7mj4 + baby: 2k7wm6dx + mutant: bv9htk44 ruki: blue: qsgbm5f6 green: vgml65cz red: sm55zs3q yellow: n43szv42 + baby: grbwvh54 + maraquan: kjx2jjcg + mutant: hsls3rd8 scorchio: blue: ohb3b486 green: kmb82xw8 red: hkjoncsx yellow: hgfofbl2 + baby: n443j7hn + maraquan: bm9b2b73 + mutant: 3mfckmq4 shoyru: blue: dj7n2zk8 green: 3qwwn4n2 red: 3zb2s2zw yellow: mmvn4tkg + baby: xvvxvvjt + maraquan: f57glqj6 + mutant: 49tjr4vf skeith: blue: 7c847jkn green: qdgwwz6c red: fc4cxk3t yellow: 533nc7rj + baby: tjjsn83l + maraquan: o65f8j4f + mutant: jb8jhqqr techo: blue: vl8qro4r green: 2zc3vd47 red: zgl2gjjn yellow: 84gvowmj + baby: ng83vkg7 + maraquan: ln8lcbvh + mutant: tkqgzsv3 tonu: blue: jd433863 green: 6mcv27mn red: oskw3hqd yellow: 5bhqs7sv + baby: h6jd7kw8 + mutant: 59kr9c96 tuskaninny: blue: jj6t6cqd green: stl4mm78 red: 48z4o75j yellow: q39wn6vq + baby: c3vhmr2m + mutant: j2hob2sj uni: blue: 74xvb24d green: njzvoflw red: jzfbdomk yellow: 5qdfrtcq + baby: st84z8xf + maraquan: 3f3nzsjf + mutant: qw3gsfbb usul: blue: xff976n8 green: 5245lzmh red: rox4mgh5 yellow: vxn8b4vr + baby: 39vzk6nc + maraquan: 8sfjr77d + mutant: 3dgowzld wocky: blue: xonntr5f green: 4ssorlcs red: 743smd5v yellow: dnr2kj4b + baby: s7tld393 xweetok: blue: ddhdrt2o green: thzwbqsq red: tdkqr2b6 yellow: o36btnbf + baby: wwh2wvkn + mutant: qs53ohzt yurble: blue: nhn5lxb4 green: mjgbr7vb red: h95cs547 yellow: vfqkbvv6 + baby: 8b8fvxo2 + mutant: 5o5t2j3s zafara: blue: x8c57g2l green: tq56zj3x red: n32sgrvm yellow: onxr95x6 + baby: 93c22wgn + maraquan: b2gxlo5h + mutant: 9c9frk9g diff --git a/config/colors_with_unique_bodies.txt b/config/colors_with_unique_bodies.txt new file mode 100644 index 00000000..85a3bf54 --- /dev/null +++ b/config/colors_with_unique_bodies.txt @@ -0,0 +1,5 @@ +8-Bit +Baby +Maraquan +Mutant +