mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-08-01 19:50:37 +00:00
re-jig all algos.coffee. add in changes I made in the recent past, put cron back in (why was this removed altogether??), fix some bugs
This commit is contained in:
parent
d42ac5d53c
commit
ad9c1940a9
2 changed files with 320 additions and 325 deletions
|
|
@ -1,186 +1,172 @@
|
|||
({ define: (
|
||||
if typeof define == "function"
|
||||
define
|
||||
moment = require('moment')
|
||||
_ = require('lodash')
|
||||
helpers = require('./helpers')
|
||||
items = require('./items')
|
||||
XP = 15
|
||||
HP = 2
|
||||
obj = module.exports = {}
|
||||
|
||||
obj.priorityValue = (priority = '!') ->
|
||||
switch priority
|
||||
when '!' then 1
|
||||
when '!!' then 1.5
|
||||
when '!!!' then 2
|
||||
else 1
|
||||
|
||||
obj.tnl = (level) ->
|
||||
if level >= 100
|
||||
value = 0
|
||||
else
|
||||
(F)->
|
||||
F(require, exports, module)
|
||||
)}).define (require, exports, module)->
|
||||
helpers = require('./helpers')
|
||||
moment = require('moment')
|
||||
items = require('./items')
|
||||
XP = 15
|
||||
HP = 2
|
||||
obj = module.exports =
|
||||
{};
|
||||
obj.priorityValue = (priority = '!') ->
|
||||
switch priority
|
||||
when '!' then 1
|
||||
when '!!' then 1.5
|
||||
when '!!!' then 2
|
||||
else
|
||||
1
|
||||
value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10
|
||||
# round to nearest 10
|
||||
return value
|
||||
|
||||
obj.tnl = (level) ->
|
||||
if level >= 100
|
||||
value = 0
|
||||
###
|
||||
Calculates Exp modificaiton based on level and weapon strength
|
||||
{value} task.value for exp gain
|
||||
{weaponStrength) weapon strength
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
obj.expModifier = (value, weaponStr, level, priority = '!') ->
|
||||
str = (level - 1) / 2
|
||||
# ultimately get this from user
|
||||
totalStr = (str + weaponStr) / 100
|
||||
strMod = 1 + totalStr
|
||||
exp = value * XP * strMod * obj.priorityValue(priority)
|
||||
return Math.round(exp)
|
||||
|
||||
###
|
||||
Calculates HP modification based on level and armor defence
|
||||
{value} task.value for hp loss
|
||||
{armorDefense} defense from armor
|
||||
{helmDefense} defense from helm
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
obj.hpModifier = (value, armorDef, helmDef, shieldDef, level, priority = '!') ->
|
||||
def = (level - 1) / 2
|
||||
# ultimately get this from user?
|
||||
totalDef = (def + armorDef + helmDef + shieldDef) / 100
|
||||
#ultimate get this from user
|
||||
defMod = 1 - totalDef
|
||||
hp = value * HP * defMod * obj.priorityValue(priority)
|
||||
return Math.round(hp * 10) / 10
|
||||
# round to 1dp
|
||||
|
||||
###
|
||||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
obj.gpModifier = (value, modifier, priority = '!', streak, user) ->
|
||||
val = value * modifier * obj.priorityValue(priority)
|
||||
if streak and user
|
||||
streakBonus = streak / 100 + 1
|
||||
# eg, 1-day streak is 1.1, 2-day is 1.2, etc
|
||||
afterStreak = val * streakBonus
|
||||
(user._tmp?={}).streakBonus = afterStreak - val if (val > 0) # keep this on-hand for later, so we can notify streak-bonus
|
||||
return afterStreak
|
||||
else
|
||||
return val
|
||||
|
||||
###
|
||||
Calculates the next task.value based on direction
|
||||
Uses a capped inverse log y=.95^x, y>= -5
|
||||
{currentValue} the current value of the task
|
||||
{direction} up or down
|
||||
###
|
||||
obj.taskDeltaFormula = (currentValue, direction) ->
|
||||
if currentValue < -47.27 then currentValue = -47.27
|
||||
else if currentValue > 21.27 then currentValue = 21.27
|
||||
delta = Math.pow(0.9747, currentValue)
|
||||
return delta if direction is 'up'
|
||||
return -delta
|
||||
|
||||
|
||||
###
|
||||
Drop System
|
||||
###
|
||||
|
||||
randomDrop = (user, delta, priority, streak = 0) ->
|
||||
# limit drops to 2 / day
|
||||
user.items.lastDrop ?=
|
||||
date: +moment().subtract('d', 1) # trick - set it to yesterday on first run, that way they can get drops today
|
||||
count: 0
|
||||
reachedDropLimit = (helpers.daysBetween(user.items.lastDrop.date, +new Date) is 0)
|
||||
and (user.items.lastDrop.count >= 2)
|
||||
return if reachedDropLimit
|
||||
|
||||
# % chance of getting a pet or meat
|
||||
chanceMultiplier = Math.abs(delta)
|
||||
chanceMultiplier *= obj.priorityValue(priority) # multiply chance by reddness
|
||||
chanceMultiplier += streak # streak bonus
|
||||
|
||||
if user.flags?.dropsEnabled and Math.random() < (.05 * chanceMultiplier)
|
||||
# current breakdown - 1% (adjustable) chance on drop
|
||||
# If they got a drop: 50% chance of egg, 50% Hatching Potion. If hatchingPotion, broken down further even further
|
||||
rarity = Math.random()
|
||||
|
||||
# Egg, 40% chance
|
||||
if rarity > .6
|
||||
drop = randomVal(pets)
|
||||
(user.items.eggs ?= []).push drop
|
||||
drop.type = 'Egg'
|
||||
drop.dialog = "You've found a #{drop.text} Egg! #{drop.notes}"
|
||||
|
||||
# Hatching Potion, 60% chance - break down by rarity even more. FIXME this may not be the best method, so revisit
|
||||
else
|
||||
value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10
|
||||
# round to nearest 10
|
||||
return value
|
||||
acceptableDrops = []
|
||||
|
||||
###
|
||||
Calculates Exp modificaiton based on level and weapon strength
|
||||
{value} task.value for exp gain
|
||||
{weaponStrength) weapon strength
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
obj.expModifier = (value, weaponStr, level, priority = '!') ->
|
||||
str = (level - 1) / 2
|
||||
# ultimately get this from user
|
||||
totalStr = (str + weaponStr) / 100
|
||||
strMod = 1 + totalStr
|
||||
exp = value * XP * strMod * obj.priorityValue(priority)
|
||||
return Math.round(exp)
|
||||
# Tier 5 (Blue Moon Rare)
|
||||
if rarity < .1
|
||||
acceptableDrops =
|
||||
['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue',
|
||||
'Golden']
|
||||
|
||||
###
|
||||
Calculates HP modification based on level and armor defence
|
||||
{value} task.value for hp loss
|
||||
{armorDefense} defense from armor
|
||||
{helmDefense} defense from helm
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
obj.hpModifier = (value, armorDef, helmDef, shieldDef, level, priority = '!') ->
|
||||
def = (level - 1) / 2
|
||||
# ultimately get this from user?
|
||||
totalDef = (def + armorDef + helmDef + shieldDef) / 100
|
||||
#ultimate get this from user
|
||||
defMod = 1 - totalDef
|
||||
hp = value * HP * defMod * obj.priorityValue(priority)
|
||||
return Math.round(hp * 10) / 10
|
||||
# round to 1dp
|
||||
# Tier 4 (Very Rare)
|
||||
else if rarity < .2
|
||||
acceptableDrops =
|
||||
['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue']
|
||||
|
||||
###
|
||||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
obj.gpModifier = (value, modifier, priority = '!', streak, user) ->
|
||||
val = value * modifier * obj.priorityValue(priority)
|
||||
if streak and user
|
||||
streakBonus = streak / 100 + 1
|
||||
# eg, 1-day streak is 1.1, 2-day is 1.2, etc
|
||||
afterStreak = val * streakBonus
|
||||
user.streakBonus = afterStreak - val if (val > 0)
|
||||
# can we do this without model? just global emit?
|
||||
return afterStreak
|
||||
else
|
||||
return val
|
||||
# Tier 3 (Rare)
|
||||
else if rarity < .3
|
||||
acceptableDrops = ['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton']
|
||||
|
||||
###
|
||||
Calculates the next task.value based on direction
|
||||
Uses a capped inverse log y=.95^x, y>= -5
|
||||
{currentValue} the current value of the task
|
||||
{direction} up or down
|
||||
###
|
||||
obj.taskDeltaFormula = (currentValue, direction) ->
|
||||
if currentValue < -47.27 then currentValue = -47.27
|
||||
else if currentValue > 21.27 then currentValue = 21.27
|
||||
delta = Math.pow(0.9747, currentValue)
|
||||
return delta if direction is 'up'
|
||||
return -delta
|
||||
|
||||
|
||||
###
|
||||
Drop System
|
||||
###
|
||||
|
||||
randomDrop = (user, delta, priority, streak = 0) ->
|
||||
# limit drops to 2 / day
|
||||
if !user.items.lastDrop?
|
||||
user.items =
|
||||
{
|
||||
date: +moment().subtract('d', 1) # trick - set it to yesterday on first run, that way they can get drops today
|
||||
count: 0
|
||||
}
|
||||
reachedDropLimit = (helpers.daysBetween(user.items.lastDrop.date,
|
||||
+new Date) is 0) and user.items.lastDrop.count >= 2
|
||||
return if reachedDropLimit
|
||||
|
||||
# % chance of getting a pet or meat
|
||||
chanceMultiplier = Math.abs(delta)
|
||||
chanceMultiplier *= obj.priorityValue(priority)
|
||||
# multiply chance by reddness
|
||||
chanceMultiplier += streak
|
||||
# streak bonus
|
||||
console.log chanceMultiplier
|
||||
|
||||
if user.flags.dropsEnabled and Math.random() < (.05 * chanceMultiplier)
|
||||
# current breakdown - 3% (adjustable) chance on drop
|
||||
# If they got a drop: 50% chance of egg, 50% Hatching Potion. If hatchingPotion, broken down further even further
|
||||
rarity = Math.random()
|
||||
|
||||
# Egg, 40% chance
|
||||
if rarity > .6
|
||||
drop = randomVal(pets)
|
||||
user.items.eggs.push drop
|
||||
drop.type = 'Egg'
|
||||
drop.dialog = "You've found a #{drop.text} Egg! #{drop.notes}"
|
||||
|
||||
# Hatching Potion, 60% chance - break down by rarity even more. FIXME this may not be the best method, so revisit
|
||||
# Tier 2 (Scarce)
|
||||
else if rarity < .4
|
||||
acceptableDrops = ['Base', 'White', 'Desert']
|
||||
# Tier 1 (Common)
|
||||
else
|
||||
acceptableDrops = []
|
||||
acceptableDrops = ['Base']
|
||||
|
||||
# Tier 5 (Blue Moon Rare)
|
||||
if rarity < .1
|
||||
acceptableDrops =
|
||||
['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue',
|
||||
'Golden']
|
||||
acceptableDrops = hatchingPotions.filter (hatchingPotion) ->
|
||||
hatchingPotion.name in acceptableDrops
|
||||
drop = randomVal acceptableDrops
|
||||
(user.items.hatchingPotions ?= []).push drop.name
|
||||
drop.type = 'HatchingPotion'
|
||||
drop.dialog = "You've found a #{drop.text} Hatching Potion! #{drop.notes}"
|
||||
|
||||
# Tier 4 (Very Rare)
|
||||
else if rarity < .2
|
||||
acceptableDrops =
|
||||
['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue']
|
||||
(user._tmp?={}).drop = drop
|
||||
#FIXME $('#item-dropped-modal').modal 'show'
|
||||
|
||||
# Tier 3 (Rare)
|
||||
else if rarity < .3
|
||||
acceptableDrops = ['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton']
|
||||
user.items.lastDrop.date = +new Date
|
||||
user.items.lastDrop.count++
|
||||
|
||||
# Tier 2 (Scarce)
|
||||
else if rarity < .4
|
||||
acceptableDrops = ['Base', 'White', 'Desert']
|
||||
# Tier 1 (Common)
|
||||
else
|
||||
acceptableDrops = ['Base']
|
||||
# {task} task you want to score
|
||||
# {direction} 'up' or 'down'
|
||||
obj.score = (user, task, direction, times=1, cron=false) ->
|
||||
{gp, hp, exp, lvl} = user.stats
|
||||
{type, value, streak} = task
|
||||
priority = task.priority or '!'
|
||||
|
||||
acceptableDrops = hatchingPotions.filter (hatchingPotion) ->
|
||||
hatchingPotion.name in acceptableDrops
|
||||
drop = randomVal acceptableDrops
|
||||
user.items.hatchingPotions.push drop.name
|
||||
drop.type = 'HatchingPotion'
|
||||
drop.dialog = "You've found a #{drop.text} Hatching Potion! #{drop.notes}"
|
||||
# If they're trying to purhcase a too-expensive reward, confirm they want to take a hit for it
|
||||
if task.value > user.stats.gp and task.type is 'reward'
|
||||
return unless confirm "Not enough GP to purchase this reward, buy anyway and lose HP? (Punishment for taking a reward you didn't earn)."
|
||||
|
||||
user.drop = drop
|
||||
|
||||
user.items.lastDrop.date = +new Date
|
||||
user.items.lastDrop.count++
|
||||
|
||||
# {task} task you want to score
|
||||
# {direction} 'up' or 'down'
|
||||
obj.score = (user, task, direction) ->
|
||||
{gp, hp, exp, lvl} = user.stats
|
||||
{type, value, streak} = task
|
||||
priority = task.priority or '!'
|
||||
|
||||
# If they're trying to purhcase a too-expensive reward, confirm they want to take a hit for it
|
||||
if task.value > user.stats.gp and task.type is 'reward'
|
||||
r = confirm "Not enough GP to purchase this reward, buy anyway and lose HP? (Punishment for taking a reward you didn't earn)."
|
||||
unless r
|
||||
#TODO if this rule is working OK.
|
||||
return
|
||||
|
||||
delta = 0
|
||||
calculateDelta = (adjustvalue = true) ->
|
||||
delta = 0
|
||||
calculateDelta = (adjustvalue = true) ->
|
||||
# If multiple days have passed, multiply times days missed
|
||||
_.times times, ->
|
||||
# Each iteration calculate the delta (nextDelta), which is then accumulated in delta
|
||||
# (aka, the total delta). This weirdness won't be necessary when calculating mathematically
|
||||
# rather than iteratively
|
||||
|
|
@ -188,194 +174,201 @@
|
|||
value += nextDelta if adjustvalue
|
||||
delta += nextDelta
|
||||
|
||||
addPoints = ->
|
||||
level = user.stats.lvl
|
||||
weaponStrength = items.items.weapon[user.items.weapon].strength
|
||||
exp += obj.expModifier(delta, weaponStrength, level, priority) / 2
|
||||
# / 2 hack for now bcause people leveling too fast
|
||||
if streak
|
||||
gp += obj.gpModifier(delta, 1, priority, streak, user)
|
||||
else
|
||||
gp += obj.gpModifier(delta, 1, priority)
|
||||
addPoints = ->
|
||||
level = user.stats.lvl
|
||||
weaponStrength = items.items.weapon[user.items.weapon].strength
|
||||
exp += obj.expModifier(delta, weaponStrength, level, priority) / 2
|
||||
# / 2 hack for now bcause people leveling too fast
|
||||
if streak
|
||||
gp += obj.gpModifier(delta, 1, priority, streak, user)
|
||||
else
|
||||
gp += obj.gpModifier(delta, 1, priority)
|
||||
|
||||
|
||||
subtractPoints = ->
|
||||
level = user.stats.lvl
|
||||
armorDefense = items.items.armor[user.items.armor].defense
|
||||
helmDefense = items.items.head[user.items.head].defense
|
||||
shieldDefense = items.items.shield[user.items.shield].defense
|
||||
hp += obj.hpModifier(delta, armorDefense, helmDefense, shieldDefense, level, priority)
|
||||
subtractPoints = ->
|
||||
level = user.stats.lvl
|
||||
armorDefense = items.items.armor[user.items.armor].defense
|
||||
helmDefense = items.items.head[user.items.head].defense
|
||||
shieldDefense = items.items.shield[user.items.shield].defense
|
||||
hp += obj.hpModifier(delta, armorDefense, helmDefense, shieldDefense, level, priority)
|
||||
|
||||
switch type
|
||||
when 'habit'
|
||||
switch type
|
||||
when 'habit'
|
||||
calculateDelta()
|
||||
# Add habit value to habit-history (if different)
|
||||
if (delta > 0) then addPoints() else subtractPoints()
|
||||
if task.value != value
|
||||
(task.history ?= []).push { date: +new Date, value: value }
|
||||
|
||||
when 'daily'
|
||||
if cron? # cron
|
||||
calculateDelta()
|
||||
# Add habit value to habit-history (if different)
|
||||
if (delta > 0) then addPoints() else subtractPoints()
|
||||
task.history ?= []
|
||||
if task.value != value
|
||||
historyEntry = { date: +new Date, value: value }
|
||||
task.history.push historyEntry
|
||||
|
||||
when 'daily'
|
||||
subtractPoints()
|
||||
task.streak = 0
|
||||
else
|
||||
calculateDelta(false)
|
||||
if delta != 0
|
||||
addPoints()
|
||||
# obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
if direction is 'up'
|
||||
streak = if streak then streak + 1 else 1
|
||||
else
|
||||
streak = if streak then streak - 1 else 0
|
||||
task.streak = streak
|
||||
|
||||
when 'todo'
|
||||
when 'todo'
|
||||
if cron? #cron
|
||||
calculateDelta()
|
||||
#don't touch stats on cron
|
||||
else
|
||||
calculateDelta()
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
|
||||
when 'reward'
|
||||
# Don't adjust values for rewards
|
||||
calculateDelta(false)
|
||||
# purchase item
|
||||
gp -= Math.abs(task.value)
|
||||
num = parseFloat(task.value).toFixed(2)
|
||||
# if too expensive, reduce health & zero gp
|
||||
if gp < 0
|
||||
hp += gp
|
||||
# hp - gp difference
|
||||
gp = 0
|
||||
when 'reward'
|
||||
# Don't adjust values for rewards
|
||||
calculateDelta(false)
|
||||
# purchase item
|
||||
gp -= Math.abs(task.value)
|
||||
num = parseFloat(task.value).toFixed(2)
|
||||
# if too expensive, reduce health & zero gp
|
||||
if gp < 0
|
||||
hp += gp
|
||||
# hp - gp difference
|
||||
gp = 0
|
||||
|
||||
task.value = value
|
||||
updateStats user, { hp, exp, gp }
|
||||
task.value = value
|
||||
updateStats user, { hp, exp, gp }
|
||||
|
||||
# Drop system
|
||||
# randomDrop(user, delta, priority, streak) if direction is 'up'
|
||||
# Drop system #FIXME
|
||||
randomDrop(user, delta, priority, streak) if direction is 'up'
|
||||
|
||||
return delta
|
||||
return delta
|
||||
|
||||
###
|
||||
Updates user stats with new stats. Handles death, leveling up, etc
|
||||
{stats} new stats
|
||||
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately
|
||||
###
|
||||
updateStats = (user, newStats) ->
|
||||
# if user is dead, dont do anything
|
||||
return if user.stats.hp <= 0
|
||||
###
|
||||
Updates user stats with new stats. Handles death, leveling up, etc
|
||||
{stats} new stats
|
||||
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately
|
||||
###
|
||||
updateStats = (user, newStats) ->
|
||||
# if user is dead, dont do anything
|
||||
return if user.stats.hp <= 0
|
||||
|
||||
if newStats.hp?
|
||||
# Game Over
|
||||
if newStats.hp <= 0
|
||||
user.stats.hp = 0
|
||||
# signifies dead
|
||||
return
|
||||
else
|
||||
user.stats.hp = newStats.hp
|
||||
if newStats.hp?
|
||||
# Game Over
|
||||
if newStats.hp <= 0
|
||||
user.stats.hp = 0 # signifies dead
|
||||
return
|
||||
else
|
||||
user.stats.hp = newStats.hp
|
||||
|
||||
if newStats.exp?
|
||||
tnl = obj.tnl(user.stats.lvl)
|
||||
#silent = false
|
||||
# if we're at level 100, turn xp to gold
|
||||
if user.stats.lvl >= 100
|
||||
newStats.gp += newStats.exp / 15
|
||||
newStats.exp = 0
|
||||
user.stats.lvl = 100
|
||||
else
|
||||
# level up & carry-over exp
|
||||
if newStats.exp >= tnl
|
||||
#silent = true # push through the negative xp silently
|
||||
user.stats.exp = newStats.exp
|
||||
# push normal + notification
|
||||
while newStats.exp >= tnl and user.stats.lvl < 100 # keep levelling up
|
||||
newStats.exp -= tnl
|
||||
user.stats.lvl++
|
||||
tnl = obj.tnl(user.stats.lvl)
|
||||
if user.stats.lvl == 100
|
||||
newStats.exp = 0
|
||||
user.stats.hp = 50
|
||||
if newStats.exp?
|
||||
tnl = obj.tnl(user.stats.lvl)
|
||||
#silent = false
|
||||
# if we're at level 100, turn xp to gold
|
||||
if user.stats.lvl >= 100
|
||||
newStats.gp += newStats.exp / 15
|
||||
newStats.exp = 0
|
||||
user.stats.lvl = 100
|
||||
else
|
||||
# level up & carry-over exp
|
||||
if newStats.exp >= tnl
|
||||
#silent = true # push through the negative xp silently
|
||||
user.stats.exp = newStats.exp # push normal + notification
|
||||
while newStats.exp >= tnl and user.stats.lvl < 100 # keep levelling up
|
||||
newStats.exp -= tnl
|
||||
user.stats.lvl++
|
||||
tnl = obj.tnl(user.stats.lvl)
|
||||
if user.stats.lvl == 100
|
||||
newStats.exp = 0
|
||||
user.stats.hp = 50
|
||||
|
||||
user.stats.exp = newStats.exp
|
||||
#if silent
|
||||
#console.log("pushing silent :" + obj.stats.exp)
|
||||
user.stats.exp = newStats.exp
|
||||
#if silent
|
||||
#console.log("pushing silent :" + obj.stats.exp)
|
||||
|
||||
|
||||
# Set flags when they unlock features
|
||||
if !user.flags.customizationsNotification and (user.stats.exp > 10 or user.stats.lvl > 1)
|
||||
user.flags.customizationsNotification = true
|
||||
if !user.flags.itemsEnabled and user.stats.lvl >= 2
|
||||
user.flags.itemsEnabled = true
|
||||
if !user.flags.partyEnabled and user.stats.lvl >= 3
|
||||
user.flags.partyEnabled = true
|
||||
if !user.flags.dropsEnabled and user.stats.lvl >= 4
|
||||
user.flags.dropsEnabled = true
|
||||
# Set flags when they unlock features
|
||||
if !user.flags.customizationsNotification and (user.stats.exp > 10 or user.stats.lvl > 1)
|
||||
user.flags.customizationsNotification = true
|
||||
if !user.flags.itemsEnabled and user.stats.lvl >= 2
|
||||
user.flags.itemsEnabled = true
|
||||
if !user.flags.partyEnabled and user.stats.lvl >= 3
|
||||
user.flags.partyEnabled = true
|
||||
if !user.flags.dropsEnabled and user.stats.lvl >= 4
|
||||
user.flags.dropsEnabled = true
|
||||
|
||||
if newStats.gp?
|
||||
#FIXME what was I doing here? I can't remember, gp isn't defined
|
||||
gp = 0.0 if (!gp? or gp < 0)
|
||||
user.stats.gp = newStats.gp
|
||||
if newStats.gp?
|
||||
#FIXME what was I doing here? I can't remember, gp isn't defined
|
||||
gp = 0.0 if (!gp? or gp < 0)
|
||||
user.stats.gp = newStats.gp
|
||||
|
||||
###
|
||||
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
||||
For incomplete Dailys, deduct experience
|
||||
Make sure to run this function once in a while as server will not take care of overnight calculations.
|
||||
And you have to run it every time client connects.
|
||||
###
|
||||
obj.cron = (user) ->
|
||||
today = +new Date
|
||||
daysPassed = helpers.daysBetween(user.lastCron, today, user.preferences.dayStart)
|
||||
if daysPassed > 0
|
||||
user.lastCron = today
|
||||
###
|
||||
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
||||
For incomplete Dailys, deduct experience
|
||||
Make sure to run this function once in a while as server will not take care of overnight calculations.
|
||||
And you have to run it every time client connects.
|
||||
###
|
||||
obj.cron = (user) ->
|
||||
today = +new Date
|
||||
|
||||
if user.flags.rest is true
|
||||
user.dailys.forEach (daily) ->
|
||||
daily.completed = false
|
||||
return
|
||||
lastCron = user.lastCron
|
||||
# New user (!lastCron, lastCron==new) or it got busted somehow, maybe they went to a different timezone
|
||||
if !lastCron? or lastCron is 'new' or moment(lastCron).isAfter(today)
|
||||
user.lastCron = +new Date
|
||||
return
|
||||
|
||||
# Tally each task
|
||||
todoTally = 0
|
||||
user.todos.concat(user.dailys).forEach (task) ->
|
||||
{id, type, completed, repeat} = task
|
||||
# Deduct experience for missed Daily tasks,
|
||||
# but not for Todos (just increase todo's value)
|
||||
unless completed
|
||||
# for todos & typical dailies, these are equivalent
|
||||
daysFailed = daysPassed
|
||||
# however, for dailys which have repeat dates, need
|
||||
# to calculate how many they've missed according to their own schedule
|
||||
if type == 'daily' && repeat
|
||||
daysFailed = 0
|
||||
for i in [1..daysPassed] by 1
|
||||
thatDay = moment().subtract('days', i + 1)
|
||||
if repeat[helpers.dayMapping[thatDay.day()]] == true
|
||||
daysFailed++
|
||||
score user, task, 'down'
|
||||
if type == 'daily'
|
||||
daysPassed = helpers.daysBetween(user.lastCron, today, user.preferences?.dayStart)
|
||||
if daysPassed > 0
|
||||
user.lastCron = today
|
||||
|
||||
# User is resting at the inn. Used to be we un-checked each daily without performing calculation (see commits before fb29e35)
|
||||
# but to prevent abusing the inn (http://goo.gl/GDb9x) we now do *not* calculate dailies, and simply set lastCron to today
|
||||
return if user.flags.rest is true
|
||||
|
||||
# Tally each task
|
||||
todoTally = 0
|
||||
user.todos.concat(user.dailys).forEach (task) ->
|
||||
{id, type, completed, repeat} = task
|
||||
# Deduct experience for missed Daily tasks, but not for Todos (just increase todo's value)
|
||||
unless completed
|
||||
scheduleMisses = daysMissed
|
||||
# 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 daysPassed, (n) ->
|
||||
thatDay = moment(today).subtract('days', n + 1)
|
||||
scheduleMisses++ if helpers.shouldDo(thatDay, repeat, obj.preferences?.dayStart) is true
|
||||
#FIXME with times & cron
|
||||
score(user, task, 'down', scheduleMisses, true) if scheduleMisses > 0
|
||||
|
||||
switch type
|
||||
when 'daily'
|
||||
if completed #set OHV for completed dailies
|
||||
task.value = task.value + taskDeltaFormula(task.value, 'up')
|
||||
task.history ?= []
|
||||
task.history.push { date: +new Date, value: task.value }
|
||||
(task.history ?= []).push { date: +new Date, value: task.value }
|
||||
task.completed = false
|
||||
else
|
||||
|
||||
when 'todo'
|
||||
#get updated value
|
||||
absVal = if (completed) then Math.abs(task.value) else task.value
|
||||
todoTally += absVal
|
||||
user.habits.forEach (task) -> # slowly reset 'onlies' value to 0
|
||||
if task.up == false or task.down == false
|
||||
if Math.abs(task.value) < 0.1
|
||||
task.value = 0
|
||||
else
|
||||
task.value = task.value / 2
|
||||
user.habits.forEach (task) -> # slowly reset 'onlies' value to 0
|
||||
if task.up is false or task.down is false
|
||||
if Math.abs(task.value) < 0.1
|
||||
task.value = 0
|
||||
else
|
||||
task.value = task.value / 2
|
||||
|
||||
# Finished tallying
|
||||
user.history ?= {};
|
||||
user.history.todos ?= [];
|
||||
user.history.exp ?= []
|
||||
user.history.todos.push { date: today, value: todoTally }
|
||||
# tally experience
|
||||
expTally = user.stats.exp
|
||||
lvl = 0
|
||||
#iterator
|
||||
while lvl < (user.stats.lvl - 1)
|
||||
lvl++
|
||||
expTally += obj.tnl(lvl)
|
||||
user.history.exp.push { date: today, value: expTally }
|
||||
user
|
||||
# Finished tallying
|
||||
((user.history ?= {}).todos ?= []).push { date: today, value: todoTally }
|
||||
user.history.exp ?= []
|
||||
# tally experience
|
||||
expTally = user.stats.exp
|
||||
lvl = 0
|
||||
#iterator
|
||||
while lvl < (user.stats.lvl - 1)
|
||||
lvl++
|
||||
expTally += obj.tnl(lvl)
|
||||
user.history.exp.push { date: today, value: expTally }
|
||||
user
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,14 +37,16 @@ module.exports =
|
|||
so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where
|
||||
Angular sets object properties directly - in which case, this function will be used.
|
||||
###
|
||||
pathSet: (path, val, obj) ->
|
||||
dotSet: (path, val, obj) ->
|
||||
arr = path.split('.')
|
||||
arr.reduce (curr, next, index) ->
|
||||
_.reduce arr, (curr, next, index) ->
|
||||
if (arr.length - 1) == index
|
||||
curr[next] = val
|
||||
curr[next]
|
||||
, obj
|
||||
|
||||
dotGet: (path, obj) -> _.reduce path.split('.'), ((curr, next) -> curr[next]), obj
|
||||
|
||||
daysBetween: daysBetween
|
||||
|
||||
shouldDo: shouldDo
|
||||
|
|
|
|||
Loading…
Reference in a new issue