feat(customize): Animal skins and ears

Implements a base-pet-themed set of avatar skins and head accessories, the latter of which has additional handling for the user to purchase them from the Avatar Customization page.
This commit is contained in:
Sabe Jones
2015-05-13 11:19:08 -05:00
parent 4050e9ad0c
commit 29109051d7
41 changed files with 88 additions and 32 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -138,5 +138,6 @@
"displayNameDescription3": "and scroll down to the Registration section to change your login name.",
"unequipBattleGear": "Unequip Battle Gear",
"unequipCostume": "Unequip Costume",
"unequipPetMountBackground": "Unequip Pet, Mount, Background"
"unequipPetMountBackground": "Unequip Pet, Mount, Background",
"animalSkins": "Animal Skins"
}

View File

@@ -529,6 +529,8 @@
"bodySpecialSummerHealerNotes": "A stylish collar of live coral! Confers no benefit. Limited Edition 2014 Summer Gear.",
"headAccessory": "head accessory",
"accessories": "Accessories",
"animalEars": "Animal Ears",
"headAccessoryBase0Text": "No Head Accessory",
"headAccessoryBase0Notes": "No Head Accessory.",
@@ -551,6 +553,23 @@
"headAccessorySpecialSpring2015HealerText": "Green Kitty Ears",
"headAccessorySpecialSpring2015HealerNotes": "These cute kitty ears will make others green with envy. Confers no benefit. Limited Edition 2015 Spring Gear.",
"headAccessoryBearEarsText": "Bear Ears",
"headAccessoryBearEarsNotes": "These ears make you look like a cuddly bear! Confers no benefit.",
"headAccessoryCactusEarsText": "Cactus Ears",
"headAccessoryCactusEarsNotes": "These ears make you look like a prickly cactus! Confers no benefit.",
"headAccessoryFoxEarsText": "Fox Ears",
"headAccessoryFoxEarsNotes": "These ears make you look like a wily fox! Confers no benefit.",
"headAccessoryLionEarsText": "Lion Ears",
"headAccessoryLionEarsNotes": "These ears make you look like a regal lion! Confers no benefit.",
"headAccessoryPandaEarsText": "Panda Ears",
"headAccessoryPandaEarsNotes": "These ears make you look like a gentle panda! Confers no benefit.",
"headAccessoryPigEarsText": "Pig Ears",
"headAccessoryPigEarsNotes": "These ears make you look like a whimsical pig! Confers no benefit.",
"headAccessoryTigerEarsText": "Tiger Ears",
"headAccessoryTigerEarsNotes": "These ears make you look like a fierce tiger! Confers no benefit.",
"headAccessoryWolfEarsText": "Wolf Ears",
"headAccessoryWolfEarsNotes": "These ears make you look like a loyal wolf! Confers no benefit.",
"headAccessoryMystery201403Text": "Forest Walker Antlers",
"headAccessoryMystery201403Notes": "These antlers shimmer with moss and lichen. Confers no benefit. March 2014 Subscriber Item.",
"headAccessoryMystery201404Text": "Twilight Butterfly Antennae",

View File

@@ -400,7 +400,15 @@ gear =
spring2015Warrior: event: events.spring2015, specialClass: 'warrior', text: t('headAccessorySpecialSpring2015WarriorText'), notes: t('headAccessorySpecialSpring2015WarriorNotes'), value: 20
spring2015Mage: event: events.spring2015, specialClass: 'wizard', text: t('headAccessorySpecialSpring2015MageText'), notes: t('headAccessorySpecialSpring2015MageNotes'), value: 20
spring2015Healer: event: events.spring2015, specialClass: 'healer', text: t('headAccessorySpecialSpring2015HealerText'), notes: t('headAccessorySpecialSpring2015HealerNotes'), value: 20
# Animal ears
bearEars: gearSet: 'animal', text: t('headAccessoryBearEarsText'), notes: t('headAccessoryBearEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_bearEars?)
cactusEars: gearSet: 'animal', text: t('headAccessoryCactusEarsText'), notes: t('headAccessoryCactusEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_cactusEars?)
foxEars: gearSet: 'animal', text: t('headAccessoryFoxEarsText'), notes: t('headAccessoryFoxEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_foxEars?)
lionEars: gearSet: 'animal', text: t('headAccessoryLionEarsText'), notes: t('headAccessoryLionEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_lionEars?)
pandaEars: gearSet: 'animal', text: t('headAccessoryPandaEarsText'), notes: t('headAccessoryPandaEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_pandaEars?)
pigEars: gearSet: 'animal', text: t('headAccessoryPigEarsText'), notes: t('headAccessoryPigEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_pigEars?)
tigerEars: gearSet: 'animal', text: t('headAccessoryTigerEarsText'), notes: t('headAccessoryTigerEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_tigerEars?)
wolfEars: gearSet: 'animal', text: t('headAccessoryWolfEarsText'), notes: t('headAccessoryWolfEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_animalEars_wolfEars?)
mystery:
201403: text: t('headAccessoryMystery201403Text'), notes: t('headAccessoryMystery201403Notes'), mystery:'201403', value: 0
201404: text: t('headAccessoryMystery201404Text'), notes: t('headAccessoryMystery201404Notes'), mystery:'201404', value: 0

View File

@@ -745,7 +745,7 @@ api.wrap = (user, main=true) ->
if type is 'gear'
item = content.gear.flat[key]
return cb?({code:401, message: i18n.t('alreadyHave', req.language)}) if user.items.gear.owned[key]
price = (if item.twoHanded then 2 else 1) / 4
price = (if item.twoHanded or item.gearSet is 'animal' then 2 else 1) / 4
else
item = content[type][key]
price = item.value / 4
@@ -895,6 +895,9 @@ api.wrap = (user, main=true) ->
return cb?({code:401, message: i18n.t('notEnoughGems', req.language)}) if user.balance < cost and !alreadyOwns
if fullSet
_.each path.split(","), (p) ->
if ~path.indexOf('gear.')
user.fns.dotSet("#{p}", true);true
else
user.fns.dotSet("purchased.#{p}", true);true
else
if alreadyOwns
@@ -904,8 +907,8 @@ api.wrap = (user, main=true) ->
return cb? null, req
user.fns.dotSet "purchased." + path, true
user.balance -= cost
user.markModified? 'purchased'
cb? null, _.pick(user,$w 'purchased preferences')
if ~path.indexOf('gear.') then user.markModified? 'gear.owned' else user.markModified? 'purchased'
cb? null, _.pick(user,$w 'purchased preferences items')
ga?.event('behavior', 'gems', path).send()
# ------

View File

@@ -103,26 +103,6 @@ habitrpg.controller("InventoryCtrl",
}
}
$scope.purchase = function(type, item){
if (type == 'special') return User.user.ops.buySpecialSpell({params:{key:item.key}});
var gems = User.user.balance * 4;
var string = (type == 'weapon') ? window.env.t('weapon') : (type == 'armor') ? window.env.t('armor') : (type == 'head') ? window.env.t('headgear') : (type == 'shield') ? window.env.t('offhand') : (type == 'headAccessory') ? window.env.t('headAccessory') : (type == 'hatchingPotions') ? window.env.t('hatchingPotion') : (type == 'eggs') ? window.env.t('eggSingular') : (type == 'quests') ? window.env.t('quest') : (item.key == 'Saddle') ? window.env.t('foodSaddleText').toLowerCase() : type; // this is ugly but temporary, once the purchase modal is done this will be removed
if (type == 'weapon' || type == 'armor' || type == 'head' || type == 'shield' || type == 'headAccessory') {
if (gems < ((item.specialClass == "wizard") && (item.type == "weapon")) + 1) return $rootScope.openModal('buyGems');
var message = window.env.t('buyThis', {text: string, price: ((item.specialClass == "wizard") && (item.type == "weapon")) + 1, gems: gems})
if($window.confirm(message))
User.user.ops.purchase({params:{type:"gear",key:item.key}});
} else {
if(gems < item.value) return $rootScope.openModal('buyGems');
var message = window.env.t('buyThis', {text: string, price: item.value, gems: gems})
if($window.confirm(message))
User.user.ops.purchase({params:{type:type,key:item.key}});
}
}
$scope.choosePet = function(egg, potion){
var petDisplayName = env.t('petName', {
potion: Content.hatchingPotions[potion] ? Content.hatchingPotions[potion].text() : potion,

View File

@@ -3,8 +3,8 @@
/* Make user and settings available for everyone through root scope.
*/
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce',
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments,$sce) {
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window',
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window) {
var user = User.user;
var initSticky = _.once(function(){
@@ -201,7 +201,40 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
chart.draw(data, options);
};
$rootScope.getGearArray = function(set){
var flatGearArray = _.toArray(Content.gear.flat);
var filteredArray = _.where(flatGearArray, {gearSet: set});
return filteredArray;
}
$rootScope.purchase = function(type, item){
if (type == 'special') return User.user.ops.buySpecialSpell({params:{key:item.key}});
var gems = User.user.balance * 4;
var string = (type == 'weapon') ? window.env.t('weapon') : (type == 'armor') ? window.env.t('armor') : (type == 'head') ? window.env.t('headgear') : (type == 'shield') ? window.env.t('offhand') : (type == 'headAccessory') ? window.env.t('headAccessory') : (type == 'hatchingPotions') ? window.env.t('hatchingPotion') : (type == 'eggs') ? window.env.t('eggSingular') : (type == 'quests') ? window.env.t('quest') : (item.key == 'Saddle') ? window.env.t('foodSaddleText').toLowerCase() : type; // FIXME this is ugly but temporary, once the purchase modal is done this will be removed
var price = ((((item.specialClass == "wizard") && (item.type == "weapon")) || item.gearSet == "animal") + 1);
if (type == 'weapon' || type == 'armor' || type == 'head' || type == 'shield' || type == 'headAccessory') {
if (User.user.items.gear.owned[item.key]) {
if (User.user.preferences.costume) return User.user.ops.equip({params:{type: 'costume', key: item.key}});
else {
return User.user.ops.equip({params:{type: 'equipped', key: item.key}})
}
}
if (gems < price) return $rootScope.openModal('buyGems');
var message = window.env.t('buyThis', {text: string, price: price, gems: gems})
if($window.confirm(message))
User.user.ops.purchase({params:{type:"gear",key:item.key}});
} else {
if(gems < item.value) return $rootScope.openModal('buyGems');
var message = window.env.t('buyThis', {text: string, price: item.value, gems: gems})
if($window.confirm(message))
User.user.ops.purchase({params:{type:type,key:item.key}});
}
}
/*
------------------------

View File

@@ -198,7 +198,8 @@ module.exports.locals = function(req, res, next) {
Content: shared.content,
mods: require('./models/user').mods,
tavern: tavern, // for world boss
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {},
_: _
});
// Put query-string party (& guild but use partyInvite for backward compatibility)

View File

@@ -4,6 +4,7 @@ mixin gemCost(cost)
= ' ' + env.t('locked')
block
-var gearGroup = function(grouping) { return env._(env.Content.gear.flat).where({gearSet:grouping}).pluck('key') }
-var showPath = function(path, items, joiner) { return path+'["'+items.join('"] '+joiner+' '+path+'["')+'"]'; }
-var unlockPath = function(path, items) { return 'unlock("'+path+'.'+items.join(','+path+'.')+'")'; }
@@ -45,6 +46,15 @@ mixin customizeProfile(mobile)
each shirt in specialShirts
button.customize-option(type='button', class='{{user.preferences.size}}_shirt_'+shirt, ng-class='{locked: !user.purchased.shirt.'+shirt+'}', ng-click='unlock("shirt.'+shirt+'")')
h3(class=mobile?'item item-divider':'')=env.t('accessories')
menu(type='list')
li.customize-menu
menu(label=env.t('animalEars'))
span(ng-hide='#{showPath("user.items.gear.owned", gearGroup("animal"), "&&")}')
+gemCost(2)
button.btn.btn-xs(ng-click='#{unlockPath("items.gear.owned", gearGroup("animal"))}')!= env.t('unlockSet', {cost: 5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.customize-option(ng-repeat='item in ::getGearArray("animal")' ng-class="{locked: user.items.gear.owned[item.key] == undefined}", popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='purchase(item.type,item)', class='shop_{{::item.key}}')
.col-md-4
h3(class=mobile?'item item-divider':'')=env.t('bodyHead')
@@ -132,11 +142,12 @@ mixin customizeProfile(mobile)
each color in ['ddc994','f5a76e','ea8349','c06534','98461a','915533','c3e1dc','6bd049']
button.customize-option(type='button', class='skin_#{color}', ng-click='set({"preferences.skin":"#{color}"})')
// Rainbow Skin
// Always-available premium skins
+buyPref('skin', ['eb052b','f69922','f5d70f','0ff591','2b43f6','d7a9f7','800ed0','rainbow'], 'rainbowSkins')
+buyPref('skin', ['pastelPink','pastelOrange','pastelYellow','pastelGreen','pastelBlue','pastelPurple','pastelRainbowChevron','pastelRainbowDiagonal'], 'pastelSkins', 'disabled')
+buyPref('skin', ['bear','cactus','fox','lion','panda','pig','tiger','wolf'], 'animalSkins')
// Special Events
// Seasonal event skins. Note that Spooky Skins are a legacy set and should always be disabled for purchase
+buyPref('skin', ['pastelPink','pastelOrange','pastelYellow','pastelGreen','pastelBlue','pastelPurple','pastelRainbowChevron','pastelRainbowDiagonal'], 'pastelSkins', 'disabled')
+buyPref('skin', ['monster','pumpkin','skeleton','zombie','ghost','shadow'], 'spookySkins', 'disabled')
+buyPref('skin', ['candycorn','ogre','pumpkin2','reptile','shadow2','skeleton2','transparent','zombie2'], 'supernaturalSkins', 'disabled')