feat(Armoire): WIP

This commit is contained in:
Sabe Jones
2015-05-26 16:06:04 -05:00
parent 399ff8c00b
commit c90f7e2c34
57 changed files with 8501 additions and 8175 deletions

View File

@@ -37,6 +37,12 @@
margin-right: 10px;
}
.multi-achievement {
margin: auto;
padding-left: 0.5em;
padding-right: 0.5em;
}
[class*="Mount_Head_"], [class*="Mount_Body_"]{
margin-top:18px; /* Sprite accommodates 105x123 box */
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 287 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 153 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 150 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 KiB

After

Width:  |  Height:  |  Size: 678 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 357 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 300 KiB

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.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

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: 19 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: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

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.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 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: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 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: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -52,9 +52,11 @@
"costume": "Costume",
"costumeText": "If you prefer the look of other gear to what you have equipped, check the \"Use Costume\" box to visually don a costume while wearing your battle gear underneath.",
"useCostume": "Use Costume",
"gearAchievement": "You have earned the \"Ultimate Gear\" Achievement for upgrading to the maximum gear set!",
"gearAchievement": "You have earned the \"Ultimate Gear\" Achievement for upgrading to the maximum gear set for a class! You have attained the following complete sets:",
"moreGearAchievements": "To attain more Ultimate Gear badges, change classes on <a href='/#/options/profile/stats' target='_blank'>your stats page</a> and buy up your new class's gear!",
"armoireUnlocked": "You've also unlocked the <strong>Enchanted Armoire!</strong> Click on the Enchanted Armoire Reward for a random chance at special Equipment! It may also give you random XP or food items.",
"ultimGearName": "Ultimate Gear",
"ultimGearText": "Has upgraded to the maximum weapon and armor set",
"ultimGearText": "Has upgraded to the maximum weapon and armor set for the following classes:",
"level": "Level",
"levelUp": "Level Up!",
"mana": "Mana",

View File

@@ -2,6 +2,10 @@
"potionText": "Health Potion",
"potionNotes": "Recover 15 Health (Instant Use)",
"armoireText": "Enchanted Armoire",
"armoireNotesFull": "Open the Armoire to randomly receive special Equipment, Experience, or food!",
"armoireNotesEmpty": "The Enchanted Armoire will have more Equipment soon. Keep clicking for Experience and Food!",
"dropEggWolfText": "Wolf",
"dropEggWolfAdjective": "loyal",

View File

@@ -138,6 +138,11 @@
"weaponMystery301404Text": "Steampunk Cane",
"weaponMystery301404Notes": "Excellent for taking a turn about town. March 3015 Subscriber Item. Confers no benefit.",
"weaponArmoireBasicCrossbowText": "Basic Crossbow",
"weaponArmoireBasicCrossbowNotes": "This crossbow can pierce a task's armor from very far away! Increases Strength by <%= str %>, Perception by <%= per %>, and Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"weaponArmoireLunarSceptreText": "Soothing Lunar Sceptre",
"weaponArmoireLunarSceptreNotes": "The healing power of this wand waxes and wanes. Increases Constitution by <%= con %> and Intelligence by <%= int %>. Enchanted Armoire: Soothing Lunar Set (Item 3 of 3).",
"armor": "armor",
"armorBase0Text": "Plain Clothing",
@@ -282,6 +287,11 @@
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
"armorArmoireLunarArmorText": "Soothing Lunar Armor",
"armorArmoireLunarArmorNotes": "The light of the moon will make you strong and savvy. Increases Strength by <%= str %> and Intelligence by <%= int %>. Enchanted Armoire: Soothing Lunar Set (Item 2 of 3).",
"armorArmoireGladiatorArmorText": "Gladiator Armor",
"armorArmoireGladiatorArmorNotes": "To be a gladiator you must be not only cunning... but strong. Increases Perception by <%= per %> and Strength by <%= str %>. Enchanted Armoire: Gladiator Set (Item 2 of 3).",
"headgear": "headgear",
"headBase0Text": "No Helm",
@@ -422,6 +432,17 @@
"headMystery301405Text": "Basic Top Hat",
"headMystery301405Notes": "A basic top hat, just begging to be paired with some fancy head accessories. Confers no benefit. May 3015 Subscriber Item.",
"headArmoireLunarCrownText": "Soothing Lunar Crown",
"headArmoireLunarCrownNotes": "This crown strengthens health and sharpens senses, especially when the moon is full. Increases Constitution by <%= con %> and Perception by <%= per %>. Enchanted Armoire: Soothing Lunar Set (Item 1 of 3).",
"headArmoireRedHairbowText": "Red Hairbow",
"headArmoireRedHairbowNotes": "Become strong, tough and smart while wearing this beautiful Red Hairbow! Increases Strength by <%= str %>, Constitution by <%= con %>, and Intelligence by <%= int %>. Enchanted Armoire: Independent Item.",
"headArmoireVioletFloppyHatText": "Violet Floppy Hat",
"headArmoireVioletFloppyHatNotes": "Many spells have been sewn into this simple hat, giving it a pleasing purple color. Increases Perception by <%= per %>, Intelligence by <%= int %>, and Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"headArmoireGladiatorHelmText": "Gladiator Helm",
"headArmoireGladiatorHelmNotes": "To be a gladiator you must be not only strong.... but cunning. Increases Intelligence by <%= int %> and Perception by <%= per %>. Enchanted Armoire: Gladiator Set (Item 1 of 3).",
"headArmoireRancherHatText": "Rancher Hat",
"headArmoireRancherHatNotes": "Round up your pets and wrangle your mounts while wearing this magical Rancher Hat! Increases Strength by <%= str %>, Perception by <%= per %>, and Intelligence by <%= int %>. Enchanted Armoire: Independent Item.",
"offhand": "shield-hand item",
"shieldBase0Text": "No Shield-Hand Equipment",
@@ -500,6 +521,9 @@
"shieldMystery301405Text": "Clock Shield",
"shieldMystery301405Notes": "Time is on your side with this towering clock shield! Confers no benefit. June 3015 Subscriber Item.",
"shieldArmoireGladiatorShieldText": "Gladiator Shield",
"shieldArmoireGladiatorShieldNotes": "To be a gladiator you must.... eh, whatever, just bash them with your shield. Increases Constitution by <%= con %> and Strength by <%= str %>. Enchanted Armoire: Gladiator Set (Item 3 of 3).",
"backBase0Text": "No Back Accessory",
"backBase0Notes": "No Back Accessory.",

View File

@@ -39,7 +39,7 @@
"close": "Close",
"saveAndClose": "Save & Close",
"cancel": "Cancel",
"ok": "Ok",
"ok": "OK",
"add": "Add",
"undo": "Undo",
"continue": "Continue",

View File

@@ -20,5 +20,8 @@
"messageDropFood": "You've found <%= dropArticle %><%= dropText %>! <%= dropNotes %>",
"messageDropEgg": "You've found a <%= dropText %> Egg! <%= dropNotes %>",
"messageDropPotion": "You've found a <%= dropText %> Hatching Potion! <%= dropNotes %>",
"messageFoundQuest": "You've found the quest \"<%= questText %>\"!"
"messageFoundQuest": "You've found the quest \"<%= questText %>\"!",
"armoireEquipment": "You found a piece of rare Equipment in the Armoire: <%= dropText %>! Awesome!",
"armoireFood": "You rummage in the Armoire and find <%= dropArticle %><%= dropText %>. What's that doing in here?",
"armoireExp": "You wrestle with the Armoire and gain Experience. Take that!"
}

View File

@@ -140,6 +140,9 @@ gear =
201502: text: t('weaponMystery201502Text'), notes: t('weaponMystery201502Notes'), mystery:'201502', value: 0
201505: text: t('weaponMystery201505Text'), notes: t('weaponMystery201505Notes'), mystery:'201505', value: 0
301404: text: t('weaponMystery301404Text'), notes: t('weaponMystery301404Notes'), mystery:'301404', value: 0
armoire:
basicCrossbow: text: t('weaponArmoireBasicCrossbowText'), notes: t('weaponArmoireBasicCrossbowNotes', {str: 5, per: 5, con: 5}), value: 100, str: 5, per: 5, con: 5
lunarSceptre: text: t('weaponArmoireLunarSceptreText'), notes: t('weaponArmoireLunarSceptreNotes', {con: 7, int: 7}), value: 100, con: 7, int: 7, set: 'soothing'
armor:
base:
@@ -224,6 +227,9 @@ gear =
201503: text: t('armorMystery201503Text'), notes: t('armorMystery201503Notes'), mystery:'201503', value: 0
201504: text: t('armorMystery201504Text'), notes: t('armorMystery201504Notes'), mystery:'201504', value: 0
301404: text: t('armorMystery301404Text'), notes: t('armorMystery301404Notes'), mystery:'301404', value: 0
armoire:
lunarArmor: text: t('armorArmoireLunarArmorText'), notes: t('armorArmoireLunarArmorNotes', {str: 7, int: 7}), value: 100, str: 7, int: 7, set: 'soothing'
gladiatorArmor: text: t('armorArmoireGladiatorArmorText'), notes: t('armorArmoireGladiatorArmorNotes', {str: 7, per: 7}), value: 100, str: 7, per: 7, set: 'gladiator'
head:
base:
@@ -306,6 +312,12 @@ gear =
201505: text: t('headMystery201505Text'), notes: t('headMystery201505Notes'), mystery:'201505', value: 0
301404: text: t('headMystery301404Text'), notes: t('headMystery301404Notes'), mystery:'301404', value: 0
301405: text: t('headMystery301405Text'), notes: t('headMystery301405Notes'), mystery:'301405', value: 0
armoire:
lunarCrown: text: t('headArmoireLunarCrownText'), notes: t('headArmoireLunarCrownNotes', {con: 7, per: 7}), value: 100, con: 7, per: 7, set: 'soothing'
redHairbow: text: t('headArmoireRedHairbowText'), notes: t('headArmoireRedHairbowNotes', {str: 5, int: 5, con: 5}), value: 100, str: 5, int: 5, con: 5
violetFloppyHat: text: t('headArmoireVioletFloppyHatText'), notes: t('headArmoireVioletFloppyHatNotes', {per: 5, int: 5, con: 5}), value: 100, per: 5, int: 5, con: 5
gladiatorHelm: text: t('headArmoireGladiatorHelmText'), notes: t('headArmoireGladiatorHelmNotes', {per: 7, int: 7}), value: 100, per: 7, int: 7, set: 'gladiator'
rancherHat: text: t('headArmoireRancherHatText'), notes: t('headArmoireRancherHatNotes', {str: 5, per: 5, int: 5}), value: 100, str: 5, per: 5, int: 5
shield:
base:
@@ -365,6 +377,8 @@ gear =
spring2015Healer: event: events.spring2015, specialClass: 'healer', text: t('shieldSpecialSpring2015HealerText'), notes: t('shieldSpecialSpring2015HealerNotes', {con: 9}), value: 70, con: 9
mystery:
301405: text: t('shieldMystery301405Text'), notes: t('shieldMystery301405Notes'), mystery:'301405', value: 0
armoire:
gladiatorShield: text: t('shieldArmoireGladiatorShieldText'), notes: t('shieldArmoireGladiatorShieldNotes', {con: 5, str: 5}), value: 100, con: 5, str: 5, set: 'gladiator'
back:
base:
@@ -442,7 +456,7 @@ api.gear =
flat: {}
_.each gearTypes, (type) ->
_.each classes.concat(['base', 'special', 'mystery']), (klass) ->
_.each classes.concat(['base', 'special', 'mystery', 'armoire']), (klass) ->
# add "type" to each item, so we can reference that as "weapon" or "armor" in the html
_.each gear[type][klass], (item, i) ->
key = "#{type}_#{klass}_#{i}"
@@ -475,11 +489,12 @@ api.timeTravelerStore = (owned) ->
###
---------------------------------------------------------------
Potion
Unique Rewards: Potion and Armoire
---------------------------------------------------------------
###
api.potion = type: 'potion', text: t('potionText'), notes: t('potionNotes'), value: 25, key: 'potion'
api.armoire = type: 'armoire', text: t('armoireText'), notes: t('armoireNotesFull'), value: 100, key: 'armoire', canOwn: ((u)-> _.contains(u.achievements.ultimateGearSets, true))
###
---------------------------------------------------------------

View File

@@ -166,6 +166,7 @@ api.updateStore = (user) ->
changes = changes.concat _.filter content.gear.flat, (v) ->
v.klass in ['special','mystery'] and !user.items.gear.owned[v.key] and v.canOwn?(user)
changes.push content.potion
if user.flags.armoireEnabled then changes.push content.armoire
# Return sorted store (array)
_.sortBy changes, (c)->sortOrder[c.type]
@@ -835,23 +836,40 @@ api.wrap = (user, main=true) ->
buy: (req, cb) ->
{key} = req.params
item = if key is 'potion' then content.potion else content.gear.flat[key]
item = if key is 'potion' then content.potion
else if key is 'armoire' then content.armoire
else content.gear.flat[key]
return cb?({code:404, message:"Item '#{key} not found (see https://github.com/HabitRPG/habitrpg-shared/blob/develop/script/content.coffee)"}) unless item
return cb?({code:401, message: i18n.t('messageNotEnoughGold', req.language)}) if user.stats.gp < item.value
return cb?({code:401, message: "You can't own this item"}) if item.canOwn? and !item.canOwn(user)
return cb?({code:401, message: "You can't buy this item"}) if item.canOwn? and !item.canOwn(user)
if item.key is 'potion'
user.stats.hp += 15
user.stats.hp = 50 if user.stats.hp > 50
else if item.key is 'armoire'
armoireResult = user.fns.predictableRandom()
eligibleEquipment = _.filter(content.gear.flat, ((i)->i.klass is 'armoire' and !user.items.gear.owned[i.key]))
if !_.isEmpty(eligibleEquipment) and (armoireResult < .7 or !user.flags.armoireOpened)
drop = user.fns.randomVal(eligibleEquipment)
user.items.gear.owned[drop.key] = true
user.flags.armoireOpened = true
message = i18n.t('armoireEquipment', {dropText: drop.text(req.language)}, req.language)
else if (!_.isEmpty(eligibleEquipment) and armoireResult < .85) or armoireResult < .6
drop = user.fns.randomVal _.where(content.food, {canDrop:true})
user.items.food[drop.key] ?= 0
user.items.food[drop.key] += 1
message = i18n.t('armoireFood', {dropArticle: drop.article, dropText: drop.text(req.language)}, req.language)
else
user.stats.exp += Math.floor(user.fns.predictableRandom(user.stats.exp) * 40 + 10)
message = i18n.t('armoireExp', req.language)
else
user.items.gear.equipped[item.type] = item.key
user.items.gear.owned[item.key] = true
message = user.fns.handleTwoHanded(item, null, req)
message ?= i18n.t('messageBought', {itemText: item.text(req.language)}, req.language)
if not user.achievements.ultimateGear and item.last
user.fns.ultimateGear()
if item.last then user.fns.ultimateGear()
user.stats.gp -= item.value
mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gold','goldCost':item.value})
cb? {code:200, message}, _.pick(user,$w 'items achievements stats')
cb? {code:200, message}, _.pick(user,$w 'items achievements stats flags')
buyMysterySet: (req, cb)->
return cb?({code:401, message:"You don't have enough Mystic Hourglasses"}) unless user.purchased.plan.consecutive.trinkets>0
@@ -1660,32 +1678,19 @@ api.wrap = (user, main=true) ->
# ----------------------------------------------------------------------
# Achievements
# ----------------------------------------------------------------------
ultimateGear: () ->
# on the server this is a LoDash transform, on the client its an object
gear = if window? then user.items.gear.owned else user.items.gear.owned.toObject()
ownedLastGear = _.chain(content.gear.flat)
.pick(_.keys gear)
.values()
.filter (gear) -> gear.last
lastGearClassTypeMatrix = {}
_.each content.classes, (klass) ->
lastGearClassTypeMatrix[klass] = {}
#_.each content.gearTypes, (type) ->
_.each ['armor', 'weapon', 'shield', 'head'], (type) ->
lastGearClassTypeMatrix[klass][type] = false
return true # false exits the each loop early
ownedLastGear.each (gear) ->
lastGearClassTypeMatrix[gear.klass]["shield"] = true if gear.twoHanded
lastGearClassTypeMatrix[gear.klass][gear.type] = true
shouldGrant = _(lastGearClassTypeMatrix)
.values()
.reduce(((ans, klass) -> ans or _(klass).values().reduce(((ans, gearType) -> ans and gearType), true)), false)
.valueOf()
user.achievements.ultimateGear = shouldGrant
ultimateGear: ->
# on the server this is a Lodash transform, on the client its an object
owned = if window? then user.items.gear.owned else user.items.gear.owned.toObject()
user.achievements.ultimateGearSets ?= {healer: false, wizard: false, rogue: false, warrior: false}
content.classes.forEach (klass) ->
user.achievements.ultimateGearSets[klass] = _.reduce ['armor', 'shield', 'head', 'weapon'], (soFarGood, type) ->
found = _.find content.gear.tree[type][klass], {last:true}
soFarGood and (!found or owned[found.key]==true) #!found only true when weapon is two-handed (mages)
, true # start with true, else `and` will fail right away
user.markModified? 'achievements.ultimateGearSets'
if _.contains(user.achievements.ultimateGearSets, true) and user.flags.armoireEnabled != true
user.flags.armoireEnabled = true
user.markModified? 'flags'
nullify: ->
user.ops = null

View File

@@ -33,7 +33,8 @@ newUser = (addTasks=true)->
todos: []
rewards: []
flags: {}
achievements: {}
achievements:
ultimateGearSets: {}
contributor:
level: 2
@@ -432,7 +433,6 @@ describe 'User', ->
expect(spell.lvl).to.be.above(0)
expect(spell.cast).to.be.a('function')
describe 'drop system', ->
user = null
@@ -481,6 +481,81 @@ describe 'User', ->
user.fns.randomVal.restore()
user.fns.predictableRandom.restore()
describe 'Enchanted Armoire', ->
user = newUser()
fullArmoire = {'weapon_warrior_0': true, 'armor_armoire_gladiatorArmor':true,'armor_armoire_lunarArmor':true,'head_armoire_gladiatorHelm':true,'head_armoire_lunarCrown':true,'head_armoire_rancherHat':true,'head_armoire_redHairbow':true,'head_armoire_violetFloppyHat':true,'shield_armoire_gladiatorShield':true,'weapon_armoire_basicCrossbow':true,'weapon_armoire_lunarSceptre':true}
beforeEach ->
# too many predictableRandom calls to stub, let's return the last element
sinon.stub(user.fns, 'randomVal', (obj)->
result = undefined
for key, val of obj
result = val
result
)
it 'does not open without paying', ->
sinon.stub(user.fns, 'predictableRandom').returns 0
user.ops.buy({params: {key: 'armoire'}})
expect(user.items.gear.owned).to.eql {'weapon_warrior_0': true}
expect(user.items.food).to.eql {}
expect(user.stats.exp).to.eql 0
it 'does not open without Ultimate Gear achievement', ->
sinon.stub(user.fns, 'predictableRandom').returns 0
user.stats.gp = 500
user.ops.buy({params: {key: 'armoire'}})
user.achievements.ultimateGearSets = {'healer':false,'wizard':false,'rogue':false,'warrior':false}
user.ops.buy({params: {key: 'armoire'}})
expect(user.items.gear.owned).to.eql {'weapon_warrior_0': true}
expect(user.items.food).to.eql {}
expect(user.stats.exp).to.eql 0
it 'always drops equipment the first time', ->
sinon.stub(user.fns, 'predictableRandom', cycle [.9,.5])
user.achievements.ultimateGearSets = {'healer':false,'wizard':false,'rogue':true,'warrior':false}
user.ops.buy({params: {key: 'armoire'}})
expect(user.items.gear.owned).to.eql {'weapon_warrior_0': true, 'shield_armoire_gladiatorShield':true}
expect(user.items.food).to.eql {}
expect(user.stats.exp).to.eql 0
expect(user.stats.gp).to.eql 400
it 'gives Experience', ->
sinon.stub(user.fns, 'predictableRandom', cycle [.9,.5])
user.ops.buy({params: {key: 'armoire'}})
expect(user.items.gear.owned).to.eql {'weapon_warrior_0': true, 'shield_armoire_gladiatorShield':true}
expect(user.items.food).to.eql {}
expect(user.stats.exp).to.eql 30
expect(user.stats.gp).to.eql 300
it 'gives food', ->
sinon.stub(user.fns, 'predictableRandom', cycle [.8,.5])
user.ops.buy({params: {key: 'armoire'}})
expect(user.items.gear.owned).to.eql {'weapon_warrior_0': true, 'shield_armoire_gladiatorShield':true}
expect(user.items.food).to.eql {'Honey': 1}
expect(user.stats.exp).to.eql 30
expect(user.stats.gp).to.eql 200
it 'gives more equipment', ->
sinon.stub(user.fns, 'predictableRandom', cycle [.6,.5])
user.ops.buy({params: {key: 'armoire'}})
expect(user.items.gear.owned).to.eql {'weapon_warrior_0': true, 'shield_armoire_gladiatorShield':true,'head_armoire_rancherHat':true}
expect(user.items.food).to.eql {'Honey': 1}
expect(user.stats.exp).to.eql 30
expect(user.stats.gp).to.eql 100
it 'does not give equipment if all equipment has been found', ->
sinon.stub(user.fns, 'predictableRandom', cycle [.6,.5])
user.items.gear.owned = fullArmoire
user.ops.buy({params: {key: 'armoire'}})
expect(user.items.gear.owned).to.eql fullArmoire
expect(user.items.food).to.eql {'Honey': 1}
expect(user.stats.exp).to.eql 60
expect(user.stats.gp).to.eql 0
afterEach ->
user.fns.randomVal.restore()
user.fns.predictableRandom.restore()
describe 'Quests', ->
_.each shared.content.quests, (quest)->
@@ -510,11 +585,11 @@ describe 'User', ->
_.each [1..5], (i) ->
user.ops.buy {params:'#{type}_#{klass}_#{i}'}
it 'does not get ultimateGear ' + klass, ->
expect(user.achievements.ultimateGear).to.not.be.ok()
expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok()
_.each shared.content.gearTypes, (type) ->
user.ops.buy {params:'#{type}_#{klass}_6'}
xit 'gets ultimateGear ' + klass, ->
expect(user.achievements.ultimateGear).to.be.ok()
expect(user.achievements.ultimateGearSets[klass]).to.be.ok()
it 'does not get beastMaster if user has less than 90 drop pets', ->
user = newUser()

View File

@@ -101,10 +101,10 @@ habitrpg.controller('NotificationCtrl',
}
});
$rootScope.$watch('user.achievements.ultimateGear', function(after, before){
if (after === before || after !== true) return;
$rootScope.$watch('user.achievements.ultimateGearSets', function(after, before){
if (_.isEqual(after,before) || !_.contains(User.user.achievements.ultimateGearSets, true)) return;
$rootScope.openModal('achievements/ultimateGear');
});
}, true);
$rootScope.$watch('user.achievements.rebirths', function(after, before){
if(after === before) return;

View File

@@ -34,7 +34,8 @@ var UserSchema = new Schema({
originalUser: Boolean,
helpedHabit: Boolean, //TODO: Deprecate this. Superseded by habitSurveys
habitSurveys: Number,
ultimateGear: Boolean,
ultimateGear: Boolean, //TODO: Deprecate this. Superseded by ultimateGearSets
ultimateGearSets: Schema.Types.Mixed,
beastMaster: Boolean,
beastMasterCount: Number,
mountMaster: Boolean,
@@ -156,7 +157,9 @@ var UserSchema = new Schema({
lastWeeklyRecap: {type: Date, 'default': Date.now},
communityGuidelinesAccepted: {type: Boolean, 'default': false},
cronCount: {type:Number, 'default':0},
welcomed: {type: Boolean, 'default': false}
welcomed: {type: Boolean, 'default': false},
armoireEnabled: {type: Boolean, 'default': false},
armoireOpened: {type: Boolean, 'default': false}
},
history: {
exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined

View File

@@ -6,7 +6,7 @@ script(type='text/ng-template', id='partials/options.inventory.equipment.html')
div
button.btn.btn-default(type="button", ng-click='dequip("battleGear");') {{env.t("unequipBattleGear")}}
li.customize-menu.inventory-gear
menu.pets-menu(label='{{::label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery")}', ng-show='gear[klass]')
menu.pets-menu(label='{{::label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery"), armoire:env.t("armoireText")}', ng-show='gear[klass]')
div(ng-repeat='item in gear[klass]')
button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='user.ops.equip({params:{key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}')
.col-md-6

View File

@@ -14,7 +14,27 @@ script(id='modals/achievements/ultimateGear.html', type='text/ng-template')
h4=env.t('modalAchievement')
.modal-body
.achievement.achievement-armor
=env.t('gearAchievement')
p=env.t('gearAchievement')
br
table.multi-achievement
tr
td(ng-if='::user.achievements.ultimateGearSets.healer').multi-achievement
.achievement-ultimate-healer.multi-achievement
=env.t('healer')
td(ng-if='::user.achievements.ultimateGearSets.wizard').multi-achievement
.achievement-ultimate-mage.multi-achievement
=env.t('mage')
td(ng-if='::user.achievements.ultimateGearSets.rogue').multi-achievement
.achievement-ultimate-rogue.multi-achievement
=env.t('rogue')
td(ng-if='::user.achievements.ultimateGearSets.warrior').multi-achievement
.achievement-ultimate-warrior.multi-achievement
=env.t('warrior')
br
p(ng-if='!(user.achievements.ultimateGearSets.healer && user.achievements.ultimateGearSets.wizard && user.achievements.ultimateGearSets.rogue && user.achievements.ultimateGearSets.warrior)')!=env.t('moreGearAchievements')
br
.shop_armoire.pull-right
p!=env.t("armoireUnlocked")
.modal-footer
button.btn.btn-default(ng-click='$close()')=env.t('ok')

View File

@@ -78,18 +78,25 @@ div(ng-if='::profile.achievements.perfect || user._id == profile._id')
small(ng-if='::profile.achievements.perfect == 1')=env.t('perfectSingularText')
hr
//-div(ng-if='::profile.achievements.ultimateGear || user._id == profile._id')
.achievement.achievement-armor(ng-if='::profile.achievements.ultimateGear')
div(ng-class='::{muted: !profile.achievements.ultimateGear}')
div(ng-if='::profile.achievements.ultimateGearSets || user._id == profile._id')
.achievement.achievement-armor(ng-if='::profile.achievements.ultimateGearSets')
div(ng-class='::{muted: !profile.achievements.ultimateGearSets}')
h5=env.t('ultimGearName')
small=env.t('ultimGearText')
hr
// Remove the following when ultimate gear is fixed (https://github.com/HabitRPG/habitrpg/issues/2232):
div(ng-if='::user._id == profile._id')
div.muted
h5=env.t('ultimGearName')
small
+aLink('https://github.com/HabitRPG/habitrpg/issues/2232', 'Returning soon')
table.multi-achievement
tr
td(ng-if='::profile.achievements.ultimateGearSets.healer').multi-achievement
.achievement-ultimate-healer.multi-achievement
=env.t('healer')
td(ng-if='::profile.achievements.ultimateGearSets.wizard').multi-achievement
.achievement-ultimate-mage.multi-achievement
=env.t('mage')
td(ng-if='::profile.achievements.ultimateGearSets.rogue').multi-achievement
.achievement-ultimate-rogue.multi-achievement
=env.t('rogue')
td(ng-if='::profile.achievements.ultimateGearSets.warrior').multi-achievement
.achievement-ultimate-warrior.multi-achievement
=env.t('warrior')
hr
div(ng-if='::profile.achievements.beastMaster || user._id == profile._id')