mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 05:07:22 +01:00
* add achievements to user * add placeholder strings * add to achievements to common script * add onboarding achievements category * add notifications * more notifications * award achievements * wip notification panel * add achievements icons and copy * do not count onboarding tasks for the created task achievement * add notes * sprites, fixes and completion status and reward * add onboarding panel * add toggle * fix toggle size * fix tests * fix typo * add notification * start adding modal * fix remove button positionin, timeout, progress bar * modal + fixes * disable broken social links from level up modal * change toggle icon color on hover * add border bottom to onboarding guide panel * add collapse animation * expanded onboarding on first open * onboarding: flip toggle colors * onboarding: show progress bar all the time * onboarding: fix panel closing on click * onboarding modal: add close icon and fix padding * wip: add migration for existing users * fix titles in guide * fix achievements copy * do not award completed task achievement when direction is down * start implementing new achievements * start migrating client * remove social links from achievements modals * prevent skipping tutorial + fix achievement notification * sync fixes * start redesign achievement modal * misc fixes to achievements, polish generic achievement modal and hatched pet modal * add special badge for onboarding * fix badge condition * modals fixes * hatched pet modal: add close icon * fix badge typo * fix justin button * new scrolling behavior for dropdowns * fix strings capitalization * add common tests * add api unit tests * add date check * achievements modal polishing * typos * add toggle for achievements categories * typo * fix test * fix edit avatar modal cannot be closed * finish migration and correct launch date * fix migration * migration fixes * fix tests
355 lines
10 KiB
JavaScript
355 lines
10 KiB
JavaScript
import get from 'lodash/get';
|
|
import content from '../content/index';
|
|
import i18n from '../i18n';
|
|
|
|
const achievs = {};
|
|
const achievsContent = content.achievements;
|
|
let index = 0;
|
|
|
|
function contribText (contrib, backer, language) {
|
|
if (!contrib && !backer) return null;
|
|
if (backer && backer.npc) return backer.npc;
|
|
|
|
const 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}`;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function _add (result, data) {
|
|
result[data.key] = {
|
|
title: data.title,
|
|
text: data.text,
|
|
icon: data.icon,
|
|
earned: data.earned,
|
|
value: data.value,
|
|
index: index += 1,
|
|
optionalCount: data.optionalCount,
|
|
};
|
|
}
|
|
|
|
function _addSimpleWithCustomPath (result, user, data) {
|
|
const value = get(user, data.path);
|
|
const 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) {
|
|
const value = user.achievements[data.path];
|
|
|
|
const key = data.key || data.path;
|
|
const 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) {
|
|
const { language } = data;
|
|
const value = user.achievements[`${data.path}Count`] || 0;
|
|
|
|
const 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: user.achievements[data.path] || false,
|
|
});
|
|
}
|
|
|
|
function _addSimpleWithCount (result, user, data) {
|
|
const value = user.achievements[data.path] || 0;
|
|
|
|
const key = data.key || data.path;
|
|
const 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) {
|
|
const value = user.achievements[data.path] || 0;
|
|
|
|
const key = data.key || data.path;
|
|
const 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;
|
|
}
|
|
|
|
const value = user.achievements.ultimateGearSets[data.altPath];
|
|
|
|
const key = `${data.path}UltimateGear`;
|
|
const thisContent = achievsContent[key];
|
|
|
|
const localizedClass = i18n.t(data.path, data.language);
|
|
const title = i18n.t(thisContent.titleKey, { ultClass: localizedClass }, data.language);
|
|
const 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) {
|
|
const 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 });
|
|
_addSimple(result, user, { path: 'joinedGuild', language });
|
|
_addSimple(result, user, { path: 'royallyLoyal', language });
|
|
_addSimple(result, user, { path: 'joinedChallenge', language });
|
|
_addSimple(result, user, { path: 'invitedFriend', language });
|
|
_addSimple(result, user, { path: 'lostMasterclasser', language });
|
|
_addSimple(result, user, { path: 'mindOverMatter', language });
|
|
_addSimple(result, user, { path: 'justAddWater', language });
|
|
_addSimple(result, user, { path: 'backToBasics', language });
|
|
_addSimple(result, user, { path: 'allYourBase', language });
|
|
_addSimple(result, user, { path: 'dustDevil', language });
|
|
_addSimple(result, user, { path: 'aridAuthority', language });
|
|
_addSimple(result, user, { path: 'monsterMagus', language });
|
|
_addSimple(result, user, { path: 'undeadUndertaker', language });
|
|
_addSimple(result, user, { path: 'primedForPainting', language });
|
|
_addSimple(result, user, { path: 'pearlyPro', 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 });
|
|
|
|
const cardAchievements = ['greeting', 'thankyou', 'birthday', 'congrats', 'getwell', 'goodluck'];
|
|
cardAchievements.forEach(path => {
|
|
_addSimpleWithCount(result, user, { path, key: `${path}Cards`, 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),
|
|
optionalCount: user.achievements.rebirths,
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function _getOnboardingAchievements (user, language) {
|
|
const result = {};
|
|
|
|
_addSimple(result, user, { path: 'createdTask', language });
|
|
_addSimple(result, user, { path: 'completedTask', language });
|
|
_addSimple(result, user, { path: 'hatchedPet', language });
|
|
_addSimple(result, user, { path: 'fedPet', language });
|
|
_addSimple(result, user, { path: 'purchasedEquipment', language });
|
|
|
|
return result;
|
|
}
|
|
|
|
function _getSeasonalAchievements (user, language) {
|
|
const result = {};
|
|
|
|
_addPlural(result, user, { path: 'habiticaDays', language });
|
|
_addPlural(result, user, { path: 'habitBirthdays', language });
|
|
|
|
const spellAchievements = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
|
|
spellAchievements.forEach(path => {
|
|
_addSimpleWithCount(result, user, { path, language });
|
|
});
|
|
|
|
const questAchievements = ['dilatory', 'stressbeast', 'burnout', 'bewilder', 'dysheartener'];
|
|
questAchievements.forEach(path => {
|
|
if (user.achievements.quests[path]) {
|
|
_addQuest(result, user, { path, language });
|
|
}
|
|
});
|
|
|
|
_addPlural(result, user, { path: 'costumeContests', language });
|
|
|
|
const cardAchievements = ['nye', 'valentine'];
|
|
cardAchievements.forEach(path => {
|
|
_addSimpleWithCount(result, user, { path, key: `${path}Cards`, language });
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function _getSpecialAchievements (user, language) {
|
|
const result = {};
|
|
|
|
_addPlural(result, user, { path: 'habitSurveys', language });
|
|
|
|
const contribKey = 'contributor';
|
|
const contribContent = achievsContent[contribKey];
|
|
const 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 });
|
|
}
|
|
|
|
if (user.achievements.kickstarter2019) {
|
|
_addSimple(result, user, { path: 'kickstarter2019', language });
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Build and return the given user's achievement data.
|
|
achievs.getAchievementsForProfile = function getAchievementsForProfile (user, language) {
|
|
const result = {
|
|
basic: {
|
|
label: 'Basic',
|
|
achievements: _getBasicAchievements(user, language),
|
|
},
|
|
onboarding: {
|
|
label: 'Onboarding',
|
|
achievements: _getOnboardingAchievements(user, language),
|
|
},
|
|
seasonal: {
|
|
label: 'Seasonal',
|
|
achievements: _getSeasonalAchievements(user, language),
|
|
},
|
|
special: {
|
|
label: 'Special',
|
|
achievements: _getSpecialAchievements(user, language),
|
|
},
|
|
};
|
|
return result;
|
|
};
|
|
|
|
achievs.getContribText = contribText;
|
|
|
|
export default achievs;
|