Files
habitica/website/common/script/libs/achievements.js
Matteo Pagliazzi 8f5a0cfe79 Onboarding guide and initial achievements refactoring (#11536)
* 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
2019-12-16 17:20:47 +01:00

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;