habitica/src/models/group.js

180 lines
5.8 KiB
JavaScript
Raw Normal View History

var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var shared = require('habitrpg-shared');
var _ = require('lodash');
var async = require('async');
var User = require('./user').model;
var GroupSchema = new Schema({
_id: {type: String, 'default': shared.uuid},
name: String,
description: String,
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']},
//_v: {type: Number,'default': 0},
chat: Array,
/*
# [{
# timestamp: Date
# user: String
# text: String
# contributor: String
# uuid: String
# id: String
# }]
*/
memberCount: {type: Number, 'default': 0},
challengeCount: {type: Number, 'default': 0},
balance: Number,
logo: String,
leaderMessage: String,
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},
progress:{
hp: Number,
collected: Schema.Types.Mixed,
},
//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
members: Schema.Types.Mixed
}
}, {
strict: 'throw',
2013-10-27 09:55:43 +00:00
minimize: false // So empty objects are returned
});
/**
* 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
GroupSchema.pre('save', function(next){
removeDuplicates(this);
this.memberCount = _.size(this.members);
this.challengeCount = _.size(this.challenges);
next();
})
GroupSchema.methods.toJSON = function(){
var doc = this.toObject();
removeDuplicates(doc);
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);
return doc;
}
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);
}
GroupSchema.methods.bossAttack = function(user, tally, cb) {
var group = this;
var questK = group.quest.key;
var quest = shared.content.quests[questK];
var dropK = quest.drop.key;
var down = tally.down * quest.stats.str; // multiply by boss strength
group.quest.progress.hp -= tally.up;
group.sendChat("`<" + user.profile.name + "> attacks <" + quest.name + "> for " + (tally.up.toFixed(1)) + " damage, <" + quest.name + "> attacks party for " + (down.toFixed(1)) + " damage.`");
//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
if (group.quest.progress.hp <= 0) {
series.push(function(cb2){
async.parallel([
// Participants: Grant rewards & achievements, finish quest
function(cb3){
var updates = {$inc:{},$set:{}};
updates['$inc']['achievements.quests.'+questK] = 1;
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.collection'] = {};
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
mongoose.models.User.update({_id:{$in: _.keys(group.quest.members)}},updates,{multi:true},cb3);
},
// Group: finish quest
function(cb3){
group.quest = {};group.markModified('quest');
group.sendChat('`' + quest.name + ' has been slain! Party has received their rewards`');
group.save(cb3);
}
],cb2);
})
}
series.push(function(cb2){group.save(cb2)});
async.series(series,cb);
//return hp;
}
module.exports.schema = GroupSchema;
2013-11-14 04:20:33 +00:00
module.exports.model = mongoose.model("Group", GroupSchema);