0817cf96e1
* pull apart achievements into different subcategories
* achievs previously hidden to others if unachieved are now always shown
* achievs previously always hidden if unachieved are now always shown
* pull apart ultimate gear achievs
* add achiev wrapper mixin
* add achiev mixin for simple counts
* add achiev mixin for singular/plural achievs
* add simpleAchiev mixin and support attributes
* always hide potentially unearnable achievs if unearned
* contributor achiev now uses string interpolation for readMore link
* transition to basic achiev grid layout
* fix npc achievement img bug introduced in c90f7e2
* move surveys and contributor achievs into special section so it is never empty
* double size of achievs in achievs grid
* achievs in grid are muted if unachieved (includes recompiled sprites)
* fix streak notification strings
* add counts to achievement badges for applicable achieved achievs
* list achievements by api
* fix achievement strings in new api
* unearned achievs now use dedicated (WIP) 'unearned' badge instead of muted versions of the normal badges
* fix & cleanup achievements api
* extract generation of the achievements result to a class
* clean up achievement counter css using existing classes
* simplify exports of new achievementBuilder lib
* remove class logic from achievementBuilder lib
* move achievs to common, add rebirth achiev logic, misc fixes
* replace achievs jade logic with results of api call
* fix linting errors
* achievs lib now returns achievs object subdivided by type (basic/seasonal/special
* add tests for new achievs lib
* fix linting errors
* update controllers and views for updated achievs lib
* add indices to achievements to preserve intended order
* move achiev popovers to left
* rename achievs lib to achievements
* adjust positioning of achieve popovers now that stats and achievs pages
are separate
* fix: achievements api correctly decides whether to append extra string for master and triadBingo achievs
* revert compiled sprites so they don't bog down the PR
* pull out achievs api integration tests
* parameterize ultimate gear achievements' text string
* break out static achievement data from user-specific data
* reorg content.achievements to add achiev data in related chunks
* cleanup, respond to feedback
* improve api documentation
* fix merge issues
* Helped Habit Grow --> Helped Habitica Grow
* achievement popovers are muted if the achiev is unearned
* fix singular achievement labels / achievement popover on click
* update apidoc for achievements (description, param-type, successExample, error-types)
* fix whitespace issues in members.js
* move html to a variable
* updated json example
* fix syntax after merge
307 lines
8.4 KiB
JavaScript
307 lines
8.4 KiB
JavaScript
import content from '../content/index';
|
|
import i18n from '../i18n';
|
|
import { get } from 'lodash';
|
|
|
|
let achievs = {};
|
|
let achievsContent = content.achievements;
|
|
let index = 0;
|
|
|
|
function contribText (contrib, backer, language) {
|
|
if (!contrib && !backer) return;
|
|
if (backer && backer.npc) return backer.npc;
|
|
let lvl = contrib && contrib.level;
|
|
if (lvl && lvl > 0) {
|
|
let contribTitle = '';
|
|
|
|
if (lvl < 3) {
|
|
contribTitle = i18n.t('friend', language);
|
|
} else if (lvl < 5) {
|
|
contribTitle = i18n.t('elite', language);
|
|
} else if (lvl < 7) {
|
|
contribTitle = i18n.t('champion', language);
|
|
} else if (lvl < 8) {
|
|
contribTitle = i18n.t('legendary', language);
|
|
} else if (lvl < 9) {
|
|
contribTitle = i18n.t('guardian', language);
|
|
} else {
|
|
contribTitle = i18n.t('heroic', language);
|
|
}
|
|
|
|
return `${contribTitle} ${contrib.text}`;
|
|
}
|
|
}
|
|
|
|
function _add (result, data) {
|
|
result[data.key] = {
|
|
title: data.title,
|
|
text: data.text,
|
|
icon: data.icon,
|
|
earned: data.earned,
|
|
value: data.value,
|
|
index: index++,
|
|
optionalCount: data.optionalCount,
|
|
};
|
|
}
|
|
|
|
function _addSimpleWithCustomPath (result, user, data) {
|
|
let value = get(user, data.path);
|
|
let thisContent = achievsContent[data.key];
|
|
|
|
_add(result, {
|
|
title: i18n.t(thisContent.titleKey, {key: value}, data.language),
|
|
text: i18n.t(thisContent.textKey, data.language),
|
|
icon: thisContent.icon,
|
|
key: data.key,
|
|
value,
|
|
earned: Boolean(value),
|
|
});
|
|
}
|
|
|
|
function _addQuest (result, user, data) {
|
|
data.key = `${data.path}Quest`;
|
|
data.path = `achievements.quests.${data.path}`;
|
|
_addSimpleWithCustomPath(result, user, data);
|
|
}
|
|
|
|
function _addSimple (result, user, data) {
|
|
let value = user.achievements[data.path];
|
|
|
|
let key = data.key || data.path;
|
|
let thisContent = achievsContent[key];
|
|
|
|
_add(result, {
|
|
title: i18n.t(thisContent.titleKey, data.language),
|
|
text: i18n.t(thisContent.textKey, data.language),
|
|
icon: thisContent.icon,
|
|
key,
|
|
value,
|
|
earned: Boolean(value),
|
|
});
|
|
}
|
|
|
|
function _addSimpleWithMasterCount (result, user, data) {
|
|
let language = data.language;
|
|
let value = user.achievements[`${data.path}Count`] || 0;
|
|
|
|
let thisContent = achievsContent[data.path];
|
|
|
|
let text = i18n.t(thisContent.textKey, language);
|
|
if (value > 0) {
|
|
text += i18n.t(thisContent.text2Key, {count: value}, language);
|
|
}
|
|
|
|
_add(result, {
|
|
title: i18n.t(thisContent.titleKey, language),
|
|
text,
|
|
icon: thisContent.icon,
|
|
key: data.path,
|
|
value,
|
|
optionalCount: value,
|
|
earned: Boolean(value),
|
|
});
|
|
}
|
|
|
|
function _addSimpleWithCount (result, user, data) {
|
|
let value = user.achievements[data.path] || 0;
|
|
|
|
let key = data.key || data.path;
|
|
let thisContent = achievsContent[key];
|
|
|
|
_add(result, {
|
|
title: i18n.t(thisContent.titleKey, data.language),
|
|
text: i18n.t(thisContent.textKey, {count: value}, data.language),
|
|
icon: thisContent.icon,
|
|
key,
|
|
value,
|
|
optionalCount: value,
|
|
earned: Boolean(value),
|
|
});
|
|
}
|
|
|
|
function _addPlural (result, user, data) {
|
|
let value = user.achievements[data.path] || 0;
|
|
|
|
let key = data.key || data.path;
|
|
let thisContent = achievsContent[key];
|
|
|
|
let titleKey;
|
|
let textKey;
|
|
// If value === 1, use singular versions of strings.
|
|
// If value !== 1, use plural versions of strings.
|
|
if (value === 1) {
|
|
titleKey = thisContent.singularTitleKey;
|
|
textKey = thisContent.singularTextKey;
|
|
} else {
|
|
titleKey = thisContent.pluralTitleKey;
|
|
textKey = thisContent.pluralTextKey;
|
|
}
|
|
|
|
_add(result, {
|
|
title: i18n.t(titleKey, {count: value}, data.language),
|
|
text: i18n.t(textKey, {count: value}, data.language),
|
|
icon: thisContent.icon,
|
|
key,
|
|
value,
|
|
optionalCount: value,
|
|
earned: Boolean(value),
|
|
});
|
|
}
|
|
|
|
function _addUltimateGear (result, user, data) {
|
|
if (!data.altPath) {
|
|
data.altPath = data.path;
|
|
}
|
|
|
|
let value = user.achievements.ultimateGearSets[data.altPath];
|
|
|
|
let key = `${data.path}UltimateGear`;
|
|
let thisContent = achievsContent[key];
|
|
|
|
let localizedClass = i18n.t(data.path, data.language);
|
|
let title = i18n.t(thisContent.titleKey, {ultClass: localizedClass}, data.language);
|
|
let text = i18n.t(thisContent.textKey, {ultClass: localizedClass}, data.language);
|
|
|
|
_add(result, {
|
|
title,
|
|
text,
|
|
icon: thisContent.icon,
|
|
key,
|
|
value,
|
|
earned: Boolean(value),
|
|
});
|
|
}
|
|
|
|
function _getBasicAchievements (user, language) {
|
|
let result = {};
|
|
|
|
_addPlural(result, user, {path: 'streak', language});
|
|
_addPlural(result, user, {path: 'perfect', language});
|
|
|
|
_addSimple(result, user, {path: 'partyUp', language});
|
|
_addSimple(result, user, {path: 'partyOn', language});
|
|
|
|
_addSimpleWithMasterCount(result, user, {path: 'beastMaster', language});
|
|
_addSimpleWithMasterCount(result, user, {path: 'mountMaster', language});
|
|
_addSimpleWithMasterCount(result, user, {path: 'triadBingo', language});
|
|
|
|
_addUltimateGear(result, user, {path: 'healer', language});
|
|
_addUltimateGear(result, user, {path: 'rogue', language});
|
|
_addUltimateGear(result, user, {path: 'warrior', language});
|
|
_addUltimateGear(result, user, {path: 'mage', altpath: 'wizard', language});
|
|
|
|
let rebirthTitle;
|
|
let rebirthText;
|
|
|
|
if (user.achievements.rebirths > 1) {
|
|
rebirthTitle = i18n.t('rebirthText', {rebirths: user.achievements.rebirths}, language);
|
|
} else {
|
|
rebirthTitle = i18n.t('rebirthBegan', language);
|
|
}
|
|
|
|
if (!user.achievements.rebirthLevel) {
|
|
rebirthText = i18n.t('rebirthOrbNoLevel', language);
|
|
} else if (user.achievements.rebirthLevel < 100) {
|
|
rebirthText = i18n.t('rebirthOrb', {level: user.achievements.rebirthLevel}, language);
|
|
} else {
|
|
rebirthText = i18n.t('rebirthOrb100', language);
|
|
}
|
|
|
|
_add(result, {
|
|
key: 'rebirth',
|
|
title: rebirthTitle,
|
|
text: rebirthText,
|
|
icon: 'achievement-sun',
|
|
earned: Boolean(user.achievements.rebirths),
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function _getSeasonalAchievements (user, language) {
|
|
let result = {};
|
|
|
|
_addPlural(result, user, {path: 'habiticaDays', language});
|
|
_addPlural(result, user, {path: 'habitBirthdays', language});
|
|
|
|
let spellAchievements = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
|
|
spellAchievements.forEach(path => {
|
|
_addSimpleWithCount(result, user, {path, language});
|
|
});
|
|
|
|
let questAchievements = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];
|
|
questAchievements.forEach(path => {
|
|
_addQuest(result, user, {path, language});
|
|
});
|
|
|
|
_addPlural(result, user, {path: 'costumeContests', language});
|
|
|
|
let cardAchievements = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday'];
|
|
cardAchievements.forEach(path => {
|
|
_addSimpleWithCount(result, user, {path, key: `${path}Cards`, language});
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function _getSpecialAchievements (user, language) {
|
|
let result = {};
|
|
|
|
_addPlural(result, user, {path: 'habitSurveys', language});
|
|
|
|
let contribKey = 'contributor';
|
|
let contribContent = achievsContent[contribKey];
|
|
let contributorAchiev = {
|
|
key: contribKey,
|
|
text: i18n.t(contribContent.textKey, language),
|
|
icon: contribContent.icon,
|
|
earned: Boolean(user.contributor && user.contributor.level),
|
|
};
|
|
if (user.contributor && user.contributor.level) {
|
|
contributorAchiev.value = user.contributor.level;
|
|
contributorAchiev.title = contribText(user.contributor, user.backer, language);
|
|
} else {
|
|
contributorAchiev.value = 0;
|
|
contributorAchiev.title = i18n.t(contribContent.titleKey, language);
|
|
}
|
|
_add(result, contributorAchiev);
|
|
|
|
if (user.backer && user.backer.npc) {
|
|
_addSimpleWithCustomPath(result, user, {key: 'npc', path: 'backer.npc', language});
|
|
}
|
|
|
|
if (user.backer && user.backer.tier) {
|
|
_addSimpleWithCustomPath(result, user, {key: 'kickstarter', path: 'backer.tier', language});
|
|
}
|
|
|
|
if (user.achievements.veteran) {
|
|
_addSimple(result, user, {path: 'veteran', language});
|
|
}
|
|
|
|
if (user.achievements.originalUser) {
|
|
_addSimple(result, user, {path: 'originalUser', language});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Build and return the given user's achievement data.
|
|
achievs.getAchievementsForProfile = function getAchievementsForProfile (user, language) {
|
|
let result = {
|
|
basic: {
|
|
label: 'Basic',
|
|
achievements: _getBasicAchievements(user, language),
|
|
},
|
|
seasonal: {
|
|
label: 'Seasonal',
|
|
achievements: _getSeasonalAchievements(user, language),
|
|
},
|
|
special: {
|
|
label: 'Special',
|
|
achievements: _getSpecialAchievements(user, language),
|
|
},
|
|
};
|
|
return result;
|
|
};
|
|
|
|
module.exports = achievs;
|