Merge branch 'develop' of github.com:HabitRPG/habitrpg into develop

This commit is contained in:
Blade Barringer 2015-05-01 12:36:07 -05:00
commit e334ce7684
7 changed files with 172 additions and 98 deletions

View file

@ -1,51 +1,51 @@
{
"spellWizardFireballText": "Burst of Flames",
"spellWizardFireballNotes": "Flames blast forth, scorching a task. You reduce the task's redness, deal damage to any monster you're battling, and gain Experience -- more for blue tasks.",
"spellWizardFireballNotes": "Flames burst from your hands. You gain XP, and you deal extra damage to Bosses! (Based on: INT)",
"spellWizardMPHealText": "Ethereal Surge",
"spellWizardMPHealNotes": "A flow of magical energy rushes from your hands and recharges your party. Your party recovers MP.",
"spellWizardMPHealNotes": "You sacrifice mana to help your friends. The rest of your party gains MP! (Based on: INT)",
"spellWizardEarthText": "Earthquake",
"spellWizardEarthNotes": "The ground below your party's tasks cracks and shakes with extreme intensity, slowing them down and opening them up to more attacks. Your party gains a buff to Intelligence, which means more Experience.",
"spellWizardEarthNotes": "Your mental power shakes the earth. Your whole party gains a buff to Intelligence! (Based on: Unbuffed INT)",
"spellWizardFrostText": "Chilling Frost",
"spellWizardFrostNotes": "Ice erupts from every surface, swallowing your tasks and freezing them in place. Your Dailies' streaks won't reset at the end of the day. Incomplete Dailies will still damage you!",
"spellWizardFrostNotes": "Ice covers your tasks. None of your streaks will reset to zero tomorrow! (One cast affects all streaks.)",
"spellWarriorSmashText": "Brutal Smash",
"spellWarriorSmashNotes": "You savagely hit a single task with all of your might. The task's redness decreases, and you deal extra damage to any monster you're fighting.",
"spellWarriorSmashNotes": "You hit a task with all of your might. It gets more blue/less red, and you deal extra damage to Bosses! (Based on: STR)",
"spellWarriorDefensiveStanceText": "Defensive Stance",
"spellWarriorDefensiveStanceNotes": "You take a moment to relax your body and enter a defensive stance to ready yourself for the tasks' next onslaught. Reduces damage from Dailies at the end of the day by boosting your Constitution.",
"spellWarriorDefensiveStanceNotes": "You prepare yourself for the onslaught of your tasks. You gain a buff to Constitution! (Based on: Unbuffed CON)",
"spellWarriorValorousPresenceText": "Valorous Presence",
"spellWarriorValorousPresenceNotes": "Your presence emboldens the party. Their newfound courage makes them tougher by boosting their Strength.",
"spellWarriorValorousPresenceNotes": "Your presence emboldens your party. Your whole party gains a buff to Strength! (Based on: Unbuffed STR)",
"spellWarriorIntimidateText": "Intimidating Gaze",
"spellWarriorIntimidateNotes": "Your gaze strikes fear into the hearts of your party's enemies. The party gains a moderate boost to defense by buffing Constitution.",
"spellWarriorIntimidateNotes": "Your gaze strikes fear into your enemies. Your whole party gains a buff to Constitution! (Based on: Unbuffed CON)",
"spellRoguePickPocketText": "Pickpocket",
"spellRoguePickPocketNotes": "Your nimble fingers run through the task's pockets and find some treasures for yourself. You gain an immediate gold bonus from a task. The 'fatter' (bluer) your task, the more gold you steal!",
"spellRoguePickPocketNotes": "You rob a nearby task. You gain gold! (Based on: PER)",
"spellRogueBackStabText": "Backstab",
"spellRogueBackStabNotes": "Without a sound, you sweep behind a task and stab it in the back. You deal higher damage to the task, with a higher chance of a critical hit.",
"spellRogueBackStabNotes": "You betray a foolish task. You gain gold and XP! (Based on: STR)",
"spellRogueToolsOfTradeText": "Tools of the Trade",
"spellRogueToolsOfTradeNotes": "You share your thievery tools with the party to aid them in 'acquiring' more gold. By buffing Perception, the party's gold bonus for tasks and chance of drops are boosted for a day.",
"spellRogueToolsOfTradeNotes": "You share your talents with friends. Your whole party gains a buff to Perception! (Based on: Unbuffed PER)",
"spellRogueStealthText": "Stealth",
"spellRogueStealthNotes": "You duck into the shadows, pulling up your hood. Many Dailies won't find you this night; fewer yet the higher your Perception.",
"spellRogueStealthNotes": "You are too sneaky to spot. Some of your undone Dailies will not cause damage tonight, and their streaks/color will not change. (Cast multiple times to affect more Dailies)",
"spellHealerHealText": "Healing Light",
"spellHealerHealNotes": "Light covers your body, healing your wounds. You recover Health.",
"spellHealerHealNotes": "Light covers your body, healing your wounds. You regain health! (Based on: CON and INT)",
"spellHealerBrightnessText": "Searing Brightness",
"spellHealerBrightnessNotes": "You cast a burst of light that blinds all of your tasks. The redness of your tasks is reduced.",
"spellHealerBrightnessNotes": "A burst of light dazzles your tasks. They become more blue and less red! (Based on: INT)",
"spellHealerProtectAuraText": "Protective Aura",
"spellHealerProtectAuraNotes": "A magical aura surrounds your party members, protecting them from damage. Your party members gain a boost to their defense by buffing Constitution.",
"spellHealerProtectAuraNotes": "You shield your party from damage. Your whole party gains a buff to Constitution! (Based on: Unbuffed CON)",
"spellHealerHealAllText": "Blessing",
"spellHealerHealAllNotes": "Soothing light envelops your party and heals them of their injuries. Your party members recover Health.",
"spellHealerHealAllNotes": "A soothing aura surrounds you. Your whole party regains health! (Based on: CON and INT)",
"spellSpecialSnowballAuraText": "Snowball",
"spellSpecialSnowballAuraNotes": "Throw a snowball at a party member! What could possibly go wrong? Lasts until member's new day.",

View file

@ -510,28 +510,30 @@ api.gearTypes = gearTypes
#
diminishingReturns = (bonus, max, halfway=max/2) -> max*(bonus/(bonus+halfway))
calculateBonus = (value, stat, crit=1, stat_scale=0.5) -> (if value < 0 then 1 else value+1) + (stat * stat_scale * crit)
api.spells =
wizard:
fireball:
# Burst of Flames
text: t('spellWizardFireballText')
mana: 10
lvl: 11
target: 'task'
notes: t('spellWizardFireballNotes')
cast: (user, target) ->
# I seriously have no idea what I'm doing here. I'm just mashing buttons until numbers seem right-ish. Anyone know math?
bonus = user._statsComputed.int * user.fns.crit('per')
target.value += diminishingReturns(bonus*.02, 4)
bonus *= Math.ceil ((if target.value < 0 then 1 else target.value+1) *.075)
#console.log {bonus, expBonus:bonus,upBonus:bonus*.1}
user.stats.exp += diminishingReturns(bonus,75)
user.party.quest.progress.up += diminishingReturns(bonus*.1,50,30)
#Sync the user stats to see if we level the user
user.party.quest.progress.up ?= 0
user.party.quest.progress.up += Math.ceil(user._statsComputed.int * .1)
# Sync the user stats to see if we level the user
req = { language: user.preferences.language }
user.fns.updateStats( user.stats , req )
mpheal:
# Ethereal Surge
text: t('spellWizardMPHealText')
mana: 30
lvl: 12
@ -539,11 +541,11 @@ api.spells =
notes: t('spellWizardMPHealNotes'),
cast: (user, target)->
_.each target, (member) ->
bonus = Math.ceil(user._statsComputed.int * .1)
bonus = 25 if bonus > 25 #prevent ability to replenish own mp infinitely
member.stats.mp += bonus
bonus = user._statsComputed.int
member.stats.mp += Math.ceil(diminishingReturns(bonus, 25, 125)) # maxes out at 25
earth:
# Earthquake
text: t('spellWizardEarthText')
mana: 35
lvl: 13
@ -551,10 +553,12 @@ api.spells =
notes: t('spellWizardEarthNotes'),
cast: (user, target) ->
_.each target, (member) ->
bonus = user._statsComputed.int - user.stats.buffs.int
member.stats.buffs.int ?= 0
member.stats.buffs.int += Math.ceil(user._statsComputed.int * .05)
member.stats.buffs.int += Math.ceil(diminishingReturns(bonus, 30,200))
frost:
# Chilling Frost
text: t('spellWizardFrostText'),
mana: 40
lvl: 14
@ -565,24 +569,32 @@ api.spells =
warrior:
smash:
# Brutal Smash
text: t('spellWarriorSmashText')
mana: 10
lvl: 11
target: 'task'
notes: t('spellWarriorSmashNotes')
cast: (user, target) ->
target.value += 2.5 * (user._statsComputed.str / (user._statsComputed.str + 50)) * user.fns.crit('con')
user.party.quest.progress.up += Math.ceil(user._statsComputed.str * .2)
bonus = user._statsComputed.str * user.fns.crit('con')
target.value += diminishingReturns(bonus, 2.5, 35)
user.party.quest.progress.up ?= 0
user.party.quest.progress.up += diminishingReturns(bonus, 55, 70)
defensiveStance:
# Defensive Stance
text: t('spellWarriorDefensiveStanceText')
mana: 25
lvl: 12
target: 'self'
notes: t('spellWarriorDefensiveStanceNotes')
cast: (user, target) ->
bonus = user._statsComputed.con - user.stats.buffs.con
user.stats.buffs.con ?= 0
user.stats.buffs.con += Math.ceil(user._statsComputed.con * .05)
user.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 40, 200))
valorousPresence:
# Valorous Presence
text: t('spellWarriorValorousPresenceText')
mana: 20
lvl: 13
@ -590,9 +602,12 @@ api.spells =
notes: t('spellWarriorValorousPresenceNotes')
cast: (user, target) ->
_.each target, (member) ->
bonus = user._statsComputed.str - user.stats.buffs.str
member.stats.buffs.str ?= 0
member.stats.buffs.str += Math.ceil(user._statsComputed.str * .05)
member.stats.buffs.str += Math.ceil(diminishingReturns(bonus, 20, 200))
intimidate:
# Intimidating Gaze
text: t('spellWarriorIntimidateText')
mana: 15
lvl: 14
@ -600,20 +615,24 @@ api.spells =
notes: t('spellWarriorIntimidateNotes')
cast: (user, target) ->
_.each target, (member) ->
bonus = user._statsComputed.con - user.stats.buffs.con
member.stats.buffs.con ?= 0
member.stats.buffs.con += Math.ceil(user._statsComputed.con * .03)
member.stats.buffs.con += Math.ceil(diminishingReturns(bonus,24,200))
rogue:
pickPocket:
# Pickpocket
text: t('spellRoguePickPocketText')
mana: 10
lvl: 11
target: 'task'
notes: t('spellRoguePickPocketNotes')
cast: (user, target) ->
bonus = (if target.value < 0 then 1 else target.value+2) + (user._statsComputed.per * 0.5)
user.stats.gp += 25 * (bonus / (bonus + 75))
bonus = calculateBonus(target.value, user._statsComputed.per)
user.stats.gp += diminishingReturns(bonus, 25, 75)
backStab:
# Backstab
text: t('spellRogueBackStabText')
mana: 15
lvl: 12
@ -621,26 +640,28 @@ api.spells =
notes: t('spellRogueBackStabNotes')
cast: (user, target) ->
_crit = user.fns.crit('str', .3)
target.value += _crit * .03
bonus = (if target.value < 0 then 1 else target.value+1) * _crit
user.stats.exp += bonus
user.stats.gp += bonus
# user.party.quest.progress.up += bonus if user.party.quest.key # remove hurting bosses for rogues, seems OP for now
#Sync the user stats to see if we level the user
bonus = calculateBonus(target.value, user._statsComputed.str, _crit)
user.stats.exp += diminishingReturns(bonus, 75, 50)
user.stats.gp += diminishingReturns(bonus, 18, 75)
# Sync the user stats to see if we level the user
req = { language: user.preferences.language }
user.fns.updateStats( user.stats , req )
toolsOfTrade:
# Tools of the Trade
text: t('spellRogueToolsOfTradeText')
mana: 25
lvl: 13
target: 'party'
notes: t('spellRogueToolsOfTradeNotes')
cast: (user, target) ->
## lasts 24 hours ##
_.each target, (member) ->
bonus = user._statsComputed.per - user.stats.buffs.per
member.stats.buffs.per ?= 0
member.stats.buffs.per += Math.ceil(user._statsComputed.per * .03)
member.stats.buffs.per += Math.ceil(diminishingReturns(bonus, 100, 50))
stealth:
# Stealth
text: t('spellRogueStealthText')
mana: 45
lvl: 14
@ -648,11 +669,12 @@ api.spells =
notes: t('spellRogueStealthNotes')
cast: (user, target) ->
user.stats.buffs.stealth ?= 0
## scales to user's # of dailies; maxes out at 100% at 100 per ##
user.stats.buffs.stealth += Math.ceil(user.dailys.length * user._statsComputed.per / 100)
## scales to user's # of dailies; Diminishing Returns, maxes out at 64%, halfway point at 55 PER##
user.stats.buffs.stealth += Math.ceil( diminishingReturns(user._statsComputed.per, user.dailys.length*0.64,55))
healer:
heal:
# Healing Light
text: t('spellHealerHealText')
mana: 15
lvl: 11
@ -661,7 +683,9 @@ api.spells =
cast: (user, target) ->
user.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * .075
user.stats.hp = 50 if user.stats.hp > 50
brightness:
# Searing Brightness
text: t('spellHealerBrightnessText')
mana: 15
lvl: 12
@ -670,19 +694,23 @@ api.spells =
cast: (user, target) ->
_.each user.tasks, (target) ->
return if target.type is 'reward'
target.value += 1.5 * (user._statsComputed.int / (user._statsComputed.int + 40))
target.value += 4 * (user._statsComputed.int / (user._statsComputed.int + 40))
protectAura:
# Protective Aura
text: t('spellHealerProtectAuraText')
mana: 30
lvl: 13
target: 'party'
notes: t('spellHealerProtectAuraNotes')
cast: (user, target) ->
## lasts 24 hours ##
_.each target, (member) ->
bonus = user._statsComputed.con - user.stats.buffs.con
member.stats.buffs.con ?= 0
member.stats.buffs.con += Math.ceil(user._statsComputed.con * .15)
member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 200, 200))
heallAll:
# Blessing
text: t('spellHealerHealAllText')
mana: 25
lvl: 14
@ -893,11 +921,11 @@ api.specialPets =
'Mammoth-Base': 'mammoth'
api.specialMounts =
'BearCub-Polar': 'polarBear'
'LionCub-Ethereal': 'etherealLion'
'MantisShrimp-Base': 'mantisShrimp'
'Turkey-Base': 'turkey'
'Mammoth-Base': 'mammoth'
'BearCub-Polar': 'polarBear'
'LionCub-Ethereal': 'etherealLion'
'MantisShrimp-Base': 'mantisShrimp'
'Turkey-Base': 'turkey'
'Mammoth-Base': 'mammoth'
api.hatchingPotions =
Base: value: 2, text: t('hatchingPotionBase')

View file

@ -80,6 +80,17 @@ api.shouldDo = (day, repeat, options={}) ->
selected = repeat[api.dayMapping[api.startOfDay(_.defaults {now:day}, o).day()]]
return selected
###
------------------------------------------------------
Level cap
------------------------------------------------------
###
api.maxLevel = 100
api.capByLevel = (lvl) ->
if lvl > api.maxLevel then api.maxLevel else lvl
###
------------------------------------------------------
Scoring
@ -476,16 +487,13 @@ api.wrap = (user, main=true) ->
rebirth: (req, cb, ga) ->
# Cost is 8 Gems ($2)
if (user.balance < 2 && user.stats.lvl < 100)
if (user.balance < 2 && user.stats.lvl < api.maxLevel)
return cb? {code:401,message: i18n.t('notEnoughGems', req.language)}
# only charge people if they are under level 100 - ryan
if user.stats.lvl < 100
# only charge people if they are under the max level - ryan
if user.stats.lvl < api.maxLevel
user.balance -= 2
# Save off user's level, for calculating achievement eligibility later
if user.stats.lvl < 100
lvl = user.stats.lvl
else
lvl = 100
lvl = api.capByLevel(user.stats.lvl)
# Turn tasks yellow, zero out streaks
_.each user.tasks, (task) ->
unless task.type is 'reward'
@ -935,7 +943,7 @@ api.wrap = (user, main=true) ->
else
return cb?({code:401,message:i18n.t('notEnoughGems', req.language)}) unless user.balance >= .75
user.balance -= .75
_.merge user.stats, {str: 0, con: 0, per: 0, int: 0, points: user.stats.lvl}
_.merge user.stats, {str: 0, con: 0, per: 0, int: 0, points: api.capByLevel(user.stats.lvl)}
user.flags.classSelected = false
ga?.event('behavior', 'gems', 'changeClass').send()
#'stats.points': this is handled on the server
@ -946,7 +954,7 @@ api.wrap = (user, main=true) ->
user.flags.classSelected = true
user.preferences.disableClasses = true
user.preferences.autoAllocate = true
user.stats.str = user.stats.lvl
user.stats.str = api.capByLevel(user.stats.lvl)
user.stats.points = 0
cb? null, _.pick(user,$w 'stats flags preferences')
@ -1068,11 +1076,10 @@ api.wrap = (user, main=true) ->
nextDelta = if not options.cron and direction is 'down' then calculateReverseDelta() else calculateDelta()
unless task.type is 'reward'
if (user.preferences.automaticAllocation is true and user.preferences.allocationMode is 'taskbased' and !(task.type is 'todo' and direction is 'down')) then user.stats.training[task.attribute] += nextDelta
# ===== STRENGTH =====
# (Only for up-scoring, ignore up-onlies and rewards)
if direction is 'up' and !(task.type is 'habit' and !task.down)
if direction is 'up' # Make progress on quest based on STR
user.party.quest.progress.up = user.party.quest.progress.up || 0;
user.party.quest.progress.up += (nextDelta * (1 + (user._statsComputed.str / 200))) if task.type in ['daily','todo']
user.party.quest.progress.up += (nextDelta * (0.5 + (user._statsComputed.str / 400))) if task.type is 'habit'
task.value += nextDelta
delta += nextDelta
@ -1113,9 +1120,17 @@ api.wrap = (user, main=true) ->
hpMod = delta * conBonus * task.priority * 2 # constant 2 multiplier for better results
stats.hp += Math.round(hpMod * 10) / 10 # round to 1dp
gainMP = (delta) ->
delta *= user._tmp.crit or 1
user.stats.mp += delta
user.stats.mp = user._statsComputed.maxMP if user.stats.mp >= user._statsComputed.maxMP
user.stats.mp = 0 if user.stats.mp < 0
# ===== starting to actually do stuff, most of above was definitions =====
switch task.type
when 'habit'
changeTaskValue()
gainMP(_.max([0.25, (.0025 * user._statsComputed.maxMP)]) * if direction is 'down' then -1 else 1)
# Add habit value to habit-history (if different)
if (delta > 0) then addPoints() else subtractPoints()
@ -1134,6 +1149,7 @@ api.wrap = (user, main=true) ->
task.streak = 0 unless user.stats.buffs.streaks
else
changeTaskValue()
gainMP(_.max([1, (.01 * user._statsComputed.maxMP)]) * if direction is 'down' then -1 else 1)
if direction is 'down'
delta = calculateDelta() # recalculate delta for unchecking so the gp and exp come out correctly
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
@ -1160,12 +1176,7 @@ api.wrap = (user, main=true) ->
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
# MP++ per checklist item in ToDo, bonus per CLI
multiplier = _.max([(_.reduce(task.checklist,((m,i)->m+(if i.completed then 1 else 0)),1)),1])
mpDelta = _.max([(multiplier), (.01 * user._statsComputed.maxMP * multiplier)])
mpDelta *= user._tmp.crit or 1
mpDelta *= -1 if direction is 'down' # unticking a todo
user.stats.mp += mpDelta
user.stats.mp = user._statsComputed.maxMP if user.stats.mp >= user._statsComputed.maxMP
user.stats.mp = 0 if user.stats.mp < 0 # BUT DO WE WANT THIS? SEE COMMIT DESCRIPTION
gainMP(_.max([(multiplier), (.01 * user._statsComputed.maxMP * multiplier)]) * if direction is 'down' then -1 else 1)
when 'reward'
# Don't adjust values for rewards
@ -1222,7 +1233,9 @@ api.wrap = (user, main=true) ->
crit: (stat='str', chance=.03) ->
#console.log("Crit Chance:"+chance*(1+user._statsComputed[stat]/100))
if user.fns.predictableRandom() <= chance*(1+user._statsComputed[stat]/100) then 1.5 + (.02*user._statsComputed[stat])
s = user._statsComputed[stat]
if user.fns.predictableRandom() <= chance*(1 + s/100)
1.5 + 4*s/(s + 200)
else 1
###
@ -1342,7 +1355,8 @@ api.wrap = (user, main=true) ->
_.invert(stats)[_.min stats]
when "classbased"
# Attributes get 3:2:1:1 per 7 levels.
ideal = [(user.stats.lvl / 7 * 3), (user.stats.lvl / 7 * 2), (user.stats.lvl / 7), (user.stats.lvl / 7)]
lvlDiv7 = user.stats.lvl / 7
ideal = [(lvlDiv7 * 3), (lvlDiv7 * 2), lvlDiv7, lvlDiv7]
# Primary, secondary etc. attributes aren't explicitly defined, so hardcode them. In order as above
preference = switch user.stats.class
when "wizard" then ["int", "per", "con", "str"]
@ -1378,6 +1392,10 @@ api.wrap = (user, main=true) ->
user.stats.lvl++
tnl = api.tnl(user.stats.lvl)
user.stats.hp = 50
continue if user.stats.lvl > api.maxLevel
# Auto-allocate a point, or give them a new manual point
if user.preferences.automaticAllocation
user.fns.autoAllocate()
@ -1388,8 +1406,6 @@ api.wrap = (user, main=true) ->
user.stats.points = 0
# This happens after dropping level with Fix Character Values and perhaps from other causes.
# TODO: Subtract points from attributes in the same manner as on death.
user.stats.hp = 50
user.stats.exp = stats.exp
# Set flags when they unlock features
@ -1417,7 +1433,7 @@ api.wrap = (user, main=true) ->
dialog: i18n.t('messageFoundQuest', {questText: content.quests[k].text(req.language)}, req.language)
if !user.flags.rebirthEnabled and (user.stats.lvl >= 50 or user.achievements.ultimateGear or user.achievements.beastMaster)
user.flags.rebirthEnabled = true
if user.stats.lvl >= 100 and !user.flags.freeRebirth
if user.stats.lvl >= api.maxLevel and !user.flags.freeRebirth
user.flags.freeRebirth = true
###
@ -1491,34 +1507,50 @@ api.wrap = (user, main=true) ->
# Tally each task
todoTally = 0
dailyChecked = 0 # how many dailies were checked?
dailyDueUnchecked = 0 # how many dailies were due but not checked?
user.party.quest.progress.down ?= 0
user.todos.concat(user.dailys).forEach (task) ->
return unless task
{id, type, completed, repeat} = task
return if (type is 'daily') && !completed && user.stats.buffs.stealth && user.stats.buffs.stealth-- # User "evades" a certain number of uncompleted dailies
# Deduct experience for missed Daily tasks, but not for Todos (just increase todo's value)
unless completed
scheduleMisses = daysMissed
# Deduct points for missed Daily tasks, but not for Todos (just increase todo's value)
EvadeTask = 0
scheduleMisses = daysMissed
if completed
if type is 'daily'
dailyChecked += 1
else
# for dailys which have repeat dates, need to calculate how many they've missed according to their own schedule
if (type is 'daily') and repeat
scheduleMisses = 0
_.times daysMissed, (n) ->
thatDay = moment(now).subtract({days: n + 1})
scheduleMisses++ if api.shouldDo(thatDay, repeat, user.preferences)
if scheduleMisses > 0
perfect = false if type is 'daily'
delta = user.ops.score({params:{id:task.id, direction:'down'}, query:{times:scheduleMisses, cron:true}});
if api.shouldDo(thatDay, repeat, user.preferences)
scheduleMisses++
if user.stats.buffs.stealth
user.stats.buffs.stealth--
EvadeTask++
if scheduleMisses > EvadeTask
if type is 'daily'
perfect = false
if task.checklist?.length > 0 # Partially completed checklists dock fewer mana points
fractionChecked = _.reduce(task.checklist,((m,i)->m+(if i.completed then 1 else 0)),0) / task.checklist.length
dailyDueUnchecked += (1 - fractionChecked)
dailyChecked += fractionChecked
else
dailyDueUnchecked += 1
delta = user.ops.score({params:{id:task.id, direction:'down'}, query:{times:(scheduleMisses-EvadeTask), cron:true}}); # this line occurs for todos or dailys
user.party.quest.progress.down += delta if type is 'daily'
switch type
when 'daily'
# This occurs whether or not the task is completed
(task.history ?= []).push({ date: +new Date, value: task.value })
task.completed = false
_.each task.checklist, ((i)->i.completed=false;true)
if completed || (scheduleMisses > 0)
_.each task.checklist, ((i)->i.completed=false;true) # this should not happen for grey tasks unless they are completed
when 'todo'
#get updated value
absVal = if (completed) then Math.abs(task.value) else task.value
@ -1553,15 +1585,14 @@ api.wrap = (user, main=true) ->
if perfect
user.achievements.perfect ?= 0
user.achievements.perfect++
if user.stats.lvl < 100
lvlDiv2 = Math.ceil(user.stats.lvl/2)
else
lvlDiv2 = 50
lvlDiv2 = Math.ceil(api.capByLevel(user.stats.lvl) / 2)
{str:lvlDiv2,int:lvlDiv2,per:lvlDiv2,con:lvlDiv2,stealth:0,streaks:false}
else clearBuffs
# Add 10 MP, or 10% of max MP if that'd be more. Perform this after Perfect Day for maximum benefit
user.stats.mp += _.max([10,.1 * user._statsComputed.maxMP])
# Adjust for fraction of dailies completed
dailyChecked=1 if dailyDueUnchecked is 0 and dailyChecked is 0
user.stats.mp += _.max([10,.1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked)
user.stats.mp = user._statsComputed.maxMP if user.stats.mp > user._statsComputed.maxMP
# Analytics
@ -1641,10 +1672,7 @@ api.wrap = (user, main=true) ->
else
+val[stat] or 0
, 0
if user.stats.lvl < 100
m[stat] += (user.stats.lvl - 1) / 2
else
m[stat] += 50
m[stat] += Math.floor(api.capByLevel(user.stats.lvl) / 2)
m
, {}
computed.maxMP = computed.int*2 + 30

1
dist/habitrpg-shared.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -37,7 +37,7 @@ describe('front page', function() {
var login = element(by.css("#login-tab input[value='Login']"));
login.click();
var alertDialog = browser.switchTo().alert();
expect(alertDialog.getText()).toMatch(/Username or password incorrect./);
expect(alertDialog.getText()).toMatch("Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".");
alertDialog.accept();
});

View file

@ -138,7 +138,7 @@ api.loginLocal = function(req, res, next) {
var login = validator.isEmail(username) ? {'auth.local.email':username} : {'auth.local.username':username};
User.findOne(login, {auth:1}, function(err, user){
if (err) return next(err);
if (!user) return res.json(401, {err:"Username or password incorrect. Click 'Forgot Password' for help with either. (Note: usernames are case-sensitive)"});
if (!user) return res.json(401, {err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\"."});
if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
// We needed the whole user object first so we can get his salt to encrypt password comparison
User.findOne(
@ -146,7 +146,7 @@ api.loginLocal = function(req, res, next) {
, {_id:1, apiToken:1}
, function(err, user){
if (err) return next(err);
if (!user) return res.json(401,{err:"Username or password incorrect. Click 'Forgot Password' for help with either. (Note: usernames are case-sensitive)"});
if (!user) return res.json(401,{err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\"."});
res.json({id: user._id,token: user.apiToken});
password = null;
});
@ -227,7 +227,7 @@ api.resetPassword = function(req, res, next){
User.findOne({'auth.local.email': RegexEscape(email)}, function(err, user){
if (err) return next(err);
if (!user) return res.send(401, {err:"Couldn't find a user registered for email " + email});
if (!user) return res.send(401, {err:"Sorry, we can't find a user registered with email " + email + "\n- Make sure your email address is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login."});
user.auth.local.salt = salt;
user.auth.local.hashed_password = hashed_password;
utils.sendEmail({

View file

@ -30,8 +30,25 @@ hr
a(href='/static/old-news', target='_blank') Read older news
mixin oldNews
h5 4/24/2015 - APRIL SUBSCRIBER ITEM SET AND NEW LANGUAGES
h5 4/30/2015 - LAST CHANCE FOR BUSY BEE AND SPRING FLING ITEMS, AND SOON-TO-APPEAR NEW EQUIPMENT!
hr
tr
td
.promo_shimmer_hair.pull-right
h5 Last Chance for Spring Fling Outfits, Hair Colors, and Shiny Seeds!
p Tomorrow everything will be back to normal in Habitica, so if you still have any remaining Spring Fling Items that you want to buy, you'd better do it now! The <a href='https://habitrpg.com/#/options/inventory/seasonalshop' target='_blank'>Seasonal Edition items</a> and <a href='https://habitrpg.com/#/options/profile/avatar' target='_blank'>Hair Colors</a> won't be back until next March, and if the Limited Edition items return they will have increased prices or changed art, so strike while the iron is hot!
tr
td
.promo_mystery_201504.pull-right
h5 Last Chance for Busy Bee Item Set
p Reminder: this is the final day to <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>subscribe</a> and receive the Busy Bee Item Set! If you want the Busy Bee Robe or the Busy Bee Robes, now's the time! Thanks so much for your support <3
tr
td
h5 New Equipment Soon
p If you're sad to see the extra gold-purchasable equipment disappear, don't worry! We have something fun in the works...
h5 4/24/2015 - APRIL SUBSCRIBER ITEM SET AND NEW LANGUAGES
tr
td
.promo_mystery_201504.pull-right