mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
153 lines
5.7 KiB
CoffeeScript
153 lines
5.7 KiB
CoffeeScript
_ = require 'lodash'
|
|
algos = require 'habitrpg-shared/script/algos'
|
|
items = require('habitrpg-shared/script/items').items
|
|
helpers = require('habitrpg-shared/script/helpers')
|
|
|
|
#TODO put this in habitrpg-shared
|
|
###
|
|
We can't always use refLists, but we often still need to get a positional path by id: eg, users.1234.tasks.5678.value
|
|
For arrays (which use indexes, not id-paths), here's a helper function so we can run indexedPath('users',:user.id,'tasks',:task.id,'value)
|
|
###
|
|
indexedPath = ->
|
|
_.reduce arguments, (m,v) =>
|
|
return v if !m #first iteration
|
|
return "#{m}.#{v}" if _.isString v #string paths
|
|
return "#{m}." + _.findIndex(@model.get(m),v)
|
|
, ''
|
|
|
|
taskInChallenge = (task) ->
|
|
return undefined unless task?.challenge
|
|
@model.at indexedPath.call(@, "groups.#{task.group.id}.challenges", {id:task.challenge}, "#{task.type}s", {id:task.id})
|
|
|
|
###
|
|
algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the
|
|
object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first
|
|
clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination),
|
|
perform the updates while tracking paths, then all the values at those paths
|
|
###
|
|
module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
|
drop = undefined
|
|
delta = batchTxn model, (uObj, paths) ->
|
|
tObj = uObj.tasks[taskId]
|
|
|
|
# Stuff for undo
|
|
if allowUndo
|
|
tObjBefore = _.cloneDeep tObj
|
|
tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo']
|
|
previousUndo = model.get('_undo')
|
|
clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId
|
|
timeoutId = setTimeout (-> model.del('_undo')), 20000
|
|
model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId}
|
|
|
|
delta = algos.score(uObj, tObj, direction, {paths})
|
|
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
|
|
drop = uObj._tmp?.drop
|
|
|
|
# Update challenge statistics
|
|
# FIXME put this in it's own batchTxn, make batchTxn model.at() ref aware (not just _user)
|
|
# FIXME use reflists for users & challenges
|
|
if (chalTask = taskInChallenge.call({model}, tObj)) and chalTask?.get()
|
|
model._dontPersist = false
|
|
chalTask.incr "value", delta
|
|
chal = model.at indexedPath.call({model}, "groups.#{tObj.group.id}.challenges", {id:tObj.challenge})
|
|
chalUser = -> indexedPath.call({model}, chal.path(), 'users', {id:uObj.id})
|
|
cu = model.at chalUser()
|
|
unless cu?.get()
|
|
chal.push "users", {id: uObj.id, name: helpers.username(uObj.auth, uObj.profile?.name)}
|
|
cu = model.at chalUser()
|
|
else
|
|
cu.set 'name', helpers.username(uObj.auth, uObj.profile?.name) # update their name incase it changed
|
|
cu.set "#{tObj.type}s.#{tObj.id}",
|
|
value: tObj.value
|
|
history: tObj.history
|
|
model._dontPersist = true
|
|
, done:->
|
|
if drop and $?
|
|
model.set '_drop', drop
|
|
$('#item-dropped-modal').modal 'show'
|
|
|
|
delta
|
|
|
|
###
|
|
Cleanup task-corruption (null tasks, rogue/invisible tasks, etc)
|
|
Obviously none of this should be happening, but we'll stop-gap until we can find & fix
|
|
Gotta love refLists! see https://github.com/lefnire/habitrpg/issues/803 & https://github.com/lefnire/habitrpg/issues/6343
|
|
###
|
|
module.exports.fixCorruptUser = (model) ->
|
|
user = model.at('_user')
|
|
tasks = user.get('tasks')
|
|
|
|
## Remove corrupted tasks
|
|
_.each tasks, (task, key) ->
|
|
unless task?.id? and task?.type?
|
|
user.del("tasks.#{key}")
|
|
delete tasks[key]
|
|
true
|
|
resetDom = false
|
|
batchTxn model, (uObj, paths, batch) ->
|
|
## fix https://github.com/lefnire/habitrpg/issues/1086
|
|
uniqPets = _.uniq(uObj.items.pets)
|
|
batch.set('items.pets', uniqPets) if !_.isEqual(uniqPets, uObj.items.pets)
|
|
|
|
if uObj.invitations?.guilds
|
|
uniqInvites = _.uniq(uObj.invitations.guilds)
|
|
batch.set('invitations.guilds', uniqInvites) if !_.isEqual(uniqInvites, uObj.invitations.guilds)
|
|
|
|
## Task List Cleanup
|
|
['habit','daily','todo','reward'].forEach (type) ->
|
|
|
|
# 1. remove duplicates
|
|
# 2. restore missing zombie tasks back into list
|
|
idList = uObj["#{type}Ids"]
|
|
taskIds = _.pluck( _.where(tasks, {type}), 'id')
|
|
union = _.union idList, taskIds
|
|
|
|
# 2. remove empty (grey) tasks
|
|
preened = _.filter union, (id) -> id and _.contains(taskIds, id)
|
|
|
|
# There were indeed issues found, set the new list
|
|
if !_.isEqual(idList, preened)
|
|
batch.set("#{type}Ids", preened)
|
|
console.error uObj.id + "'s #{type}s were corrupt."
|
|
true
|
|
resetDom = !_.isEmpty(paths)
|
|
require('./browser').resetDom(model) if resetDom
|
|
|
|
module.exports.viewHelpers = (view) ->
|
|
|
|
#misc
|
|
view.fn "percent", (x, y) ->
|
|
x=1 if x==0
|
|
Math.round(x/y*100)
|
|
view.fn 'indexOf', (str1, str2) ->
|
|
return false unless str1 && str2
|
|
str1.indexOf(str2) != -1
|
|
view.fn "round", Math.round
|
|
view.fn "floor", Math.floor
|
|
view.fn "ceil", Math.ceil
|
|
view.fn "truarr", (num) -> num-1
|
|
view.fn 'count', (arr) -> arr?.length or 0
|
|
view.fn 'int',
|
|
get: (num) -> num
|
|
set: (num) -> [parseInt(num)]
|
|
view.fn 'indexedPath', indexedPath
|
|
|
|
|
|
#iCal
|
|
view.fn "encodeiCalLink", helpers.encodeiCalLink
|
|
|
|
#User
|
|
view.fn "gems", (balance) -> balance * 4
|
|
|
|
#Challenges
|
|
view.fn 'taskInChallenge', (task) ->
|
|
taskInChallenge.call(@,task)?.get()
|
|
view.fn 'taskAttrFromChallenge', (task, attr) ->
|
|
taskInChallenge.call(@,task)?.get(attr)
|
|
view.fn 'brokenChallengeLink', (task) ->
|
|
task?.challenge and !(taskInChallenge.call(@,task)?.get())
|
|
|
|
view.fn 'challengeMemberScore', (member, tType, tid) ->
|
|
Math.round(member["#{tType}s"]?[tid]?.value)
|
|
|