2013-08-31 21:47:03 +00:00
var mongoose = require ( "mongoose" ) ;
var Schema = mongoose . Schema ;
2013-12-11 16:30:39 +00:00
var shared = require ( 'habitrpg-shared' ) ;
2013-08-31 21:47:03 +00:00
var _ = require ( 'lodash' ) ;
2013-12-20 22:43:10 +00:00
var async = require ( 'async' ) ;
2013-12-23 19:43:53 +00:00
var User = require ( './user' ) . model ;
2013-08-29 05:45:19 +00:00
2013-08-31 21:47:03 +00:00
var GroupSchema = new Schema ( {
2013-12-11 16:30:39 +00:00
_id : { type : String , 'default' : shared . uuid } ,
2013-08-29 05:45:19 +00:00
name : String ,
description : String ,
2013-10-28 05:27:07 +00:00
leader : { type : String , ref : 'User' } ,
members : [ { type : String , ref : 'User' } ] ,
invites : [ { type : String , ref : 'User' } ] ,
type : { type : String , "enum" : [ 'guild' , 'party' ] } ,
privacy : { type : String , "enum" : [ 'private' , 'public' ] } ,
2013-11-15 04:29:48 +00:00
//_v: {type: Number,'default': 0},
2013-08-29 05:45:19 +00:00
chat : Array ,
/ *
# [ {
# timestamp : Date
# user : String
# text : String
# contributor : String
# uuid : String
# id : String
# } ]
* /
2013-10-29 00:31:27 +00:00
memberCount : { type : Number , 'default' : 0 } ,
challengeCount : { type : Number , 'default' : 0 } ,
2013-08-29 05:45:19 +00:00
balance : Number ,
logo : String ,
2013-10-27 00:24:45 +00:00
leaderMessage : String ,
2013-12-20 19:42:36 +00:00
challenges : [ { type : 'String' , ref : 'Challenge' } ] , // do we need this? could depend on back-ref instead (Challenge.find({group:GID}))
quest : {
key : String ,
active : { type : Boolean , 'default' : false } ,
2013-12-20 22:43:10 +00:00
progress : {
hp : Number ,
2013-12-24 17:25:09 +00:00
collect : { type : Schema . Types . Mixed , 'default' : { } } // {feather: 5, ingot: 3}
2013-12-20 22:43:10 +00:00
} ,
2013-12-20 19:42:36 +00:00
2013-12-20 22:43:10 +00:00
//Shows boolean for each party-member who has accepted the quest. Eg {UUID: true, UUID: false}. Once all users click
//'Accept', the quest begins. If a false user waits too long, probably a good sign to prod them or boot them.
//TODO when booting user, remove from .joined and check again if we can now start the quest
2013-12-20 19:42:36 +00:00
members : Schema . Types . Mixed
}
2013-10-27 09:55:05 +00:00
} , {
2013-10-27 00:24:45 +00:00
strict : 'throw' ,
2013-10-27 09:55:43 +00:00
minimize : false // So empty objects are returned
2013-10-27 09:55:05 +00:00
} ) ;
2013-08-29 05:45:19 +00:00
2013-08-31 21:47:03 +00:00
/ * *
* Derby duplicated stuff . This is a temporary solution , once we 're completely off derby we' ll run an mongo migration
* to remove duplicates , then take these fucntions out
* /
function removeDuplicates ( doc ) {
// Remove duplicate members
if ( doc . members ) {
var uniqMembers = _ . uniq ( doc . members ) ;
if ( uniqMembers . length != doc . members . length ) {
doc . members = uniqMembers ;
}
}
}
2013-10-30 03:37:22 +00:00
// FIXME this isn't always triggered, since we sometimes use update() or findByIdAndUpdate()
// @see https://github.com/LearnBoost/mongoose/issues/964
2013-08-31 21:47:03 +00:00
GroupSchema . pre ( 'save' , function ( next ) {
removeDuplicates ( this ) ;
2013-10-29 00:31:27 +00:00
this . memberCount = _ . size ( this . members ) ;
this . challengeCount = _ . size ( this . challenges ) ;
2013-08-31 21:47:03 +00:00
next ( ) ;
} )
GroupSchema . methods . toJSON = function ( ) {
var doc = this . toObject ( ) ;
removeDuplicates ( doc ) ;
2013-10-29 19:26:13 +00:00
doc . _isMember = this . _isMember ;
2013-10-30 03:37:22 +00:00
// @see pre('save') comment above
this . memberCount = _ . size ( this . members ) ;
this . challengeCount = _ . size ( this . challenges ) ;
2013-08-31 21:47:03 +00:00
return doc ;
}
2013-12-20 22:43:10 +00:00
GroupSchema . methods . sendChat = function ( message , user ) {
var group = this ;
var message = {
id : shared . uuid ( ) ,
text : message ,
timestamp : + ( new Date )
} ;
if ( user ) {
_ . defaults ( message , {
uuid : user . _id ,
contributor : user . contributor && user . contributor . toObject ( ) ,
backer : user . backer && user . backer . toObject ( ) ,
user : user . profile . name ,
} ) ;
} else {
message . uuid = 'system' ;
}
group . chat . unshift ( message ) ;
group . chat . splice ( 200 ) ;
}
2013-12-24 17:25:09 +00:00
// Participants: Grant rewards & achievements, finish quest
GroupSchema . methods . finishQuest = function ( quest , cb ) {
2013-12-20 22:43:10 +00:00
var group = this ;
2013-12-24 17:25:09 +00:00
var questK = quest . key ;
2013-12-23 19:43:53 +00:00
var dropK = quest . drop . key ;
2013-12-24 17:25:09 +00:00
var updates = { $inc : { } , $set : { } } ;
2013-12-25 06:30:48 +00:00
updates [ '$inc' ] [ 'achievements.quests.' + questK ] = 1 ;
2013-12-24 17:25:09 +00:00
updates [ '$inc' ] [ 'stats.gp' ] = + quest . drop . gp ;
updates [ '$inc' ] [ 'stats.exp' ] = + quest . drop . exp ;
updates [ '$inc' ] [ '_v' ] = 1 ;
updates [ '$unset' ] = { 'party.quest.key' : undefined } ;
updates [ '$set' ] [ 'party.quest.collect' ] = { } ;
2013-12-25 06:30:48 +00:00
updates [ '$set' ] [ 'party.quest.completed' ] = questK ;
2013-12-24 17:25:09 +00:00
switch ( quest . drop . type ) {
case 'gear' :
// TODO This means they can lose their new gear on death, is that what we want?
updates [ '$set' ] [ 'items.gear.owned.' + dropK ] = true ;
break ;
case 'eggs' :
case 'food' :
case 'hatchingPotions' :
updates [ '$inc' ] [ 'items.' + quest . drop . type + '.' + dropK ] = 1 ;
break ;
case 'pets' :
updates [ '$set' ] [ 'items.pets.' + dropK ] = 5 ;
break ;
case 'mounts' :
updates [ '$set' ] [ 'items.mounts.' + dropK ] = true ;
break ;
}
// FIXME this is TERRIBLE practice. Looks like there are circular dependencies in the models, such that `var User` at
// this point is undefined. So we get around that by loading from mongoose only once we get to this point
var members = _ . keys ( group . quest . members ) ;
group . quest = { } ; group . markModified ( 'quest' ) ;
mongoose . models . User . update ( { _id : { $in : members } } , updates , { multi : true } , cb ) ;
}
2013-12-25 04:57:56 +00:00
GroupSchema . methods . collectQuest = function ( user , progress , cb ) {
2013-12-24 17:25:09 +00:00
var group = this ,
quest = shared . content . quests [ group . quest . key ] ;
2013-12-25 04:57:56 +00:00
_ . each ( progress . collect , function ( v , k ) {
2013-12-24 17:25:09 +00:00
group . quest . progress . collect [ k ] += v ;
} ) ;
2013-12-25 04:57:56 +00:00
var foundText = _ . reduce ( progress . collect , function ( m , v , k ) {
2013-12-24 17:25:09 +00:00
m . push ( v + ' ' + quest . collect [ k ] . text ) ;
return m ;
} , [ ] ) ;
foundText = foundText ? foundText . join ( ', ' ) : 'nothing' ;
group . sendChat ( "`<" + user . profile . name + "> found " + foundText + ".`" ) ;
group . markModified ( 'quest.progress.collect' ) ;
// Still needs completing
if ( _ . find ( shared . content . quests [ group . quest . key ] . collect , function ( v , k ) {
return group . quest . progress . collect [ k ] < v . count ;
} ) ) return group . save ( cb ) ;
async . series ( [
function ( cb2 ) {
group . finishQuest ( quest , cb2 ) ;
} ,
function ( cb2 ) {
group . sendChat ( '`All items found! Party has received their rewards.`' ) ;
group . save ( cb2 ) ;
}
] , cb ) ;
}
2013-12-25 04:57:56 +00:00
GroupSchema . methods . bossQuest = function ( user , progress , cb ) {
2013-12-24 17:25:09 +00:00
var group = this ;
var quest = shared . content . quests [ group . quest . key ] ;
2013-12-26 06:13:36 +00:00
if ( ! progress ) progress = { down : 0 , up : 0 } ; // FIXME why is this ever happening, progress should be defined at this point
2013-12-25 04:57:56 +00:00
var down = progress . down * quest . boss . str ; // multiply by boss strength
2013-12-23 19:43:53 +00:00
2013-12-25 04:57:56 +00:00
group . quest . progress . hp -= progress . up ;
group . sendChat ( "`<" + user . profile . name + "> attacks <" + quest . boss . name + "> for " + ( progress . up . toFixed ( 1 ) ) + " damage, <" + quest . boss . name + "> attacks party for " + ( down . toFixed ( 1 ) ) + " damage.`" ) ;
2013-12-23 19:43:53 +00:00
//var hp = group.quest.progress.hp;
// Everyone takes damage
var series = [
function ( cb2 ) {
mongoose . models . User . update ( { _id : { $in : _ . keys ( group . quest . members ) } } , { $inc : { 'stats.hp' : down , _v : 1 } } , { multi : true } , cb2 ) ;
}
]
// Boss slain, finish quest
2013-12-20 22:43:10 +00:00
if ( group . quest . progress . hp <= 0 ) {
2013-12-24 17:25:09 +00:00
group . sendChat ( '`' + quest . boss . name + ' has been slain! Party has received their rewards.`' ) ;
// Participants: Grant rewards & achievements, finish quest
2013-12-23 19:43:53 +00:00
series . push ( function ( cb2 ) {
2013-12-24 17:25:09 +00:00
group . finishQuest ( quest , cb2 ) ;
} ) ;
2013-12-20 22:43:10 +00:00
}
2013-12-23 19:43:53 +00:00
series . push ( function ( cb2 ) { group . save ( cb2 ) } ) ;
async . series ( series , cb ) ;
//return hp;
2013-12-20 22:43:10 +00:00
}
2013-08-31 21:47:03 +00:00
module . exports . schema = GroupSchema ;
2013-11-14 04:20:33 +00:00
module . exports . model = mongoose . model ( "Group" , GroupSchema ) ;