2013-05-15 04:51:21 +00:00
|
|
|
({ define: (
|
|
|
|
|
if typeof define == "function"
|
|
|
|
|
define
|
|
|
|
|
else
|
|
|
|
|
(F)->
|
|
|
|
|
F(require, exports, module)
|
|
|
|
|
)}).define (require, exports, module)->
|
2013-05-15 06:17:53 +00:00
|
|
|
helpers = require('./helpers')
|
|
|
|
|
moment = require('./moment')
|
2013-05-15 13:59:01 +00:00
|
|
|
items = require('./items')
|
2013-05-15 04:51:21 +00:00
|
|
|
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
|
|
|
|
|
value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10
|
|
|
|
|
# round to nearest 10
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
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.streakBonus = afterStreak - val if (val > 0)
|
|
|
|
|
# can we do this without model? just global emit?
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
else
|
|
|
|
|
acceptableDrops = []
|
|
|
|
|
|
|
|
|
|
# Tier 5 (Blue Moon Rare)
|
|
|
|
|
if rarity < .1
|
|
|
|
|
acceptableDrops =
|
|
|
|
|
['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue',
|
|
|
|
|
'Golden']
|
|
|
|
|
|
|
|
|
|
# Tier 4 (Very Rare)
|
|
|
|
|
else if rarity < .2
|
|
|
|
|
acceptableDrops =
|
|
|
|
|
['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue']
|
|
|
|
|
|
|
|
|
|
# Tier 3 (Rare)
|
|
|
|
|
else if rarity < .3
|
|
|
|
|
acceptableDrops = ['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton']
|
|
|
|
|
|
|
|
|
|
# Tier 2 (Scarce)
|
|
|
|
|
else if rarity < .4
|
|
|
|
|
acceptableDrops = ['Base', 'White', 'Desert']
|
|
|
|
|
# Tier 1 (Common)
|
|
|
|
|
else
|
|
|
|
|
acceptableDrops = ['Base']
|
|
|
|
|
|
|
|
|
|
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}"
|
|
|
|
|
|
|
|
|
|
user.drop = drop
|
|
|
|
|
|
|
|
|
|
user.items.lastDrop.date = +new Date
|
|
|
|
|
user.items.lastDrop.count++
|
|
|
|
|
|
|
|
|
|
# {task} task you want to score
|
|
|
|
|
# {direction} 'up' or 'down'
|
2013-05-15 13:59:01 +00:00
|
|
|
obj.score = (user, task, direction) ->
|
2013-05-15 04:51:21 +00:00
|
|
|
{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) ->
|
|
|
|
|
# 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
|
|
|
|
|
nextDelta = obj.taskDeltaFormula(value, direction)
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'
|
|
|
|
|
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'
|
|
|
|
|
calculateDelta(false)
|
|
|
|
|
if delta != 0
|
|
|
|
|
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'
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
task.value = value
|
|
|
|
|
updateStats user, { hp, exp, gp }
|
|
|
|
|
|
|
|
|
|
# Drop system
|
|
|
|
|
# randomDrop(user, delta, priority, streak) if direction is 'up'
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
if user.flags.rest is true
|
|
|
|
|
user.dailys.forEach (daily) ->
|
|
|
|
|
daily.completed = false
|
|
|
|
|
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'
|
|
|
|
|
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.completed = false
|
|
|
|
|
else
|
|
|
|
|
#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
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
|