diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index 7255682437..b33eaa64bd 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -430,5 +430,13 @@ "worldBossBullet2": "The World Boss won’t damage you for missed tasks, but its Rage meter will go up. If the bar fills up, the Boss will attack one of Habitica’s shopkeepers!", "worldBossBullet3": "You can continue with normal Quest Bosses, damage will apply to both", "worldBossBullet4": "Check the Tavern regularly to see World Boss progress and Rage attacks", - "chatQuestStarted": "Your quest, <%= questName %>, has started." + "chatQuestStarted": "Your quest, <%= questName %>, has started.", + "chatBossDamage": "<%= username %> attacks <%= bossName %> for <%= userDamage %> damage. <%= bossName %> attacks party for <%= bossDamage %> damage.", + "chatBossDontAttack": "<%= bossDamage %> does not attack, because it respects the fact that there are some bugs post-maintenance and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!", + "chatBossDefeated": "You defeated <%= bossName %>! Questing party members receive the rewards of victory.", + "chatFindItems": "<%= username %> found <%= itemName %>.", + "chatItemQuestFinish": "All items found! Party has received their rewards.", + "chatCastSpell": "<%= username %> casts <%= spellName %> <%= targetText %>.", + "chatQuestAborted": "<%= username %> aborted the party quest <%= questName %>.", + "tavernBossTired": "<%= bossName %> tries to unleash ${quest.boss.rage.title('en')} but is too tired." } diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js index fba5277d49..19cf4a53ce 100644 --- a/website/server/controllers/api-v3/chat.js +++ b/website/server/controllers/api-v3/chat.js @@ -74,7 +74,7 @@ api.getChat = { let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'chat'}); if (!group) throw new NotFound(res.t('groupNotFound')); - res.respond(200, Group.toJSONCleanChat(group, user).chat); + res.respond(200, await Group.toJSONCleanChat(group, user).chat); }, }; @@ -194,7 +194,7 @@ api.postChat = { } if (chatUpdated) { - res.respond(200, {chat: Group.toJSONCleanChat(savedGroup, user).chat}); + res.respond(200, {chat: await Group.toJSONCleanChat(savedGroup, user).chat}); } else { res.respond(200, {message: savedGroup.chat[0]}); } @@ -486,7 +486,7 @@ api.deleteChat = { ).exec(); if (chatUpdated) { - let chatRes = Group.toJSONCleanChat(group, user).chat; + let chatRes = await Group.toJSONCleanChat(group, user).chat; removeFromArray(chatRes, {id: chatId}); res.respond(200, chatRes); } else { diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index ae7c8c1234..59cd767e85 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -391,7 +391,7 @@ api.getGroup = { throw new NotFound(res.t('groupNotFound')); } - let groupJson = Group.toJSONCleanChat(group, user); + let groupJson = await Group.toJSONCleanChat(group, user); if (groupJson.leader === user._id) { groupJson.purchased.plan = group.purchased.plan.toObject(); @@ -455,7 +455,7 @@ api.updateGroup = { _.assign(group, _.merge(group.toObject(), Group.sanitizeUpdate(req.body))); let savedGroup = await group.save(); - let response = Group.toJSONCleanChat(savedGroup, user); + let response = await Group.toJSONCleanChat(savedGroup, user); // If the leader changed fetch new data, otherwise use authenticated user if (response.leader !== user._id) { @@ -619,7 +619,7 @@ api.joinGroup = { promises = await Bluebird.all(promises); - let response = Group.toJSONCleanChat(promises[0], user); + let response = await Group.toJSONCleanChat(promises[0], user); let leader = await User.findById(response.leader).select(nameFields).exec(); if (leader) { response.leader = leader.toJSON({minimize: true}); diff --git a/website/server/models/group.js b/website/server/models/group.js index d0dab7eef7..38dd1e3d1d 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -317,7 +317,8 @@ schema.statics.getGroups = async function getGroups (options = {}) { return groupsArray; }; -function translateSystemMessages(group, user) { +async function translateSystemMessages(group, user) { + let usernames = {}; for (let i = 0; i < group.chat.length; i++) { if (_.isEmpty(group.chat[i].info)) { continue; @@ -326,6 +327,42 @@ function translateSystemMessages(group, user) { case 'quest_start': group.chat[i].text = '`' + shared.i18n.t('chatQuestStarted', {'questName': questScrolls[group.chat[i].info.quest].text(user.preferences.language)}, user.preferences.language) + '`'; break; + case 'boss_damage': + if (!_.has(usernames, group.chat[i].info.user)) { + let user = await User + .findById(group.chat[i].info.user) + .select(nameFields) + .exec(); + usernames[group.chat[i].info.user] = user.profile.name; + } + group.chat[i].text = '`' + shared.i18n.t('chatBossDamage', {'username': usernames[group.chat[i].info.user], 'bossName': questScrolls[group.chat[i].info.quest].boss.name(user.preferences.language), 'userDamage': group.chat[i].info.userDamage, 'bossDamage': group.chat[i].info.bossDamage}, user.preferences.language) + '`'; + break; + case 'boss_dont_attack': + break; + case 'boss_rage': + group.chat[i].text = '`' + questScrolls[group.chat[i].info.quest].boss.rage.effect(user.preferences.language) + '`'; + break; + case 'boss_defeated': + break; + case 'user_found_items': + break; + case 'all_items_found': + break; + case 'spell_cast': + break; + case 'quest_abort': + break; + case 'tavern_quest_completed': + group.chat[i].text = '`' + questScrolls[group.chat[i].info.quest].completionChat(user.preferences.language) + '`'; + break; + case 'tavern_boss_rage_tired': + break; + case 'tavern_boss_rage': + group.chat[i].text = '`' + questScrolls[group.chat[i].info.quest].boss.rage[group.chat[i].info.scene](user.preferences.language) + '`'; + break; + case 'tavern_boss_desperation': + group.chat[i].text = '`' + questScrolls[group.chat[i].info.quest].boss.desperation.text(user.preferences.language) + '`'; + break; } } return group; @@ -335,8 +372,8 @@ function translateSystemMessages(group, user) { // unless the user is an admin or said chat is posted by that user // Not putting into toJSON because there we can't access user // It also removes the _meta field that can be stored inside a chat message -schema.statics.toJSONCleanChat = function groupToJSONCleanChat (group, user) { - group = translateSystemMessages(group, user); +schema.statics.toJSONCleanChat = async function groupToJSONCleanChat (group, user) { + group = await translateSystemMessages(group, user); let toJSON = group.toJSON(); @@ -907,8 +944,16 @@ schema.methods._processBossQuest = async function processBossQuest (options) { // TODO Create a party preferred language option so emits like this can be localized. Suggestion: Always display the English version too. Or, if English is not displayed to the players, at least include it in a new field in the chat object that's visible in the database - essential for admins when troubleshooting quests! let playerAttack = `${user.profile.name} attacks ${quest.boss.name('en')} for ${progress.up.toFixed(1)} damage.`; let bossAttack = CRON_SAFE_MODE || CRON_SEMI_SAFE_MODE ? `${quest.boss.name('en')} does not attack, because it respects the fact that there are some bugs\` \`post-maintenance and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`; - // TODO Consider putting the safe mode boss attack message in an ENV var - group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``); + if (CRON_SAFE_MODE || CRON_SEMI_SAFE_MODE) { + } else { + group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``, null, null, { + 'type': 'boss_damage', + 'user': user._id, + 'quest': group.quest.key, + 'userDamage': progress.up.toFixed(1), + 'bossDamage': Math.abs(down).toFixed(1), + }); + } // If boss has Rage, increment Rage as well if (quest.boss.rage) {