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
This commit is contained in:
Matteo Pagliazzi
2019-12-16 17:20:47 +01:00
committed by GitHub
parent a00a8cced8
commit 8f5a0cfe79
108 changed files with 18515 additions and 17229 deletions

View File

@@ -0,0 +1,107 @@
/*
* Award Onboarding Achievements for existing users
*/
/* eslint-disable no-console */
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20191218_onboarding_achievements';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const set = {};
set.migration = MIGRATION_NAME;
const hasPet = Object.keys(user.items.pets).find(petKey => {
const pet = user.items.pets[petKey];
if (pet >= 5) return true;
return false;
});
if (hasPet) {
set['achievements.hatchedPet'] = true;
}
const hasFedPet = Object.keys(user.items.pets).find(petKey => {
const pet = user.items.pets[petKey];
if (pet > 5) return true;
return false;
});
if (hasFedPet) {
set['achievements.fedPet'] = true;
}
const hasGear = Object.keys(user.items.gear.owned).find(gearKey => {
const gear = user.items.gear.owned[gearKey];
if (gear === true && gearKey.indexOf('_special_') === -1) return true;
return false;
});
if (hasGear) {
set['achievements.purchasedEquipment'] = true;
}
const hasTask = Object.keys(user.tasksOrder).find(tasksOrderType => {
const order = user.tasksOrder[tasksOrderType];
if (order && order.length > 0) return true;
return false;
});
if (hasTask) {
set['achievements.createdTask'] = true;
}
const hasExperience = user.stats && user.stats.exp && user.stats.exp > 0;
if (hasTask && hasExperience) {
set['achievements.completedTask'] = true;
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.update({ _id: user._id }, { $set: set }).exec();
}
module.exports = async function processUsers () { // eslint-disable-line import/no-commonjs
const query = {
migration: { $ne: MIGRATION_NAME },
};
const fields = {
_id: 1,
stats: 1,
items: 1,
achievements: 1,
tasksOrder: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(100)
.sort({ _id: 1 })
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -11,6 +11,7 @@ import { model as User } from '../../../../../../website/server/models/user';
import { model as Group } from '../../../../../../website/server/models/group'; import { model as Group } from '../../../../../../website/server/models/group';
import { import {
generateGroup, generateGroup,
sleep,
} from '../../../../../helpers/api-unit.helper'; } from '../../../../../helpers/api-unit.helper';
describe('Purchasing a group plan for group', () => { describe('Purchasing a group plan for group', () => {
@@ -307,6 +308,7 @@ describe('Purchasing a group plan for group', () => {
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
await sleep(0.5);
expect(sender.sendTxn).to.have.callCount(4); expect(sender.sendTxn).to.have.callCount(4);
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL); expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
@@ -340,6 +342,7 @@ describe('Purchasing a group plan for group', () => {
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
await sleep(0.5);
expect(sender.sendTxn).to.have.callCount(4); expect(sender.sendTxn).to.have.callCount(4);
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL); expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);

View File

@@ -5,6 +5,7 @@ import {
moveTask, moveTask,
} from '../../../../website/server/libs/taskManager'; } from '../../../../website/server/libs/taskManager';
import i18n from '../../../../website/common/script/i18n'; import i18n from '../../../../website/common/script/i18n';
import shared from '../../../../website/common/script';
import { import {
generateUser, generateUser,
generateGroup, generateGroup,
@@ -58,6 +59,51 @@ describe('taskManager', () => {
expect(newTask.createdAt).to.exist; expect(newTask.createdAt).to.exist;
}); });
describe('onboarding', () => {
beforeEach(() => {
user.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
});
it('adds the onboarding achievement to the user and checks the onboarding status', async () => {
req.body = testHabit;
res.t = i18n.t;
user.flags.welcomed = true;
await createTasks(req, res, { user });
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('createdTask');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if flags.welcomed is false', async () => {
req.body = testHabit;
res.t = i18n.t;
user.flags.welcomed = false;
await createTasks(req, res, { user });
expect(user.addAchievement).to.not.be.called;
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', async () => {
req.body = testHabit;
res.t = i18n.t;
user.achievements.createdTask = true;
await createTasks(req, res, { user });
expect(user.addAchievement).to.not.be.called;
});
});
it('gets user tasks', async () => { it('gets user tasks', async () => {
req.body = testHabit; req.body = testHabit;
res.t = i18n.t; res.t = i18n.t;

View File

@@ -1879,6 +1879,8 @@ describe('Group Model', () => {
await questLeader.save(); await questLeader.save();
await party.finishQuest(quest); await party.finishQuest(quest);
await sleep(0.5);
const [ const [
updatedLeader, updatedLeader,
updatedParticipatingMember, updatedParticipatingMember,

View File

@@ -84,6 +84,103 @@ describe('User Model', () => {
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl)); expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
}); });
context('achievements', () => {
it('can add an achievement', () => {
const user = new User();
const originalUserToJSON = user.toJSON({ minimize: false });
expect(originalUserToJSON.achievements.createdTask).to.not.eql(true);
const notificationsN = originalUserToJSON.notifications.length;
user.addAchievement('createdTask');
const userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(notificationsN + 1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
});
it('throws an error if the achievement is not valid', () => {
const user = new User();
expect(() => user.addAchievement('notAnAchievement')).to.throw;
});
context('static push method', () => {
it('throws an error if the achievement is not valid', async () => {
const user = new User();
await user.save();
await expect(User.addAchievementUpdate({ _id: user._id }, 'notAnAchievement'))
.to.eventually.be.rejected;
expect(() => user.addAchievement('notAnAchievement')).to.throw;
});
it('adds an achievement for a single member via static method', async () => {
let user = new User();
await user.save();
const originalUserToJSON = user.toJSON({ minimize: false });
expect(originalUserToJSON.achievements.createdTask).to.not.eql(true);
const notificationsN = originalUserToJSON.notifications.length;
await User.addAchievementUpdate({ _id: user._id }, 'createdTask');
user = await User.findOne({ _id: user._id }).exec();
const userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(notificationsN + 1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
});
it('adds an achievement for all given users via static method', async () => {
let user = new User();
const otherUser = new User();
await Promise.all([user.save(), otherUser.save()]);
await User.addAchievementUpdate({ _id: { $in: [user._id, otherUser._id] } }, 'createdTask');
user = await User.findOne({ _id: user._id }).exec();
let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
user = await User.findOne({ _id: otherUser._id }).exec();
userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
});
});
});
context('notifications', () => { context('notifications', () => {
it('can add notifications without data', () => { it('can add notifications without data', () => {
const user = new User(); const user = new User();

View File

@@ -239,6 +239,47 @@ describe('achievements', () => {
}); });
}); });
describe('unearned onboarding achievements', () => {
const user = generateUser();
const onboardingAchievs = shared
.achievements.getAchievementsForProfile(user).onboarding.achievements;
it('created task achievement exists with no count', () => {
const { createdTask } = onboardingAchievs;
expect(createdTask).to.exist;
expect(createdTask.optionalCount).to.be.undefined;
});
it('completed task achievement exists with no count', () => {
const { completedTask } = onboardingAchievs;
expect(completedTask).to.exist;
expect(completedTask.optionalCount).to.be.undefined;
});
it('hatched pet achievement exists with no count', () => {
const { hatchedPet } = onboardingAchievs;
expect(hatchedPet).to.exist;
expect(hatchedPet.optionalCount).to.be.undefined;
});
it('fed pet achievement exists with no count', () => {
const { fedPet } = onboardingAchievs;
expect(fedPet).to.exist;
expect(fedPet.optionalCount).to.be.undefined;
});
it('purchased equipment achievement exists with no count', () => {
const { purchasedEquipment } = onboardingAchievs;
expect(purchasedEquipment).to.exist;
expect(purchasedEquipment.optionalCount).to.be.undefined;
});
});
describe('earned seasonal achievements', () => { describe('earned seasonal achievements', () => {
const user = generateUser(); const user = generateUser();
const quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder']; const quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];

View File

@@ -0,0 +1,100 @@
import moment from 'moment';
import {
hasActiveOnboarding,
hasCompletedOnboarding,
onOnboardingComplete,
checkOnboardingStatus,
} from '../../../website/common/script/libs/onboarding';
import { generateUser } from '../../helpers/common.helper';
describe('onboarding', () => {
let user;
beforeEach(() => {
user = generateUser();
user.addNotification = sinon.spy();
// Make sure the onboarding is active
user.auth.timestamps.created = moment('2019-12-20').toDate();
});
describe('hasActiveOnboarding', () => {
// The value of BEGIN DATE is available in common/script/libs/onboarding
it('returns true if the account is created after BEGIN_DATE', () => {
expect(hasActiveOnboarding(user)).to.eql(true);
});
it('returns false if the account is created before BEGIN_DATE', () => {
user.auth.timestamps.created = moment('2019-12-01').toDate();
expect(hasActiveOnboarding(user)).to.eql(false);
});
});
describe('hasCompletedOnboarding', () => {
it('returns false if no achievement has been awarded', () => {
const result = hasCompletedOnboarding(user);
expect(result).to.eql(false);
});
it('returns false if not all achievements have been awarded', () => {
user.achievements.completedTask = true;
const result = hasCompletedOnboarding(user);
expect(result).to.eql(false);
});
it('returns true if all achievements have been awarded', () => {
user.achievements.createdTask = true;
user.achievements.completedTask = true;
user.achievements.hatchedPet = true;
user.achievements.fedPet = true;
user.achievements.purchasedEquipment = true;
const result = hasCompletedOnboarding(user);
expect(result).to.eql(true);
});
});
describe('onOnboardingComplete', () => {
it('awards prizes', () => {
const { gp } = user.stats;
onOnboardingComplete(user);
expect(user.stats.gp).to.eql(gp + 100);
});
});
describe('checkOnboardingStatus', () => {
it('does nothing if onboarding is not active', () => {
const { gp } = user.stats;
user.auth.timestamps.created = moment('2019-12-01').toDate();
checkOnboardingStatus(user);
expect(user.addNotification).to.not.be.called;
expect(user.stats.gp).to.eql(gp);
});
it('does nothing if onboarding is not complete', () => {
const { gp } = user.stats;
checkOnboardingStatus(user);
expect(user.addNotification).to.not.be.called;
expect(user.stats.gp).to.eql(gp);
});
it('awards prize and add notification when onboarding is complete', () => {
user.achievements.createdTask = true;
user.achievements.completedTask = true;
user.achievements.hatchedPet = true;
user.achievements.fedPet = true;
user.achievements.purchasedEquipment = true;
const { gp } = user.stats;
checkOnboardingStatus(user);
expect(user.addNotification).to.be.calledOnce;
expect(user.addNotification).to.be.calledWith('ONBOARDING_COMPLETE');
expect(user.stats.gp).to.eql(gp + 100);
});
});
});

View File

@@ -41,7 +41,10 @@ describe('shared.ops.buyMarketGear', () => {
}, },
}); });
user.addAchievement = sinon.spy();
sinon.stub(shared, 'randomVal'); sinon.stub(shared, 'randomVal');
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
sinon.stub(shared.fns, 'predictableRandom'); sinon.stub(shared.fns, 'predictableRandom');
sinon.stub(analytics, 'track'); sinon.stub(analytics, 'track');
}); });
@@ -49,6 +52,7 @@ describe('shared.ops.buyMarketGear', () => {
afterEach(() => { afterEach(() => {
shared.randomVal.restore(); shared.randomVal.restore();
shared.fns.predictableRandom.restore(); shared.fns.predictableRandom.restore();
shared.onboarding.checkOnboardingStatus.restore();
analytics.track.restore(); analytics.track.restore();
}); });
@@ -86,6 +90,27 @@ describe('shared.ops.buyMarketGear', () => {
expect(analytics.track).to.be.calledOnce; expect(analytics.track).to.be.calledOnce;
}); });
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
user.stats.gp = 31;
buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('purchasedEquipment');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
user.stats.gp = 31;
user.achievements.purchasedEquipment = true;
buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
expect(user.addAchievement).to.not.be.called;
});
it('deducts gold from user', () => { it('deducts gold from user', () => {
user.stats.gp = 31; user.stats.gp = 31;

View File

@@ -10,12 +10,19 @@ import {
generateUser, generateUser,
} from '../../helpers/common.helper'; } from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage'; import errorMessage from '../../../website/common/script/libs/errorMessage';
import shared from '../../../website/common/script';
describe('shared.ops.feed', () => { describe('shared.ops.feed', () => {
let user; let user;
beforeEach(() => { beforeEach(() => {
user = generateUser(); user = generateUser();
user.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
}); });
context('failure conditions', () => { context('failure conditions', () => {
@@ -223,5 +230,28 @@ describe('shared.ops.feed', () => {
expect(user.items.mounts['Wolf-Base']).to.equal(true); expect(user.items.mounts['Wolf-Base']).to.equal(true);
expect(user.items.currentPet).to.equal(''); expect(user.items.currentPet).to.equal('');
}); });
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Meat = 2;
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' } });
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('fedPet');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Meat = 2;
user.achievements.fedPet = true;
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' } });
expect(user.addAchievement).to.not.be.called;
});
}); });
}); });

View File

@@ -9,12 +9,19 @@ import {
generateUser, generateUser,
} from '../../helpers/common.helper'; } from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage'; import errorMessage from '../../../website/common/script/libs/errorMessage';
import shared from '../../../website/common/script';
describe('shared.ops.hatch', () => { describe('shared.ops.hatch', () => {
let user; let user;
beforeEach(() => { beforeEach(() => {
user = generateUser(); user = generateUser();
user.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
}); });
context('Pet Hatching', () => { context('Pet Hatching', () => {
@@ -195,6 +202,30 @@ describe('shared.ops.hatch', () => {
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Spooky' } }); hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Spooky' } });
expect(user.achievements.dustDevil).to.eql(true); expect(user.achievements.dustDevil).to.eql(true);
}); });
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
user.items.eggs = { Wolf: 1 };
user.items.hatchingPotions = { Base: 1 };
user.items.pets = {};
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Base' } });
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('hatchedPet');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
user.items.eggs = { Wolf: 1 };
user.items.hatchingPotions = { Base: 1 };
user.items.pets = {};
user.achievements.hatchedPet = true;
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Base' } });
expect(user.addAchievement).to.not.be.called;
});
}); });
}); });
}); });

View File

@@ -12,6 +12,7 @@ import {
NotAuthorized, NotAuthorized,
} from '../../../website/common/script/libs/errors'; } from '../../../website/common/script/libs/errors';
import crit from '../../../website/common/script/fns/crit'; import crit from '../../../website/common/script/fns/crit';
import shared from '../../../website/common/script';
const EPSILON = 0.0001; // negligible distance between datapoints const EPSILON = 0.0001; // negligible distance between datapoints
@@ -340,5 +341,49 @@ describe('shared.ops.scoreTask', () => {
expectClosePoints(ref.beforeUser, ref.afterUser, freshTodo, todo); expectClosePoints(ref.beforeUser, ref.afterUser, freshTodo, todo);
}); });
}); });
context('onboarding', () => {
beforeEach(() => {
ref.afterUser.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
});
it('adds the achievement to the user and checks the onboarding status', () => {
scoreTask({ user: ref.afterUser, task: todo, direction: 'up' });
expect(ref.afterUser.addAchievement).to.be.calledOnce;
expect(ref.afterUser.addAchievement).to.be.calledWith('completedTask');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(ref.afterUser);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
ref.afterUser.achievements.completedTask = true;
scoreTask({ user: ref.afterUser, task: todo, direction: 'up' });
expect(ref.afterUser.addAchievement).to.not.be.called;
});
it('does not add the onboarding achievement to the user if it\'s scored down', () => {
scoreTask({ user: ref.afterUser, task: todo, direction: 'down' });
expect(ref.afterUser.addAchievement).to.not.be.called;
});
it('does not add the onboarding achievement to the user if cron is running', () => {
scoreTask({
user: ref.afterUser,
task: todo,
direction: 'up',
cron: true,
});
expect(ref.afterUser.addAchievement).to.not.be.called;
});
});
}); });
}); });

View File

@@ -1,222 +1,246 @@
.achievement-alien { .achievement-alien {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1323px -1628px; background-position: -1656px -1480px;
width: 24px; width: 24px;
height: 26px; height: 26px;
} }
.achievement-alien2x { .achievement-alien2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1357px -1480px; background-position: -1460px -1480px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-allYourBase2x { .achievement-allYourBase2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -901px -1480px; background-position: -759px -1480px;
width: 64px; width: 64px;
height: 56px; height: 56px;
} }
.achievement-alpha2x { .achievement-alpha2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1406px -1480px; background-position: -1509px -1480px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-aridAuthority2x { .achievement-aridAuthority2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -966px -1480px; background-position: -824px -1480px;
width: 64px; width: 64px;
height: 56px; height: 56px;
} }
.achievement-armor2x { .achievement-armor2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1455px -1480px; background-position: -1558px -1480px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-backToBasics2x { .achievement-backToBasics2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1161px -1480px; background-position: -1019px -1480px;
width: 48px; width: 48px;
height: 56px; height: 56px;
} }
.achievement-bewilder2x { .achievement-bewilder2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1504px -1480px; background-position: -1607px -1480px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-birthday2x { .achievement-birthday2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1553px -1480px; background-position: -568px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-boot2x { .achievement-boot2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1602px -1480px; background-position: -617px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-bow2x { .achievement-bow2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1651px -1480px; background-position: -666px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-burnout2x { .achievement-burnout2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -1549px; background-position: -715px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-cactus2x { .achievement-cactus2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -759px -1549px; background-position: -764px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-cake2x { .achievement-cake2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -808px -1549px; background-position: -813px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-cave2x { .achievement-cave2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -857px -1549px; background-position: -862px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-challenge2x { .achievement-challenge2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -906px -1549px; background-position: -911px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-comment2x { .achievement-comment2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -955px -1549px; background-position: -960px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-completedTask2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1068px -1480px;
width: 48px;
height: 56px;
}
.achievement-congrats2x { .achievement-congrats2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1004px -1549px; background-position: -1009px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-costumeContest2x { .achievement-costumeContest2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1053px -1549px; background-position: -1058px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-createdTask2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1117px -1480px;
width: 48px;
height: 56px;
}
.achievement-dilatory2x { .achievement-dilatory2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1102px -1549px; background-position: -1107px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-dustDevil2x { .achievement-dustDevil2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1210px -1480px; background-position: -1166px -1480px;
width: 48px; width: 48px;
height: 56px; height: 56px;
} }
.achievement-dysheartener2x { .achievement-dysheartener2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1151px -1549px; background-position: -1156px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-fedPet2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1215px -1480px;
width: 48px;
height: 56px;
}
.achievement-friends2x { .achievement-friends2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1200px -1549px; background-position: -1205px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-getwell2x { .achievement-getwell2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1249px -1549px; background-position: -1254px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-goodluck2x { .achievement-goodluck2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1298px -1549px; background-position: -1303px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-greeting2x { .achievement-greeting2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1347px -1549px; background-position: -1352px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-guild2x { .achievement-guild2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1396px -1549px; background-position: -1401px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-habitBirthday2x { .achievement-habitBirthday2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1445px -1549px; background-position: -1450px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-habiticaDay2x { .achievement-habiticaDay2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1494px -1549px; background-position: -1499px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-hatchedPet2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1264px -1480px;
width: 48px;
height: 56px;
}
.achievement-heart2x { .achievement-heart2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1543px -1549px; background-position: -1548px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-justAddWater2x { .achievement-justAddWater2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -779px -1480px; background-position: -637px -1480px;
width: 60px; width: 60px;
height: 64px; height: 64px;
} }
.achievement-karaoke-2x { .achievement-karaoke-2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1592px -1549px; background-position: -1597px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-karaoke { .achievement-karaoke {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1348px -1628px; background-position: -1323px -1628px;
width: 24px; width: 24px;
height: 26px; height: 26px;
} }
.achievement-kickstarter20192x { .achievement-kickstarter20192x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -1480px; background-position: -568px -1480px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.achievement-lostMasterclasser2x { .achievement-lostMasterclasser2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1641px -1549px; background-position: -1646px -1549px;
width: 48px; width: 48px;
height: 52px; height: 52px;
} }
.achievement-mindOverMatter2x { .achievement-mindOverMatter2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -840px -1480px; background-position: -698px -1480px;
width: 60px; width: 60px;
height: 64px; height: 64px;
} }
.achievement-monsterMagus2x { .achievement-monsterMagus2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1259px -1480px; background-position: -1313px -1480px;
width: 48px; width: 48px;
height: 56px; height: 56px;
} }
@@ -252,7 +276,7 @@
} }
.achievement-pearlyPro2x { .achievement-pearlyPro2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1031px -1480px; background-position: -889px -1480px;
width: 64px; width: 64px;
height: 56px; height: 56px;
} }
@@ -264,7 +288,13 @@
} }
.achievement-primedForPainting2x { .achievement-primedForPainting2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1308px -1480px; background-position: -1362px -1480px;
width: 48px;
height: 56px;
}
.achievement-purchasedEquipment2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1411px -1480px;
width: 48px; width: 48px;
height: 56px; height: 56px;
} }
@@ -378,7 +408,7 @@
} }
.achievement-undeadUndertaker2x { .achievement-undeadUndertaker2x {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -1096px -1480px; background-position: -954px -1480px;
width: 64px; width: 64px;
height: 56px; height: 56px;
} }
@@ -1162,9 +1192,3 @@
width: 141px; width: 141px;
height: 147px; height: 147px;
} }
.background_mountain_pyramid {
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -1480px;
width: 141px;
height: 147px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,90 @@
.weapon_warrior_3 { .weapon_warrior_0 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -997px; background-position: -1802px -997px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_warrior_4 { .weapon_warrior_1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -997px; background-position: -1893px -997px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_warrior_5 { .weapon_warrior_2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1088px; background-position: -1802px -1088px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_warrior_6 { .weapon_warrior_3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1088px; background-position: -1893px -1088px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_0 { .weapon_warrior_4 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1179px; background-position: -1802px -1179px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_1 { .weapon_warrior_5 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1179px; background-position: -1893px -1179px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_2 { .weapon_warrior_6 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1270px; background-position: -1802px -1270px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_3 { .weapon_wizard_0 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1270px; background-position: -1893px -1270px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_4 { .weapon_wizard_1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1361px; background-position: -1802px -1361px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_5 { .weapon_wizard_2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1361px; background-position: -1893px -1361px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_6 { .weapon_wizard_3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1452px; background-position: -1802px -1452px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.weapon_wizard_4 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1452px;
width: 90px;
height: 90px;
}
.weapon_wizard_5 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1543px;
width: 90px;
height: 90px;
}
.weapon_wizard_6 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1543px;
width: 90px;
height: 90px;
}
.Pet_Currency_Gem { .Pet_Currency_Gem {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1298px; background-position: -1696px -1433px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
@@ -84,19 +102,19 @@
} }
.PixelPaw-Gold { .PixelPaw-Gold {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1688px -1436px; background-position: -660px -508px;
width: 51px; width: 51px;
height: 51px; height: 51px;
} }
.PixelPaw { .PixelPaw {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1740px -1436px; background-position: -660px -560px;
width: 51px; width: 51px;
height: 51px; height: 51px;
} }
.PixelPaw002 { .PixelPaw002 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -220px -203px; background-position: -880px -655px;
width: 51px; width: 51px;
height: 51px; height: 51px;
} }
@@ -132,13 +150,13 @@
} }
.ghost { .ghost {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1452px; background-position: -1627px -422px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.inventory_present { .inventory_present {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -746px; background-position: -1696px -881px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
@@ -168,187 +186,187 @@
} }
.inventory_present_05 { .inventory_present_05 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -470px; background-position: -1718px -422px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_present_06 { .inventory_present_06 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -470px; background-position: -1718px -513px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_present_07 { .inventory_present_07 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -539px; background-position: -1718px -604px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_present_08 { .inventory_present_08 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -539px; background-position: -1627px -743px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_present_09 { .inventory_present_09 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -608px; background-position: -1696px -743px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_present_10 { .inventory_present_10 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -608px; background-position: -1627px -812px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_present_11 { .inventory_present_11 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -677px; background-position: -1696px -812px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_present_12 { .inventory_present_12 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -677px; background-position: -1627px -881px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_birthday { .inventory_special_birthday {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -746px; background-position: -1627px -950px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_congrats { .inventory_special_congrats {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -815px; background-position: -1696px -950px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_fortify { .inventory_special_fortify {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -815px; background-position: -1627px -1019px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_getwell { .inventory_special_getwell {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -884px; background-position: -1696px -1019px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_goodluck { .inventory_special_goodluck {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -884px; background-position: -1627px -1088px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_greeting { .inventory_special_greeting {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -953px; background-position: -1696px -1088px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_nye { .inventory_special_nye {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -953px; background-position: -1627px -1157px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_opaquePotion { .inventory_special_opaquePotion {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1022px; background-position: -1696px -1157px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_seafoam { .inventory_special_seafoam {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1022px; background-position: -1627px -1226px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_shinySeed { .inventory_special_shinySeed {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1091px; background-position: -1696px -1226px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_snowball { .inventory_special_snowball {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1091px; background-position: -1627px -1295px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_spookySparkles { .inventory_special_spookySparkles {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1160px; background-position: -1696px -1295px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_thankyou { .inventory_special_thankyou {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1160px; background-position: -1627px -1364px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_trinket { .inventory_special_trinket {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1229px; background-position: -1696px -1364px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.inventory_special_valentine { .inventory_special_valentine {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1229px; background-position: -1627px -1433px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.knockout { .knockout {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -422px; background-position: -1627px -695px;
width: 120px; width: 120px;
height: 47px; height: 47px;
} }
.pet_key { .pet_key {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1298px; background-position: -220px -203px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.rebirth_orb { .rebirth_orb {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1367px; background-position: -220px -272px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.seafoam_star { .seafoam_star {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1543px; background-position: -1627px -513px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.shop_armoire { .shop_armoire {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1367px; background-position: -220px -341px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.snowman { .snowman {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1543px; background-position: -1627px -604px;
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
.zzz { .zzz {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -220px -255px; background-position: -660px -612px;
width: 40px; width: 40px;
height: 40px; height: 40px;
} }
.zzz_light { .zzz_light {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1748px -422px; background-position: -1748px -695px;
width: 40px; width: 40px;
height: 40px; height: 40px;
} }
@@ -474,7 +492,7 @@
} }
.npc_bailey { .npc_bailey {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1436px; background-position: -660px -435px;
width: 60px; width: 60px;
height: 72px; height: 72px;
} }
@@ -486,7 +504,7 @@
} }
.npc_matt { .npc_matt {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -208px -1529px; background-position: 0px -1529px;
width: 195px; width: 195px;
height: 138px; height: 138px;
} }
@@ -504,13 +522,13 @@
} }
.phobia_dysheartener { .phobia_dysheartener {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -307px -220px; background-position: -527px -220px;
width: 201px; width: 201px;
height: 195px; height: 195px;
} }
.quest_alligator { .quest_alligator {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -967px -660px; background-position: -1187px -880px;
width: 201px; width: 201px;
height: 213px; height: 213px;
} }
@@ -528,19 +546,19 @@
} }
.quest_atom1 { .quest_atom1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1084px -1315px; background-position: -873px -1315px;
width: 250px; width: 250px;
height: 150px; height: 150px;
} }
.quest_atom2 { .quest_atom2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: 0px -1529px; background-position: -1375px -1315px;
width: 207px; width: 207px;
height: 138px; height: 138px;
} }
.quest_atom3 { .quest_atom3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -433px -1315px; background-position: -222px -1315px;
width: 216px; width: 216px;
height: 180px; height: 180px;
} }
@@ -564,7 +582,7 @@
} }
.quest_beetle { .quest_beetle {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -747px -440px; background-position: -967px -660px;
width: 204px; width: 204px;
height: 201px; height: 201px;
} }
@@ -576,7 +594,7 @@
} }
.quest_bunny { .quest_bunny {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -222px -1315px; background-position: -307px -220px;
width: 210px; width: 210px;
height: 186px; height: 186px;
} }
@@ -606,7 +624,7 @@
} }
.quest_dilatoryDistress1 { .quest_dilatoryDistress1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1187px -880px; background-position: -1407px -1097px;
width: 210px; width: 210px;
height: 210px; height: 210px;
} }
@@ -690,7 +708,7 @@
} }
.quest_goldenknight2 { .quest_goldenknight2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1335px -1315px; background-position: -1124px -1315px;
width: 250px; width: 250px;
height: 150px; height: 150px;
} }
@@ -702,7 +720,7 @@
} }
.quest_gryphon { .quest_gryphon {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -650px -1315px; background-position: -439px -1315px;
width: 216px; width: 216px;
height: 177px; height: 177px;
} }
@@ -720,7 +738,7 @@
} }
.quest_hedgehog { .quest_hedgehog {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -527px -220px; background-position: -747px -440px;
width: 219px; width: 219px;
height: 186px; height: 186px;
} }
@@ -744,7 +762,7 @@
} }
.quest_kraken { .quest_kraken {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -867px -1315px; background-position: -656px -1315px;
width: 216px; width: 216px;
height: 177px; height: 177px;
} }
@@ -826,9 +844,3 @@
width: 219px; width: 219px;
height: 219px; height: 219px;
} }
.quest_nudibranch {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1407px -1097px;
width: 216px;
height: 216px;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,132 +1,180 @@
.Pet_Food_Candy_CottonCandyBlue {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -636px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_CottonCandyPink {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -705px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_Desert {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -774px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_Golden {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -843px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_Red {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -912px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_Shade {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -981px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_Skeleton {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1050px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_White {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1119px -1550px;
width: 68px;
height: 68px;
}
.Pet_Food_Candy_Zombie { .Pet_Food_Candy_Zombie {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -954px -1550px; background-position: -1188px -1550px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Chocolate { .Pet_Food_Chocolate {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1023px -1550px; background-position: -1257px -1550px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_CottonCandyBlue { .Pet_Food_CottonCandyBlue {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1092px -1550px; background-position: -1326px -1550px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_CottonCandyPink { .Pet_Food_CottonCandyPink {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1161px -1550px; background-position: -1395px -1550px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Fish { .Pet_Food_Fish {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1230px -1550px; background-position: -1464px -1550px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Honey { .Pet_Food_Honey {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1299px -1550px; background-position: -1533px -1550px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Meat { .Pet_Food_Meat {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1368px -1550px; background-position: -1602px -1550px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Milk { .Pet_Food_Milk {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1437px -1550px; background-position: 0px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_Base { .Pet_Food_Pie_Base {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1506px -1550px; background-position: -69px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_CottonCandyBlue { .Pet_Food_Pie_CottonCandyBlue {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1575px -1550px; background-position: -138px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_CottonCandyPink { .Pet_Food_Pie_CottonCandyPink {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -1644px -1550px; background-position: -207px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_Desert { .Pet_Food_Pie_Desert {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: 0px -1656px; background-position: -276px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_Golden { .Pet_Food_Pie_Golden {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -69px -1656px; background-position: -345px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_Red { .Pet_Food_Pie_Red {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -138px -1656px; background-position: -414px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_Shade { .Pet_Food_Pie_Shade {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -207px -1656px; background-position: -483px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_Skeleton { .Pet_Food_Pie_Skeleton {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -276px -1656px; background-position: -552px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_White { .Pet_Food_Pie_White {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -345px -1656px; background-position: -621px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Pie_Zombie { .Pet_Food_Pie_Zombie {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -414px -1656px; background-position: -690px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Potatoe { .Pet_Food_Potatoe {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -483px -1656px; background-position: -759px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_RottenMeat { .Pet_Food_RottenMeat {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -552px -1656px; background-position: -828px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Saddle { .Pet_Food_Saddle {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -621px -1656px; background-position: -897px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_Food_Strawberry { .Pet_Food_Strawberry {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png'); background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -690px -1656px; background-position: -966px -1656px;
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
@@ -1510,21 +1558,3 @@
width: 105px; width: 105px;
height: 105px; height: 105px;
} }
.Mount_Body_Egg-CottonCandyBlue {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -636px -1550px;
width: 105px;
height: 105px;
}
.Mount_Body_Egg-CottonCandyPink {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -742px -1550px;
width: 105px;
height: 105px;
}
.Mount_Body_Egg-Desert {
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
background-position: -848px -1550px;
width: 105px;
height: 105px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 KiB

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 682 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 127 KiB

View File

@@ -50,3 +50,18 @@
width: 20px; width: 20px;
} }
} }
.close-icon {
position: absolute;
top: 24px;
right: 24px;
cursor: pointer;
& svg path {
stroke: $gray-200;
}
&:hover svg path {
stroke: $gray-100;
}
}

View File

@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="48" viewBox="0 0 150 48">
<defs>
<filter id="a" width="225%" height="225%" x="-62.5%" y="-62.5%" filterUnits="objectBoundingBox">
<feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="4"/>
<feColorMatrix in="shadowBlurOuter1" result="shadowMatrixOuter1" values="0 0 0 0 0.101960784 0 0 0 0 0.0941176471 0 0 0 0 0.11372549 0 0 0 0.32 0"/>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g fill="none" fill-rule="evenodd">
<g fill="#FFA624">
<path d="M125.45 35.414l3.346-2.967-4.382-.896-2.967-3.347-.896 4.382-3.347 2.967 4.382.896 2.967 3.347z" opacity=".75"/>
<path d="M136.43 30.407l3.325-.442-2.348-2.395-.442-3.325-2.395 2.348-3.325.442 2.348 2.395.442 3.325z" opacity=".5"/>
<path d="M146.901 35.41l2.067.854-.557-2.165.853-2.067-2.165.557-2.067-.853.557 2.165-.853 2.067z" opacity=".25"/>
<path d="M138.353 39.874l1.488-.093-.967-1.134-.093-1.488-1.134.967-1.488.093.967 1.134.093 1.488z" opacity=".35"/>
</g>
<g fill="#FFA624">
<path d="M24.55 35.414l-3.346-2.967 4.382-.896 2.967-3.347.896 4.382 3.347 2.967-4.382.896-2.967 3.347z" opacity=".75"/>
<path d="M13.57 30.407l-3.325-.442 2.348-2.395.442-3.325 2.395 2.348 3.325.442-2.348 2.395-.442 3.325z" opacity=".5"/>
<path d="M3.099 35.41l-2.067.854.557-2.165-.853-2.067 2.165.557 2.067-.853-.557 2.165.853 2.067z" opacity=".25"/>
<path d="M11.647 39.874l-1.488-.093.967-1.134.093-1.488 1.134.967 1.488.093-.967 1.134-.093 1.488z" opacity=".35"/>
</g>
<g opacity=".5" transform="translate(83 22)">
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
</g>
<g opacity=".5" transform="translate(43 22)">
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
</g>
<g transform="translate(59 6)">
<circle cx="16" cy="16" r="16" fill="#FFA623"/>
<path fill="#FFF" d="M8.4 23.6c-4.133-4.133-4.133-10.933 0-15.067 4.133-4.133 10.933-4.133 15.067 0" opacity=".5"/>
<path fill="#FFF" d="M23.6 8.4c4.133 4.133 4.133 10.933 0 15.067-4.133 4.133-10.933 4.133-15.067 0" opacity=".25"/>
<path fill="#BF7D1A" d="M16 2.667c-7.333 0-13.333 6-13.333 13.333s6 13.333 13.333 13.333 13.333-6 13.333-13.333S23.333 2.667 16 2.667zm0 24c-5.867 0-10.667-4.8-10.667-10.667S10.133 5.333 16 5.333 26.667 10.133 26.667 16 21.867 26.667 16 26.667z" opacity=".5"/>
<path fill="#BF7D1A" d="M17.333 12v2.667h-2.666V12H12v8h2.667v-2.667h2.666V20H20v-8z" opacity=".75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" viewBox="0 0 12 11">
<path fill="#FFF" fill-rule="evenodd" d="M6 8.76l-3.527 2.094.902-4.001L.294 4.146l4.084-.379L6 0l1.622 3.767 4.084.379-3.081 2.707.902 4.001z"/>
</svg>

After

Width:  |  Height:  |  Size: 241 B

View File

@@ -1,104 +1,16 @@
<template> <template>
<div <div
class="modal-footer" class="modal-footer"
style="margin-top:0"
ng-init="loadWidgets()"
> >
<div class="container-fluid share-buttons"> <!-- TODO disabled for now, see https://github.com/HabitRPG/habitica/issues/11283 -->
<div class="row">
<div class="col-12 text-center">
<a
class="twitter-share-button share-button"
:href="twitterLink"
target="_blank"
>
<div
class="social-icon twitter svg-icon"
v-html="icons.twitter"
></div>
{{ $t('tweet') }}
</a>
<a
class="fb-share-button share-button"
:href="facebookLink"
target="_blank"
>
<div
class="social-icon facebook svg-icon"
v-html="icons.facebook"
></div>
{{ $t('share') }}
</a>
<!-- @TODO: Still want this?
.col-4a.tumblr-share-button(:data-href='socialLevelLink', data-notes='none')-->
</div>
</div>
</div>
</div> </div>
</template> </template>
<style scoped> <style lang="scss" scoped>
.share-buttons { .modal-footer {
margin-top: 1em; border-top: none;
margin-bottom: 1em; }
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
</style> </style>
<script> <script>
// @TODO:
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
const BASE_URL = 'https://habitica.com';
export default {
data () {
const tweet = this.$t('achievementShare');
return {
icons: Object.freeze({
twitter,
facebook,
}),
tweet,
achievementLink: `${BASE_URL}`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}`,
};
},
};
</script> </script>

View File

@@ -3,14 +3,32 @@
id="generic-achievement" id="generic-achievement"
:title="data.message" :title="data.message"
size="md" size="md"
:hide-footer="true" :hide-header="true"
> >
<div class="modal-body"> <span
<div class="col-12"> class="close-icon svg-icon inline icon-10"
<achievement-avatar class="avatar" /> @click="close()"
v-html="icons.close"
></span>
<div class="content">
<div
v-once
class="dialog-header title"
>
{{ $t('earnedAchievement') }}
</div> </div>
<div class="col-6 offset-3 text-center"> <div class="inner-content">
<p v-html="data.modalText"></p> <div class="achievement-background d-flex align-items-center">
<div
class="icon"
:class="achievementClass"
></div>
</div>
<h4
class="title"
v-html="$t(achievement.titleKey)"
>
</h4>
<button <button
class="btn btn-primary" class="btn btn-primary"
@click="close()" @click="close()"
@@ -19,33 +37,86 @@
</button> </button>
</div> </div>
</div> </div>
<achievement-footer /> <div
slot="modal-footer"
class="clearfix"
></div>
</b-modal> </b-modal>
</template> </template>
<style scoped> <style lang="scss">
.avatar { @import '~@/assets/scss/modal.scss';
width: 140px;
#generic-achievement {
@include centeredModal();
.modal-dialog {
width: 330px;
}
.modal-footer {
padding-top: 0px;
}
}
</style>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.content {
text-align: center;
}
.inner-content {
margin: 24px auto auto;
display: flex;
flex-direction: column;
align-items: center;
}
.achievement-background {
width: 112px;
height: 112px;
border-radius: 4px;
background-color: $gray-700;
}
.dialog-header {
margin-top: 16px !important;
color: $purple-200 !important;
}
.title {
margin-bottom: 24px !important;
}
.icon {
margin: 0 auto; margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
} }
</style> </style>
<script> <script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import achievements from '@/../../common/script/content/achievements';
import svgClose from '@/assets/svg/close.svg';
export default { export default {
components: {
achievementFooter,
achievementAvatar,
},
props: ['data'], props: ['data'],
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
computed: { computed: {
...mapState({ user: 'user.data' }), ...mapState({ user: 'user.data' }),
achievement () {
return achievements[this.data.achievement];
},
achievementClass () {
return `${this.achievement.icon}2x`;
},
}, },
methods: { methods: {
close () { close () {

View File

@@ -27,36 +27,6 @@
label(style='display:inline-block') {{ $t('dontShowAgain') }} label(style='display:inline-block') {{ $t('dontShowAgain') }}
--> -->
</div> </div>
<div class="container-fluid share-buttons">
<div class="row">
<div class="col-12 text-center">
<a
class="twitter-share-button share-button"
:href="twitterLink"
target="_blank"
>
<div
class="social-icon twitter svg-icon"
v-html="icons.twitter"
></div>
{{ $t('tweet') }}
</a>
<a
class="fb-share-button share-button"
:href="facebookLink"
target="_blank"
>
<div
class="social-icon facebook svg-icon"
v-html="icons.facebook"
></div>
{{ $t('share') }}
</a>
</div>
<!-- @TODO: Still want this? .col-4a.tumblr
-share-button(:data-href='socialLevelLink', data-notes='none')-->
</div>
</div>
</b-modal> </b-modal>
</template> </template>
@@ -72,7 +42,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
.modal-body { .modal-body {
padding-top: 1em; padding-top: 1em;
padding-bottom: 0;
} }
.modal-footer { .modal-footer {
@@ -99,45 +68,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
margin-top: 1em; margin-top: 1em;
min-height: 0px; min-height: 0px;
} }
.share-buttons {
margin-top: 1em;
margin-bottom: 1em;
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
} }
</style> </style>
@@ -152,10 +82,6 @@ import Avatar from '../avatar';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants'; import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
import styleHelper from '@/mixins/styleHelper'; import styleHelper from '@/mixins/styleHelper';
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
const BASE_URL = 'https://habitica.com';
export default { export default {
components: { components: {
@@ -163,18 +89,9 @@ export default {
}, },
mixins: [styleHelper], mixins: [styleHelper],
data () { data () {
const tweet = this.$t('levelUpShare');
return { return {
icons: Object.freeze({
twitter,
facebook,
}),
statsAllocationBoxIsOpen: true, statsAllocationBoxIsOpen: true,
maxHealth, maxHealth,
tweet,
socialLevelLink: `${BASE_URL}/social/level-up`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}/social/level-up&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}/social/level-up`,
}; };
}, },
computed: { computed: {
@@ -183,16 +100,10 @@ export default {
return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation; return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation;
}, },
}, },
mounted () {
this.loadWidgets();
},
methods: { methods: {
close () { close () {
this.$root.$emit('bv::hide::modal', 'level-up'); this.$root.$emit('bv::hide::modal', 'level-up');
}, },
loadWidgets () {
// @TODO:
},
changeLevelupSuppress () { changeLevelupSuppress () {
// @TODO: dispatch set({"preferences.suppressModals.levelUp": // @TODO: dispatch set({"preferences.suppressModals.levelUp":
// user.preferences.suppressModals.levelUp?true: false}) // user.preferences.suppressModals.levelUp?true: false})

View File

@@ -0,0 +1,106 @@
<template>
<b-modal
id="onboarding-complete"
size="sm"
:hide-footer="true"
:hide-header="true"
>
<div class="content text-center">
<span
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<h2>{{ $t('congratulations') }}</h2>
<img
class="onboarding-complete-banner d-block"
src="~@/assets/images/onboarding-complete-banner@2x.png"
>
<p
class="onboarding-complete-text"
v-html="$t('onboardingCompleteDesc')"
></p>
<button
class="btn btn-primary"
@click="closeWithAction()"
>
{{ $t('viewAchievements') }}
</button>
</div>
</b-modal>
</template>
<style lang="scss">
#onboarding-complete {
.modal-content {
min-width: 330px;
}
.modal-body {
padding-top: 1em;
padding-bottom: 0;
}
.modal-footer {
margin-top: 0;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
h2 {
color: $purple-200;
margin-bottom: 24px;
margin-top: 2px;
}
.content {
padding: 0 8px;
margin-top: 18px;
}
.onboarding-complete-banner {
width: 282px;
margin: 0 auto;
margin-bottom: 24px;
}
.onboarding-complete-text {
margin-bottom: 24px;
}
.onboarding-complete-text ::v-deep .gold-amount {
color: $yellow-5;
}
button {
margin-bottom: 24px;
}
</style>
<script>
import svgClose from '@/assets/svg/close.svg';
export default {
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'onboarding-complete');
},
closeWithAction () {
this.close();
setTimeout(() => {
this.$router.push({ name: 'achievements' });
}, 200);
},
},
};
</script>

View File

@@ -6,7 +6,15 @@
:hide-header="true" :hide-header="true"
:hide-footer="true" :hide-footer="true"
:modal-class="{'page-2':modalPage > 1 && !editing}" :modal-class="{'page-2':modalPage > 1 && !editing}"
:no-close-on-esc="!editing"
:no-close-on-backdrop="!editing"
> >
<span
v-if="editing"
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<div <div
v-if="modalPage === 1 && !editing" v-if="modalPage === 1 && !editing"
class="section row welcome-section" class="section row welcome-section"
@@ -1099,6 +1107,7 @@ import gold from '@/assets/svg/gold.svg';
import pin from '@/assets/svg/pin.svg'; import pin from '@/assets/svg/pin.svg';
import arrowRight from '@/assets/svg/arrow_right.svg'; import arrowRight from '@/assets/svg/arrow_right.svg';
import arrowLeft from '@/assets/svg/arrow_left.svg'; import arrowLeft from '@/assets/svg/arrow_left.svg';
import svgClose from '@/assets/svg/close.svg';
import isPinned from '@/../../common/script/libs/isPinned'; import isPinned from '@/../../common/script/libs/isPinned';
import { avatarEditorUtilies } from '../mixins/avatarEditUtilities'; import { avatarEditorUtilies } from '../mixins/avatarEditUtilities';
@@ -1138,6 +1147,7 @@ export default {
gold, gold,
arrowRight, arrowRight,
arrowLeft, arrowLeft,
close: svgClose,
}), }),
modalPage: 1, modalPage: 1,
activeTopPage: 'body', activeTopPage: 'body',
@@ -1158,8 +1168,6 @@ export default {
}, },
computed: { computed: {
...mapState({ user: 'user.data' }), ...mapState({ user: 'user.data' }),
editing () { editing () {
return this.$store.state.avatarEditorOptions.editingUser; return this.$store.state.avatarEditorOptions.editingUser;
}, },
@@ -1228,6 +1236,9 @@ export default {
this.$root.$on('buyModal::boughtItem', this.backgroundPurchased); this.$root.$on('buyModal::boughtItem', this.backgroundPurchased);
}, },
methods: { methods: {
close () {
this.$root.$emit('bv::hide::modal', 'avatar-modal');
},
purchase (type, key) { purchase (type, key) {
this.$store.dispatch('shops:purchase', { this.$store.dispatch('shops:purchase', {
type, type,
@@ -1278,6 +1289,9 @@ export default {
if (this.$route.path !== '/') { if (this.$route.path !== '/') {
this.$router.push('/'); this.$router.push('/');
} }
// NOTE: it's important this flag is set AFTER the onboarding default tasks
// have been created or it'll break the onboarding guide achievement for creating a task
this.$store.dispatch('user:set', { this.$store.dispatch('user:set', {
'flags.welcomed': true, 'flags.welcomed': true,
}); });

View File

@@ -1,8 +1,9 @@
<template functional> <template functional>
<span <span
class="message-count" class="message-count d-flex align-items-center justify-content-center"
:class="{'top-count': props.top === true, 'top-count-gray': props.gray === true}" :class="{'top-count': props.top === true, 'top-count-gray': props.gray === true}"
> {{ props.count }} </span> v-html="props.badge || props.count"
></span>
</template> </template>
<style lang="scss"> <style lang="scss">
@@ -18,6 +19,11 @@
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
font-size: 12px; font-size: 12px;
svg {
width: 12px;
height: 12px;
}
} }
.message-count.top-count { .message-count.top-count {

View File

@@ -10,7 +10,10 @@
> >
<slot name="icon"></slot> <slot name="icon"></slot>
</div> </div>
<div class="notification-content"> <div
class="notification-content"
:class="{'has-text': hasText}"
>
<slot name="content"></slot> <slot name="content"></slot>
</div> </div>
<div <div
@@ -107,17 +110,19 @@
line-height: 1.43; line-height: 1.43;
color: $gray-50; color: $gray-50;
max-width: calc(100% - 26px); // to make space for the close icon max-width: 100%;
&.has-text {
padding-right: 12px;
}
} }
.notification-remove { .notification-remove {
// total distance from the notification top edge is 20 pixels position: absolute;
margin-top: 7px;
width: 18px; width: 18px;
height: 18px; height: 18px;
margin-left: 12px;
padding: 4px; padding: 4px;
right: 24px;
.svg-icon { .svg-icon {
width: 10px; width: 10px;
@@ -131,7 +136,25 @@ import closeIcon from '@/assets/svg/close.svg';
import { mapActions, mapState } from '@/libs/store'; import { mapActions, mapState } from '@/libs/store';
export default { export default {
props: ['notification', 'canRemove', 'hasIcon', 'readAfterClick'], props: {
notification: {
type: Object,
required: true,
},
canRemove: {
type: Boolean,
},
hasIcon: {
type: Boolean,
},
readAfterClick: {
type: Boolean,
},
hasText: {
type: Boolean,
default: true,
},
},
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({

View File

@@ -0,0 +1,74 @@
<template>
<base-notification
:can-remove="canRemove"
:has-icon="false"
:read-after-click="true"
:notification="notification"
:has-text="false"
@click="action"
>
<div
slot="content"
class="d-flex flex-column align-items-center onboarding-complete-content"
>
<img
class="onboarding-complete-banner d-block"
src="~@/assets/images/onboarding-complete-banner@2x.png"
>
<h3>{{ $t('congratulations') }}</h3>
<p
class="onboarding-complete-text"
v-html="$t('onboardingCompleteDesc')"
></p>
<div class="notifications-buttons">
<div
class="btn btn-small btn-primary btn-block"
>
{{ $t('viewAchievements') }}
</div>
</div>
</div>
</base-notification>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.onboarding-complete-content {
text-align: center;
}
.onboarding-complete-banner {
width: 282px;
margin-top: 12px;
margin-bottom: 12px;
}
.onboarding-complete-text {
margin-bottom: 4px;
}
.onboarding-complete-text ::v-deep .gold-amount {
color: $yellow-5;
}
.notifications-buttons {
width: 100%;
}
</style>
<script>
import BaseNotification from './base';
export default {
components: {
BaseNotification,
},
props: ['notification', 'canRemove'],
methods: {
action () {
this.$router.push({ name: 'achievements' });
},
},
};
</script>

View File

@@ -11,10 +11,11 @@
:aria-label="$t('notifications')" :aria-label="$t('notifications')"
> >
<message-count <message-count
v-if="notificationsCount > 0" v-if="notificationsCount > 0 || hasSpecialBadge"
:count="notificationsCount" :count="notificationsCount"
:top="true" :top="true"
:gray="!hasUnseenNotifications" :gray="!hasUnseenNotifications && !hasSpecialBadge"
:badge="hasSpecialBadge ? icons.starBadge : null"
/> />
<div <div
class="top-menu-icon svg-icon notifications" class="top-menu-icon svg-icon notifications"
@@ -22,7 +23,10 @@
></div> ></div>
</div> </div>
</div> </div>
<div slot="dropdown-content"> <div
v-if="openStatus === 1"
slot="dropdown-content"
>
<div <div
class="dropdown-item dropdown-separated class="dropdown-item dropdown-separated
d-flex justify-content-between dropdown-inactive align-items-center" d-flex justify-content-between dropdown-inactive align-items-center"
@@ -41,6 +45,10 @@
>{{ $t('dismissAll') }}</a> >{{ $t('dismissAll') }}</a>
</div> </div>
<world-boss /> <world-boss />
<onboarding-guide
v-if="showOnboardingGuide"
:never-seen="hasSpecialBadge"
/>
<component <component
:is="notification.type" :is="notification.type"
v-for="notification in notifications" v-for="notification in notifications"
@@ -49,7 +57,7 @@
:can-remove="!isActionable(notification)" :can-remove="!isActionable(notification)"
/> />
<div <div
v-if="notificationsCount === 0" v-if="notificationsCount === 0 && !showOnboardingGuide"
class="dropdown-item dropdown-separated class="dropdown-item dropdown-separated
d-flex justify-content-center dropdown-inactive no-notifications flex-column" d-flex justify-content-center dropdown-inactive no-notifications flex-column"
> >
@@ -106,10 +114,16 @@
<script> <script>
import { mapState, mapActions } from '@/libs/store'; import { mapState, mapActions } from '@/libs/store';
import * as quests from '@/../../common/script/content/quests'; import * as quests from '@/../../common/script/content/quests';
import {
hasCompletedOnboarding,
hasActiveOnboarding,
} from '@/../../common/script/libs/onboarding';
import notificationsIcon from '@/assets/svg/notifications.svg'; import notificationsIcon from '@/assets/svg/notifications.svg';
import MenuDropdown from '../ui/customMenuDropdown'; import MenuDropdown from '../ui/customMenuDropdown';
import MessageCount from './messageCount'; import MessageCount from './messageCount';
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
import successImage from '@/assets/svg/success.svg'; import successImage from '@/assets/svg/success.svg';
import starBadge from '@/assets/svg/star-badge.svg';
// Notifications // Notifications
import NEW_STUFF from './notifications/newStuff'; import NEW_STUFF from './notifications/newStuff';
@@ -132,6 +146,8 @@ import VERIFY_USERNAME from './notifications/verifyUsername';
import ACHIEVEMENT_JUST_ADD_WATER from './notifications/justAddWater'; import ACHIEVEMENT_JUST_ADD_WATER from './notifications/justAddWater';
import ACHIEVEMENT_LOST_MASTERCLASSER from './notifications/lostMasterclasser'; import ACHIEVEMENT_LOST_MASTERCLASSER from './notifications/lostMasterclasser';
import ACHIEVEMENT_MIND_OVER_MATTER from './notifications/mindOverMatter'; import ACHIEVEMENT_MIND_OVER_MATTER from './notifications/mindOverMatter';
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
import OnboardingGuide from './onboardingGuide';
export default { export default {
components: { components: {
@@ -158,13 +174,17 @@ export default {
ACHIEVEMENT_MIND_OVER_MATTER, ACHIEVEMENT_MIND_OVER_MATTER,
WorldBoss: WORLD_BOSS, WorldBoss: WORLD_BOSS,
VERIFY_USERNAME, VERIFY_USERNAME,
OnboardingGuide,
ONBOARDING_COMPLETE,
}, },
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
notifications: notificationsIcon, notifications: notificationsIcon,
success: successImage, success: successImage,
starBadge,
}), }),
hasSpecialBadge: false,
quests, quests,
openStatus: undefined, openStatus: undefined,
actionableNotifications: [ actionableNotifications: [
@@ -177,11 +197,11 @@ export default {
handledNotifications: [ handledNotifications: [
'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK', 'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK',
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION', 'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED', 'GROUP_TASK_CLAIMED', 'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED', 'GROUP_TASK_CLAIMED', 'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS', 'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER', 'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER',
'VERIFY_USERNAME', 'VERIFY_USERNAME', 'ONBOARDING_COMPLETE',
], ],
}; };
}, },
@@ -275,6 +295,20 @@ export default {
hasClass () { hasClass () {
return this.$store.getters['members:hasClass'](this.user); return this.$store.getters['members:hasClass'](this.user);
}, },
showOnboardingGuide () {
return hasActiveOnboarding(this.user) && !hasCompletedOnboarding(this.user);
},
},
mounted () {
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
if (
onboardingPanelState !== CONSTANTS.onboardingPanelValues.PANEL_OPENED
&& this.showOnboardingGuide
) {
// The first time the onboarding panel is opened a special
// badge for notifications should be used
this.hasSpecialBadge = true;
}
}, },
methods: { methods: {
...mapActions({ ...mapActions({
@@ -286,6 +320,18 @@ export default {
// Mark notifications as seen when the menu is opened // Mark notifications as seen when the menu is opened
if (openStatus) this.markAllAsSeen(); if (openStatus) this.markAllAsSeen();
// Reset the special notification badge as soon as it's opened
if (this.hasSpecialBadge) {
setLocalSetting(
CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE,
CONSTANTS.onboardingPanelValues.PANEL_OPENED,
);
setTimeout(() => {
this.hasSpecialBadge = false;
}, 100);
}
}, },
markAllAsSeen () { markAllAsSeen () {
const idsToSee = this.notifications.map(notification => { const idsToSee = this.notifications.map(notification => {
@@ -318,5 +364,6 @@ export default {
return this.actionableNotifications.indexOf(notification.type) !== -1; return this.actionableNotifications.indexOf(notification.type) !== -1;
}, },
}, },
}; };
</script> </script>

View File

@@ -0,0 +1,259 @@
<template>
<div
class="onboarding-guide-panel d-flex align-items-center flex-column p-4 dropdown-separated"
@click.stop.prevent="action"
>
<div
class="svg-icon onboarding-toggle"
:class="{'onboarding-toggle-open': open}"
@click="openPanel"
v-html="icons.down"
></div>
<div
class="svg-icon onboarding-guide-banner"
v-html="icons.onboardingGuideBanner"
></div>
<h3 class="getting-started">
{{ $t('gettingStarted') }}
</h3>
<span
class="getting-started-desc"
v-html="$t('gettingStartedDesc')"
></span>
<div
class="onboarding-progress-box d-flex flex-row justify-content-between small-text mb-2"
>
<strong>Your Progress</strong>
<span :class="{'has-progress': progress > 0}">{{ progressText }}</span>
</div>
<div class="onboarding-progress-bar mb-3">
<div
class="onboarding-progress-bar-fill"
:style="{width: `${progress}%`}"
></div>
</div>
<b-collapse
id="onboardingPanelCollapse"
v-model="open"
>
<div
v-for="(achievement, key) in onboardingAchievements"
:key="key"
:class="{
'achievement-earned': achievement.earned
}"
class="achievement-box d-flex flex-row"
>
<div class="achievement-icon-wrapper">
<div :class="`achievement-icon ${getAchievementIcon(achievement)}`"></div>
</div>
<div class="achievement-info d-flex flex-column">
<strong class="achievement-title">{{ achievement.title }}</strong>
<span class="small-text achievement-desc">{{ getAchievementText(key) }}</span>
</div>
</div>
</b-collapse>
</div>
</template>
<style lang='scss' scoped>
@import '~@/assets/scss/colors.scss';
.onboarding-guide-panel {
white-space: normal;
text-align: center;
}
.onboarding-toggle {
cursor: pointer;
position: absolute;
top: 75px;
right: 24px;
width: 16px;
& ::v-deep svg path {
stroke: $gray-200;
}
&:hover ::v-deep svg path {
stroke: $gray-100;
}
&-open {
transform: rotate(-180deg);
}
}
.onboarding-guide-banner {
margin-top: -8px;
margin-bottom: 12px;
width: 154px;
height: 48px;
}
.getting-started {
margin-bottom: 4px;
}
.getting-started-desc {
font-size: 14px;
padding-bottom: 12px;
}
.getting-started-desc ::v-deep .gold-amount {
color: $yellow-5;
}
.onboarding-progress-box {
width: 100%;
font-style: normal;
strong {
color: $gray-50;
}
.has-progress {
color: $yellow-5;
}
}
.onboarding-progress-bar {
width: 100%;
height: 4px;
border-radius: 2px;
background-color: $gray-600;
.onboarding-progress-bar-fill {
height: 100%;
border-radius: 2px;
background: $yellow-5;
}
}
.achievement-box {
padding-top: 11px;
padding-bottom: 12px;
border-bottom: 1px dashed $gray-500;
width: 100%;
&:last-child {
padding-bottom: 0px;
border-bottom: none;
}
}
.achievement-earned {
.achievement-icon-wrapper {
opacity: 1;
}
color: $gray-200;
.achievement-title {
text-decoration: line-through;
}
}
.achievement-icon-wrapper {
opacity: 0.5;
width: 40px;
.achievement-icon {
margin-left: -12px;
transform: scale(0.5);
}
}
.achievement-info {
width: 100%;
text-align: left;
font-size: 14px;
}
.achievement-title {
margin-bottom: 4px;
}
.achievement-desc {
font-style: normal;
}
</style>
<script>
import achievs from '@/../../common/script/libs/achievements';
import { mapState } from '@/libs/store';
import onboardingGuideBanner from '@/assets/svg/onboarding-guide-banner.svg';
import downIcon from '@/assets/svg/down.svg';
export default {
props: {
neverSeen: { // whether it's ever been seen by the user
type: Boolean,
default: false,
},
},
data () {
return {
icons: Object.freeze({
onboardingGuideBanner,
down: downIcon,
}),
open: false,
};
},
computed: {
...mapState({ user: 'user.data' }),
onboardingAchievements () {
return achievs.getAchievementsForProfile(this.user).onboarding.achievements;
},
progress () {
const keys = Object.keys(this.onboardingAchievements);
let nEarned = 0;
keys.forEach(key => {
if (this.onboardingAchievements[key].earned) nEarned += 1;
});
return (nEarned / keys.length) * 100;
},
progressText () {
if (this.progress === 0) {
return this.$t('letsGetStarted');
}
return this.$t('onboardingProgress', { percentage: this.progress });
},
},
created () {
// null means it's never been automatically toggled
if (this.neverSeen === true) {
// The first time the panel should be automatically opened
this.open = true;
}
},
methods: {
getAchievementIcon (achievement) {
if (achievement.earned) {
return `${achievement.icon}2x`;
}
return 'achievement-unearned2x';
},
getAchievementText (key) {
let stringKey = 'achievement';
stringKey += key.charAt(0).toUpperCase() + key.slice(1);
stringKey += 'ModalText';
return this.$t(stringKey);
},
openPanel (e) {
e.preventDefault();
e.stopPropagation();
this.open = !this.open;
},
action () {
// Do nothing, used to prevent closure on click.
},
},
};
</script>

View File

@@ -3,6 +3,11 @@
id="hatchedPet-modal" id="hatchedPet-modal"
:hide-header="true" :hide-header="true"
> >
<span
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<div <div
v-if="pet != null" v-if="pet != null"
class="content" class="content"
@@ -14,7 +19,7 @@
{{ $t('hatchedPetGeneric') }} {{ $t('hatchedPetGeneric') }}
</div> </div>
<div class="inner-content"> <div class="inner-content">
<div class="pet-background"> <div class="pet-background d-flex align-items-center">
<div :class="pet.class"></div> <div :class="pet.class"></div>
</div> </div>
<h4 class="title"> <h4 class="title">
@@ -51,6 +56,10 @@
width: 330px; width: 330px;
} }
.modal-footer {
padding-top: 0px;
}
.content { .content {
text-align: center; text-align: center;
} }
@@ -75,12 +84,25 @@
.dialog-header { .dialog-header {
color: $purple-200; color: $purple-200;
margin-top: 16px;
}
.text {
margin-bottom: 24px;
min-height: 0;
&.markdown {
p {
margin-bottom: 0px;
}
}
} }
} }
</style> </style>
<script> <script>
import markdownDirective from '@/directives/markdown'; import markdownDirective from '@/directives/markdown';
import svgClose from '@/assets/svg/close.svg';
export default { export default {
directives: { directives: {
@@ -94,6 +116,9 @@ export default {
data () { data () {
return { return {
pet: null, pet: null,
icons: Object.freeze({
close: svgClose,
}),
}; };
}, },
mounted () { mounted () {

View File

@@ -26,10 +26,14 @@
<quest-completed /> <quest-completed />
<quest-invitation /> <quest-invitation />
<verify-username /> <verify-username />
<generic-achievement :data="notificationData" /> <generic-achievement
v-if="notificationData && notificationData.achievement"
:data="notificationData"
/>
<just-add-water /> <just-add-water />
<lost-masterclasser /> <lost-masterclasser />
<mind-over-matter /> <mind-over-matter />
<onboarding-complete />
</div> </div>
</template> </template>
@@ -92,6 +96,10 @@
background-color: #4f2a93 !important; background-color: #4f2a93 !important;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12) !important; box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12) !important;
} }
.introjs-skipbutton.btn-primary, .introjs-donebutton.btn-primary {
color: #fff;
}
</style> </style>
<script> <script>
@@ -99,9 +107,11 @@ import axios from 'axios';
import moment from 'moment'; import moment from 'moment';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import Vue from 'vue';
import { toNextLevel } from '@/../../common/script/statHelpers'; import { toNextLevel } from '@/../../common/script/statHelpers';
import { shouldDo } from '@/../../common/script/cron'; import { shouldDo } from '@/../../common/script/cron';
import { onOnboardingComplete } from '@/../../common/script/libs/onboarding';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import notifications from '@/mixins/notifications'; import notifications from '@/mixins/notifications';
import guide from '@/mixins/guide'; import guide from '@/mixins/guide';
@@ -132,6 +142,7 @@ import justAddWater from './achievements/justAddWater';
import lostMasterclasser from './achievements/lostMasterclasser'; import lostMasterclasser from './achievements/lostMasterclasser';
import mindOverMatter from './achievements/mindOverMatter'; import mindOverMatter from './achievements/mindOverMatter';
import loginIncentives from './achievements/login-incentives'; import loginIncentives from './achievements/login-incentives';
import onboardingComplete from './achievements/onboardingComplete';
import verifyUsername from './settings/verifyUsername'; import verifyUsername from './settings/verifyUsername';
const NOTIFICATIONS = { const NOTIFICATIONS = {
@@ -164,21 +175,33 @@ const NOTIFICATIONS = {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementAllYourBase')}`, label: $t => `${$t('achievement')}: ${$t('achievementAllYourBase')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'allYourBase', // defined manually until the server sends all the necessary data
},
}, },
ACHIEVEMENT_BACK_TO_BASICS: { ACHIEVEMENT_BACK_TO_BASICS: {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementBackToBasics')}`, label: $t => `${$t('achievement')}: ${$t('achievementBackToBasics')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'backToBasics', // defined manually until the server sends all the necessary data
},
}, },
ACHIEVEMENT_DUST_DEVIL: { ACHIEVEMENT_DUST_DEVIL: {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementDustDevil')}`, label: $t => `${$t('achievement')}: ${$t('achievementDustDevil')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'dustDevil', // defined manually until the server sends all the necessary data
},
}, },
ACHIEVEMENT_ARID_AUTHORITY: { ACHIEVEMENT_ARID_AUTHORITY: {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementAridAuthority')}`, label: $t => `${$t('achievement')}: ${$t('achievementAridAuthority')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'aridAuthority', // defined manually until the server sends all the necessary data
},
}, },
ACHIEVEMENT_PARTY_UP: { ACHIEVEMENT_PARTY_UP: {
achievement: true, achievement: true,
@@ -187,6 +210,7 @@ const NOTIFICATIONS = {
data: { data: {
message: $t => $t('achievement'), message: $t => $t('achievement'),
modalText: $t => $t('achievementPartyUp'), modalText: $t => $t('achievementPartyUp'),
achievement: 'partyUp', // defined manually until the server sends all the necessary data
}, },
}, },
ACHIEVEMENT_PARTY_ON: { ACHIEVEMENT_PARTY_ON: {
@@ -196,6 +220,7 @@ const NOTIFICATIONS = {
data: { data: {
message: $t => $t('achievement'), message: $t => $t('achievement'),
modalText: $t => $t('achievementPartyOn'), modalText: $t => $t('achievementPartyOn'),
achievement: 'partyOn', // defined manually until the server sends all the necessary data
}, },
}, },
ACHIEVEMENT_BEAST_MASTER: { ACHIEVEMENT_BEAST_MASTER: {
@@ -205,6 +230,7 @@ const NOTIFICATIONS = {
data: { data: {
message: $t => $t('achievement'), message: $t => $t('achievement'),
modalText: $t => $t('beastAchievement'), modalText: $t => $t('beastAchievement'),
achievement: 'beastMaster', // defined manually until the server sends all the necessary data
}, },
}, },
ACHIEVEMENT_MOUNT_MASTER: { ACHIEVEMENT_MOUNT_MASTER: {
@@ -214,6 +240,7 @@ const NOTIFICATIONS = {
data: { data: {
message: $t => $t('achievement'), message: $t => $t('achievement'),
modalText: $t => $t('mountAchievement'), modalText: $t => $t('mountAchievement'),
achievement: 'mountMaster', // defined manually until the server sends all the necessary data
}, },
}, },
ACHIEVEMENT_TRIAD_BINGO: { ACHIEVEMENT_TRIAD_BINGO: {
@@ -223,27 +250,49 @@ const NOTIFICATIONS = {
data: { data: {
message: $t => $t('achievement'), message: $t => $t('achievement'),
modalText: $t => $t('triadBingoAchievement'), modalText: $t => $t('triadBingoAchievement'),
achievement: 'triadBingo', // defined manually until the server sends all the necessary data
}, },
}, },
ACHIEVEMENT_MONSTER_MAGUS: { ACHIEVEMENT_MONSTER_MAGUS: {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementMonsterMagus')}`, label: $t => `${$t('achievement')}: ${$t('achievementMonsterMagus')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'monsterMagus', // defined manually until the server sends all the necessary data
},
}, },
ACHIEVEMENT_UNDEAD_UNDERTAKER: { ACHIEVEMENT_UNDEAD_UNDERTAKER: {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementUndeadUndertaker')}`, label: $t => `${$t('achievement')}: ${$t('achievementUndeadUndertaker')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'undeadUndertaker', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT: { // data filled in handleUserNotifications
achievement: true,
modalId: 'generic-achievement',
label: null, // data filled in handleUserNotifications
data: {
message: $t => $t('achievement'),
modalText: null, // data filled in handleUserNotifications
},
}, },
ACHIEVEMENT_PRIMED_FOR_PAINTING: { ACHIEVEMENT_PRIMED_FOR_PAINTING: {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementPrimedForPainting')}`, label: $t => `${$t('achievement')}: ${$t('achievementPrimedForPainting')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'primedForPainting', // defined manually until the server sends all the necessary data
},
}, },
ACHIEVEMENT_PEARLY_PRO: { ACHIEVEMENT_PEARLY_PRO: {
achievement: true, achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementPearlyPro')}`, label: $t => `${$t('achievement')}: ${$t('achievementPearlyPro')}`,
modalId: 'generic-achievement', modalId: 'generic-achievement',
data: {
achievement: 'pearlyPro', // defined manually until the server sends all the necessary data
},
}, },
}; };
@@ -276,6 +325,7 @@ export default {
lostMasterclasser, lostMasterclasser,
mindOverMatter, mindOverMatter,
justAddWater, justAddWater,
onboardingComplete,
}, },
mixins: [notifications, guide], mixins: [notifications, guide],
data () { data () {
@@ -302,7 +352,7 @@ export default {
'GENERIC_ACHIEVEMENT', 'ACHIEVEMENT_PARTY_UP', 'ACHIEVEMENT_PARTY_ON', 'ACHIEVEMENT_BEAST_MASTER', 'GENERIC_ACHIEVEMENT', 'ACHIEVEMENT_PARTY_UP', 'ACHIEVEMENT_PARTY_ON', 'ACHIEVEMENT_BEAST_MASTER',
'ACHIEVEMENT_MOUNT_MASTER', 'ACHIEVEMENT_TRIAD_BINGO', 'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY', 'ACHIEVEMENT_MOUNT_MASTER', 'ACHIEVEMENT_TRIAD_BINGO', 'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY',
'ACHIEVEMENT_MONSTER_MAGUS', 'ACHIEVEMENT_UNDEAD_UNDERTAKER', 'ACHIEVEMENT_PRIMED_FOR_PAINTING', 'ACHIEVEMENT_MONSTER_MAGUS', 'ACHIEVEMENT_UNDEAD_UNDERTAKER', 'ACHIEVEMENT_PRIMED_FOR_PAINTING',
'ACHIEVEMENT_PEARLY_PRO', 'GENERIC_ACHIEVEMENT', 'ACHIEVEMENT_PEARLY_PRO', 'ACHIEVEMENT', 'ONBOARDING_COMPLETE',
].forEach(type => { ].forEach(type => {
handledNotifications[type] = true; handledNotifications[type] = true;
}); });
@@ -478,7 +528,7 @@ export default {
this.text(config.label(this.$t), () => { this.text(config.label(this.$t), () => {
this.notificationData = data; this.notificationData = data;
this.$root.$emit('bv::show::modal', config.modalId); this.$root.$emit('bv::show::modal', config.modalId);
}, false); }, true, 10000);
} }
}, },
debounceCheckUserAchievements: debounce(function debounceCheck () { debounceCheckUserAchievements: debounce(function debounceCheck () {
@@ -713,6 +763,18 @@ export default {
case 'GENERIC_ACHIEVEMENT': case 'GENERIC_ACHIEVEMENT':
this.showNotificationWithModal(notification); this.showNotificationWithModal(notification);
break; break;
case 'ACHIEVEMENT': { // generic achievement
const { achievement } = notification.data;
const upperCaseAchievement = achievement.charAt(0).toUpperCase() + achievement.slice(1);
const achievementTitleKey = `achievement${upperCaseAchievement}`;
NOTIFICATIONS.ACHIEVEMENT.label = $t => `${$t('achievement')}: ${$t(achievementTitleKey)}`;
NOTIFICATIONS.ACHIEVEMENT.data.modalText = $t => $t(achievementTitleKey);
this.showNotificationWithModal(notification);
// Set the achievement as it's not defined in the user schema
Vue.set(this.user.achievements, achievement, true);
break;
}
case 'CRON': case 'CRON':
if (notification.data) { if (notification.data) {
if (notification.data.hp) this.hp(notification.data.hp, 'hp'); if (notification.data.hp) this.hp(notification.data.hp, 'hp');
@@ -742,6 +804,20 @@ export default {
this.$root.$emit('bv::show::modal', 'login-incentives'); this.$root.$emit('bv::show::modal', 'login-incentives');
} }
break; break;
case 'ONBOARDING_COMPLETE':
// Award rewards
onOnboardingComplete(this.user);
// If the user cronned in the last 3 minutes
// Don't show too many modals on app load
// Use notification panel
if (moment().diff(this.user.lastCron, 'minutes') < 3) {
markAsRead = false;
} else {
// Otherwise use the modal
this.$root.$emit('bv::show::modal', 'onboarding-complete');
}
break;
} }
if (markAsRead) notificationsToRead.push(notification.id); if (markAsRead) notificationsToRead.push(notification.id);

View File

@@ -50,8 +50,6 @@ A simplified dropdown component that doesn't rely on buttons as toggles like bo
.dropdown-menu { .dropdown-menu {
cursor: auto; cursor: auto;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12); box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
max-height: calc(100vh - 100px);
overflow: auto;
left: inherit; left: inherit;
right: 0px !important; right: 0px !important;

View File

@@ -1,116 +0,0 @@
<template>
<div class="standard-page container">
<div
v-for="(category, key) in achievements"
:key="key"
class="row"
>
<h2 class="col-12">
{{ $t(key+'Achievs') }}
</h2>
<div
v-for="(achievment, achievmentKey) in category.achievements"
:key="achievmentKey"
class="col-3 text-center"
>
<div
class="achievement-container"
:data-popover-html="achievment.title + achievment.text"
popover-placement="achievPopoverPlacement"
popover-append-to-body="achievAppendToBody"
>
<div
popover-trigger="mouseenter"
:data-popover-html="achievment.title + achievment.text"
popover-placement="achievPopoverPlacement"
popover-append-to-body="achievAppendToBody"
>
<div
v-if="achievment.earned"
class="achievement"
:class="achievment.icon + '2x'"
>
<div
v-if="achievment.optionalCount"
class="counter badge badge-info stack-count"
>
{{ achievment.optionalCount }}
</div>
</div>
<div
v-if="!achievment.earned"
class="achievement achievement-unearned2x"
></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<h2>Challeges Won</h2>
<div
v-for="chal in user.achievements.challenges"
:key="chal"
>
<span>{{ chal }}</span>
</div>
</div>
<div class="col-6">
<h2>Quests Completed</h2>
<div
v-for="(value, key) in user.achievements.quests"
:key="key"
>
<span>{{ content.quests[k].text() }}</span>
<span>{{ value }}</span>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
h2 {
margin-top: 2em;
}
.achievement-container {
margin-bottom: 1em;
padding: 2em;
background: #fff;
}
.achievement {
margin: 0 auto;
}
.counter.badge {
color: #fff;
position: absolute;
top: 0;
right: 0;
background-color: #ff944c;
}
</style>
<script>
import { mapState } from '@/libs/store';
import achievementsLib from '@/../../common/script/libs/achievements';
import Content from '@/../../common/script/content';
export default {
data () {
return {
achievements: {},
content: Content,
};
},
computed: {
...mapState({ user: 'user.data' }),
},
mounted () {
this.achievements = achievementsLib.getAchievementsForProfile(this.user);
},
};
</script>

View File

@@ -300,50 +300,64 @@
<div <div
v-for="(category, key) in achievements" v-for="(category, key) in achievements"
:key="key" :key="key"
class="row" class="row category-row"
> >
<h2 class="col-12 text-center"> <h3 class="col-12 text-center mb-3">
{{ $t(key+'Achievs') }} {{ $t(key+'Achievs') }}
</h2> </h3>
<div <div class="col-12">
v-for="(achievement, achievKey) in category.achievements" <div class="row achievements-row justify-content-center">
:key="achievKey"
class="col-12 col-md-3 text-center"
>
<div
:id="achievKey + '-achievement'"
class="box achievement-container"
:class="{'achievement-unearned': !achievement.earned}"
>
<b-popover
:target="'#' + achievKey + '-achievement'"
triggers="hover"
placement="top"
>
<h4 class="popover-content-title">
{{ achievement.title }}
</h4>
<div
class="popover-content-text"
v-html="achievement.text"
></div>
</b-popover>
<div <div
v-if="achievement.earned" v-for="(achievement, achievKey) in achievementsCategory(key, category)"
class="achievement" :key="achievKey"
:class="achievement.icon + '2x'" class="achievement-wrapper col text-center"
> >
<div <div
v-if="achievement.optionalCount" :id="achievKey + '-achievement'"
class="counter badge badge-info stack-count" class="box achievement-container"
:class="{'achievement-unearned': !achievement.earned}"
> >
{{ achievement.optionalCount }} <b-popover
:target="'#' + achievKey + '-achievement'"
triggers="hover"
placement="top"
>
<h4 class="popover-content-title">
{{ achievement.title }}
</h4>
<div
class="popover-content-text"
v-html="achievement.text"
></div>
</b-popover>
<div
v-if="achievement.earned"
class="achievement"
:class="achievement.icon + '2x'"
>
<div
v-if="achievement.optionalCount"
class="counter badge badge-pill stack-count"
>
{{ achievement.optionalCount }}
</div>
</div>
<div
v-if="!achievement.earned"
class="achievement achievement-unearned achievement-unearned2x"
></div>
</div> </div>
</div> </div>
<div <div
v-if="!achievement.earned" v-if="achievementsCategories[key].number > 5"
class="achievement achievement-unearned achievement-unearned2x" class="btn btn-flat btn-show-more"
></div> @click="toggleAchievementsCategory(key)"
>
{{ achievementsCategories[key].open ?
$t('hideAchievements', {category: $t(key+'Achievs')}) :
$t('showAllAchievements', {category: $t(key+'Achievs')})
}}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -353,31 +367,38 @@
v-if="user.achievements.challenges" v-if="user.achievements.challenges"
class="col-12 col-md-6" class="col-12 col-md-6"
> >
<div class="achievement-icon achievement-karaoke"></div> <div class="achievement-icon achievement-karaoke-2x"></div>
<h2 class="text-center"> <h3 class="text-center mt-2 mb-4">
{{ $t('challengesWon') }} {{ $t('challengesWon') }}
</h2> </h3>
<div <div
v-for="chal in user.achievements.challenges" v-for="chal in user.achievements.challenges"
:key="chal" :key="chal"
class="achievement-list-item"
> >
<span v-markdown="chal"></span> <span v-markdown="chal"></span>
<hr>
</div> </div>
</div> </div>
<div <div
v-if="user.achievements.quests" v-if="user.achievements.quests"
class="col-12 col-md-6" class="col-12 col-md-6"
> >
<div class="achievement-icon achievement-alien"></div> <div class="achievement-icon achievement-alien2x"></div>
<h2 class="text-center"> <h3 class="text-center mt-2 mb-4">
{{ $t('questsCompleted') }} {{ $t('questsCompleted') }}
</h2> </h3>
<div <div
v-for="(value, key) in user.achievements.quests" v-for="(value, key) in user.achievements.quests"
:key="key" :key="key"
class="achievement-list-item d-flex justify-content-between"
> >
<span>{{ content.quests[key].text() }} ({{ value }})</span> <span>{{ content.quests[key].text() }}</span>
<span
v-if="value > 1"
class="badge badge-pill stack-count"
>
{{ value }}
</span>
</div> </div>
</div> </div>
</div> </div>
@@ -519,37 +540,71 @@
} }
#achievements { #achievements {
.category-row {
margin-bottom: 34px;
&:last-child {
margin-bottom: 0px;
}
}
.achievements-row {
max-width: 590px;
margin: 0 auto;
}
.achievement-wrapper {
width: 94px;
max-width: 94px;
margin-right: 12px;
margin-left: 12px;
padding: 0px;
}
.box { .box {
margin: 0 auto; margin: 0 auto;
margin-top: 1em; margin-bottom: 1em;
padding-top: 1.2em; padding-top: 1.2em;
background: #fff; background: $white;
}
hr {
margin-bottom: 48px;
margin-top: 48px;
} }
.box.achievement-unearned { .box.achievement-unearned {
background-color: #edecee; background-color: $gray-600;
}
h2 {
margin-top: 1em;
} }
.counter.badge { .counter.badge {
position: absolute; position: absolute;
top: .5em; top: -0.8em;
right: 3em; right: -0.5em;
color: #fff; color: $white;
background-color: #ff944c; background-color: $orange-100;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12); max-height: 24px;
min-width: 24px;
min-height: 24px;
border-radius: 2em;
padding: .5em;
} }
.achievement-icon { .achievement-icon {
margin: 0 auto; margin: 0 auto;
} }
.achievement-list-item {
padding-top: 11px;
padding-bottom: 12px;
border-top: 1px solid $gray-500;
&:last-child {
border-bottom: 1px solid $gray-500;
}
.badge {
margin-right: 8px;
background: $gray-600;
color: $gray-300;
}
}
} }
.achievement { .achievement {
@@ -699,6 +754,7 @@ export default {
}, },
selectedPage: 'profile', selectedPage: 'profile',
achievements: {}, achievements: {},
achievementsCategories: {}, // number, open
content: Content, content: Content,
user: undefined, user: undefined,
}; };
@@ -783,6 +839,16 @@ export default {
// @TODO: this common code should handle the above // @TODO: this common code should handle the above
this.achievements = achievementsLib.getAchievementsForProfile(user); this.achievements = achievementsLib.getAchievementsForProfile(user);
const achievementsCategories = {};
Object.keys(this.achievements).forEach(category => {
achievementsCategories[category] = {
open: false,
number: Object.keys(this.achievements[category].achievements).length,
};
});
this.achievementsCategories = achievementsCategories;
// @TODO For some reason markdown doesn't seem to be handling numbers or maybe undefined? // @TODO For some reason markdown doesn't seem to be handling numbers or maybe undefined?
user.profile.blurb = user.profile.blurb ? `${user.profile.blurb}` : ''; user.profile.blurb = user.profile.blurb ? `${user.profile.blurb}` : '';
@@ -906,6 +972,27 @@ export default {
showAllocation () { showAllocation () {
return this.user._id === this.userLoggedIn._id && this.hasClass; return this.user._id === this.userLoggedIn._id && this.hasClass;
}, },
achievementsCategory (categoryKey, category) {
const achievementsKeys = Object.keys(category.achievements);
if (this.achievementsCategories[categoryKey].open === true) {
return category.achievements;
}
const fiveAchievements = achievementsKeys.slice(0, 5);
const categoryAchievements = {};
fiveAchievements.forEach(key => {
categoryAchievements[key] = category.achievements[key];
});
return categoryAchievements;
},
toggleAchievementsCategory (categoryKey) {
const status = this.achievementsCategories[categoryKey].open;
this.achievementsCategories[categoryKey].open = !status;
},
}, },
}; };
</script> </script>

View File

@@ -5,6 +5,7 @@ const CONSTANTS = {
EQUIPMENT_DRAWER_STATE: 'equipment-drawer-state', EQUIPMENT_DRAWER_STATE: 'equipment-drawer-state',
CURRENT_EQUIPMENT_DRAWER_TAB: 'current-equipment-drawer-tab', CURRENT_EQUIPMENT_DRAWER_TAB: 'current-equipment-drawer-tab',
STABLE_SORT_STATE: 'stable-sort-state', STABLE_SORT_STATE: 'stable-sort-state',
ONBOARDING_PANEL_STATE: 'onboarding-panel-state',
}, },
drawerStateValues: { drawerStateValues: {
DRAWER_CLOSED: 'drawer-closed', DRAWER_CLOSED: 'drawer-closed',
@@ -17,6 +18,9 @@ const CONSTANTS = {
savedAppStateValues: { savedAppStateValues: {
SAVED_APP_STATE: 'saved-app-state', SAVED_APP_STATE: 'saved-app-state',
}, },
onboardingPanelValues: {
PANEL_OPENED: 'onboarding-panel-opened',
},
}; };
function setLocalSetting (key, value) { function setLocalSetting (key, value) {

View File

@@ -63,9 +63,9 @@ export default {
streak (val, onClick) { streak (val, onClick) {
this.notify(`${val}`, 'streak', null, null, onClick, typeof onClick === 'undefined'); this.notify(`${val}`, 'streak', null, null, onClick, typeof onClick === 'undefined');
}, },
text (val, onClick, timeout) { text (val, onClick, timeout, delay) {
if (!val) return; if (!val) return;
this.notify(val, 'info', null, null, onClick, timeout); this.notify(val, 'info', null, null, onClick, timeout, delay);
}, },
sign (number) { sign (number) {
return getSign(number); return getSign(number);
@@ -73,7 +73,7 @@ export default {
round (number, nDigits) { round (number, nDigits) {
return round(number, nDigits); return round(number, nDigits);
}, },
notify (html, type, icon, sign, onClick, timeout = true) { notify (html, type, icon, sign, onClick, timeout = true, delay) {
this.$store.dispatch('snackbars:add', { this.$store.dispatch('snackbars:add', {
title: '', title: '',
text: html, text: html,
@@ -82,6 +82,7 @@ export default {
sign, sign,
onClick, onClick,
timeout, timeout,
delay,
}); });
}, },
}, },

View File

@@ -30,6 +30,7 @@ describe('shops actions', () => {
preferences: { preferences: {
autoEquip: true, autoEquip: true,
}, },
achievements: {},
}; };
store.state.user.data = user; store.state.user.data = user;

View File

@@ -4,6 +4,14 @@
"onwards": "Onwards!", "onwards": "Onwards!",
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!", "levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
"reachedLevel": "You Reached Level <%= level %>", "reachedLevel": "You Reached Level <%= level %>",
"gettingStartedDesc": "Lets create a task, complete it, then check out your rewards. Youll earn <strong>5 achievements</strong> and <strong class=\"gold-amount\">100 gold</strong> once youre done!",
"onboardingProgress": "<%= percentage %>% progress",
"letsGetStarted": "Let's get started!",
"viewAchievements": "View Achievements",
"earnedAchievement": "You earned an achievement!",
"onboardingCompleteDesc": "You earned <strong>5 achievements</strong> and <strong class=\"gold-amount\">100</strong> gold for completing the list.",
"showAllAchievements": "Show All <%= category %>",
"hideAchievements": "Hide <%= category %>",
"achievementLostMasterclasser": "Quest Completionist: Masterclasser Series", "achievementLostMasterclasser": "Quest Completionist: Masterclasser Series",
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!", "achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
"achievementLostMasterclasserModalText": "You completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!", "achievementLostMasterclasserModalText": "You completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
@@ -35,6 +43,21 @@
"achievementUndeadUndertaker": "Undead Undertaker", "achievementUndeadUndertaker": "Undead Undertaker",
"achievementUndeadUndertakerText": "Has tamed all Zombie Mounts.", "achievementUndeadUndertakerText": "Has tamed all Zombie Mounts.",
"achievementUndeadUndertakerModalText": "You tamed all the Zombie Mounts!", "achievementUndeadUndertakerModalText": "You tamed all the Zombie Mounts!",
"achievementCreatedTask": "Create a Task",
"achievementCreatedTaskText": "Created their first task.",
"achievementCreatedTaskModalText": "Add a task for something you would like to accomplish this week",
"achievementCompletedTask": "Complete a Task",
"achievementCompletedTaskText": "Completed their first task.",
"achievementCompletedTaskModalText": "Check off any of your tasks to earn rewards",
"achievementHatchedPet": "Hatch a Pet",
"achievementHatchedPetText": "Hatched their first pet.",
"achievementHatchedPetModalText": "Head over to your inventory and try combining a hatching potion and an egg",
"achievementFedPet": "Feed a Pet",
"achievementFedPetText": "Fed their first pet.",
"achievementFedPetModalText": "There are many different types of food, but pets can be picky",
"achievementPurchasedEquipment": "Purchase Equipment",
"achievementPurchasedEquipmentText": "Purchased their first piece of equipment.",
"achievementPurchasedEquipmentModalText": "Equipment is a way to customize your avatar and improve your stats",
"achievementPrimedForPainting": "Primed for Painting", "achievementPrimedForPainting": "Primed for Painting",
"achievementPrimedForPaintingText": "Has collected all White Pets.", "achievementPrimedForPaintingText": "Has collected all White Pets.",
"achievementPrimedForPaintingModalText": "You collected all the White Pets!", "achievementPrimedForPaintingModalText": "You collected all the White Pets!",

View File

@@ -71,7 +71,6 @@
"noPermissionEditChallenge": "You don't have permissions to edit this challenge", "noPermissionEditChallenge": "You don't have permissions to edit this challenge",
"noPermissionDeleteChallenge": "You don't have permissions to delete this challenge", "noPermissionDeleteChallenge": "You don't have permissions to delete this challenge",
"noPermissionCloseChallenge": "You don't have permissions to close this challenge", "noPermissionCloseChallenge": "You don't have permissions to close this challenge",
"congratulations": "Congratulations!",
"hurray": "Hurray!", "hurray": "Hurray!",
"noChallengeOwner": "no owner", "noChallengeOwner": "no owner",
"noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account.", "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account.",

View File

@@ -57,6 +57,7 @@
"basicAchievs": "Basic Achievements", "basicAchievs": "Basic Achievements",
"seasonalAchievs": "Seasonal Achievements", "seasonalAchievs": "Seasonal Achievements",
"specialAchievs": "Special Achievements", "specialAchievs": "Special Achievements",
"onboardingAchievs": "Onboarding Achievements",
"modalAchievement": "Achievement!", "modalAchievement": "Achievement!",
"special": "Special", "special": "Special",
"site": "Site", "site": "Site",
@@ -79,6 +80,7 @@
"saveAndClose": "Save & Close", "saveAndClose": "Save & Close",
"saveAndConfirm": "Save & Confirm", "saveAndConfirm": "Save & Confirm",
"cancel": "Cancel", "cancel": "Cancel",
"congratulations": "Congratulations!",
"ok": "OK", "ok": "OK",
"add": "Add", "add": "Add",
"undo": "Undo", "undo": "Undo",

View File

@@ -180,6 +180,35 @@ const basicAchievs = {
}; };
Object.assign(achievementsData, basicAchievs); Object.assign(achievementsData, basicAchievs);
const onboardingAchievs = {
createdTask: {
icon: 'achievement-createdTask',
titleKey: 'achievementCreatedTask',
textKey: 'achievementCreatedTaskText',
},
completedTask: {
icon: 'achievement-completedTask',
titleKey: 'achievementCompletedTask',
textKey: 'achievementCompletedTaskText',
},
hatchedPet: {
icon: 'achievement-hatchedPet',
titleKey: 'achievementHatchedPet',
textKey: 'achievementHatchedPetText',
},
fedPet: {
icon: 'achievement-fedPet',
titleKey: 'achievementFedPet',
textKey: 'achievementFedPetText',
},
purchasedEquipment: {
icon: 'achievement-purchasedEquipment',
titleKey: 'achievementPurchasedEquipment',
textKey: 'achievementPurchasedEquipmentText',
},
};
Object.assign(achievementsData, onboardingAchievs);
const specialAchievs = { const specialAchievs = {
contributor: { contributor: {
icon: 'achievement-boot', icon: 'achievement-boot',

View File

@@ -1,82 +1,29 @@
// When using a common module from the website or the server NEVER import the module directly // When using a common module from the website or the server NEVER import the module directly
// but access it through `api` (the main common) module, // but access it through `api` (the main common) module,
// otherwise you would require the non transpiled version of the file in production. // otherwise you would require the non transpiled version of the file in production.
import content from './content/index';
import * as errors from './libs/errors';
import i18n from './i18n';
import commonErrors from './errors/commonErrorMessages';
import apiErrors from './errors/apiErrorMessages';
// TODO under api.libs.cron?
import { shouldDo, daysSince, DAY_MAPPING } from './cron';
import { import {
MAX_HEALTH,
MAX_LEVEL,
MAX_STAT_POINTS,
MAX_INCENTIVES,
TAVERN_ID,
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
MAX_SUMMARY_SIZE_FOR_GUILDS,
MAX_SUMMARY_SIZE_FOR_CHALLENGES,
MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
SUPPORTED_SOCIAL_NETWORKS,
GUILDS_PER_PAGE,
PARTY_LIMIT_MEMBERS,
CHAT_FLAG_LIMIT_FOR_HIDING,
CHAT_FLAG_FROM_MOD, CHAT_FLAG_FROM_MOD,
CHAT_FLAG_FROM_SHADOW_MUTE, CHAT_FLAG_FROM_SHADOW_MUTE,
CHAT_FLAG_LIMIT_FOR_HIDING,
GUILDS_PER_PAGE,
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
MAX_HEALTH,
MAX_INCENTIVES,
MAX_LEVEL,
MAX_STAT_POINTS,
MAX_SUMMARY_SIZE_FOR_CHALLENGES,
MAX_SUMMARY_SIZE_FOR_GUILDS,
MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
PARTY_LIMIT_MEMBERS,
SUPPORTED_SOCIAL_NETWORKS,
TAVERN_ID,
} from './constants'; } from './constants';
import content from './content/index';
// TODO under api.libs.statHelpers?
import * as statHelpers from './statHelpers';
import splitWhitespace from './libs/splitWhitespace';
import refPush from './libs/refPush';
import planGemLimits from './libs/planGemLimits';
import preenTodos from './libs/preenTodos';
import updateStore from './libs/updateStore';
import inAppRewards from './libs/inAppRewards';
import setDebuffPotionItems from './libs/setDebuffPotionItems';
import getDebuffPotionItems from './libs/getDebuffPotionItems';
import uuid from './libs/uuid';
import taskDefaults from './libs/taskDefaults';
import percent from './libs/percent';
import gold from './libs/gold';
import silver from './libs/silver';
import noTags from './libs/noTags';
import appliedTags from './libs/appliedTags';
import pickDeep from './libs/pickDeep';
import * as count from './count'; import * as count from './count';
// TODO under api.libs.cron?
import statsComputed from './libs/statsComputed'; import { daysSince, DAY_MAPPING, shouldDo } from './cron';
import apiErrors from './errors/apiErrorMessages';
import shops from './libs/shops'; import commonErrors from './errors/commonErrorMessages';
import achievements from './libs/achievements';
import randomVal from './libs/randomVal';
import hasClass from './libs/hasClass';
import autoAllocate from './fns/autoAllocate'; import autoAllocate from './fns/autoAllocate';
import crit from './fns/crit'; import crit from './fns/crit';
import handleTwoHanded from './fns/handleTwoHanded'; import handleTwoHanded from './fns/handleTwoHanded';
@@ -85,33 +32,59 @@ import randomDrop from './fns/randomDrop';
import resetGear from './fns/resetGear'; import resetGear from './fns/resetGear';
import ultimateGear from './fns/ultimateGear'; import ultimateGear from './fns/ultimateGear';
import updateStats from './fns/updateStats'; import updateStats from './fns/updateStats';
import i18n from './i18n';
import scoreTask from './ops/scoreTask'; import achievements from './libs/achievements';
import sleep from './ops/sleep'; import appliedTags from './libs/appliedTags';
import allocateNow from './ops/stats/allocateNow'; import * as errors from './libs/errors';
import allocate from './ops/stats/allocate'; import getDebuffPotionItems from './libs/getDebuffPotionItems';
import allocateBulk from './ops/stats/allocateBulk'; import gold from './libs/gold';
import hasClass from './libs/hasClass';
import inAppRewards from './libs/inAppRewards';
import noTags from './libs/noTags';
import * as onboarding from './libs/onboarding';
import percent from './libs/percent';
import pickDeep from './libs/pickDeep';
import planGemLimits from './libs/planGemLimits';
import preenTodos from './libs/preenTodos';
import randomVal from './libs/randomVal';
import refPush from './libs/refPush';
import setDebuffPotionItems from './libs/setDebuffPotionItems';
import shops from './libs/shops';
import silver from './libs/silver';
import splitWhitespace from './libs/splitWhitespace';
import statsComputed from './libs/statsComputed';
import taskDefaults from './libs/taskDefaults';
import updateStore from './libs/updateStore';
import uuid from './libs/uuid';
import blockUser from './ops/blockUser';
import buy from './ops/buy/buy'; import buy from './ops/buy/buy';
import hatch from './ops/hatch';
import feed from './ops/feed';
import equip from './ops/equip';
import changeClass from './ops/changeClass'; import changeClass from './ops/changeClass';
import disableClasses from './ops/disableClasses'; import disableClasses from './ops/disableClasses';
import readCard from './ops/readCard'; import equip from './ops/equip';
import feed from './ops/feed';
import hatch from './ops/hatch';
import markPmsRead from './ops/markPMSRead';
import openMysteryItem from './ops/openMysteryItem'; import openMysteryItem from './ops/openMysteryItem';
import releasePets from './ops/releasePets'; import * as pinnedGearUtils from './ops/pinnedGearUtils';
import readCard from './ops/readCard';
import rebirth from './ops/rebirth';
import releaseBoth from './ops/releaseBoth'; import releaseBoth from './ops/releaseBoth';
import releaseMounts from './ops/releaseMounts'; import releaseMounts from './ops/releaseMounts';
import updateTask from './ops/updateTask'; import releasePets from './ops/releasePets';
import sell from './ops/sell';
import unlock from './ops/unlock';
import revive from './ops/revive';
import rebirth from './ops/rebirth';
import blockUser from './ops/blockUser';
import reroll from './ops/reroll'; import reroll from './ops/reroll';
import reset from './ops/reset'; import reset from './ops/reset';
import markPmsRead from './ops/markPMSRead'; import revive from './ops/revive';
import * as pinnedGearUtils from './ops/pinnedGearUtils'; import scoreTask from './ops/scoreTask';
import sell from './ops/sell';
import sleep from './ops/sleep';
import allocate from './ops/stats/allocate';
import allocateBulk from './ops/stats/allocateBulk';
import allocateNow from './ops/stats/allocateNow';
import unlock from './ops/unlock';
import updateTask from './ops/updateTask';
// TODO under api.libs.statHelpers?
import * as statHelpers from './statHelpers';
const api = {}; const api = {};
api.content = content; api.content = content;
@@ -162,10 +135,10 @@ api.shops = shops;
api.achievements = achievements; api.achievements = achievements;
api.randomVal = randomVal; api.randomVal = randomVal;
api.hasClass = hasClass; api.hasClass = hasClass;
api.onboarding = onboarding;
api.setDebuffPotionItems = setDebuffPotionItems; api.setDebuffPotionItems = setDebuffPotionItems;
api.getDebuffPotionItems = getDebuffPotionItems; api.getDebuffPotionItems = getDebuffPotionItems;
api.fns = { api.fns = {
autoAllocate, autoAllocate,
crit, crit,

View File

@@ -241,6 +241,18 @@ function _getBasicAchievements (user, language) {
return result; 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) { function _getSeasonalAchievements (user, language) {
const result = {}; const result = {};
@@ -321,6 +333,10 @@ achievs.getAchievementsForProfile = function getAchievementsForProfile (user, la
label: 'Basic', label: 'Basic',
achievements: _getBasicAchievements(user, language), achievements: _getBasicAchievements(user, language),
}, },
onboarding: {
label: 'Onboarding',
achievements: _getOnboardingAchievements(user, language),
},
seasonal: { seasonal: {
label: 'Seasonal', label: 'Seasonal',
achievements: _getSeasonalAchievements(user, language), achievements: _getSeasonalAchievements(user, language),

View File

@@ -0,0 +1,31 @@
import moment from 'moment';
const BEGIN_DATE = moment('2019-12-18');
// Only users that signed up after the BEGIN DATE should see the onboarding
export function hasActiveOnboarding (user) {
return BEGIN_DATE.isBefore(user.auth.timestamps.created);
}
export function hasCompletedOnboarding (user) {
return (
user.achievements.createdTask === true
&& user.achievements.completedTask === true
&& user.achievements.hatchedPet === true
&& user.achievements.fedPet === true
&& user.achievements.purchasedEquipment === true
);
}
export function onOnboardingComplete (user) {
// Award gold
user.stats.gp += 100;
}
// Add notification and awards (server)
export function checkOnboardingStatus (user) {
if (hasActiveOnboarding(user) && hasCompletedOnboarding(user) && user.addNotification) {
user.addNotification('ONBOARDING_COMPLETE');
onOnboardingComplete(user);
}
}

View File

@@ -2,6 +2,7 @@ import get from 'lodash/get';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import content from '../../content/index'; import content from '../../content/index';
import splitWhitespace from '../../libs/splitWhitespace'; import splitWhitespace from '../../libs/splitWhitespace';
import { checkOnboardingStatus } from '../../libs/onboarding';
import { import {
BadRequest, BadRequest,
NotAuthorized, NotAuthorized,
@@ -67,6 +68,11 @@ export class BuyMarketGearOperation extends AbstractGoldItemOperation { // eslin
message = handleTwoHanded(user, item, undefined, req); message = handleTwoHanded(user, item, undefined, req);
} }
if (!user.achievements.purchasedEquipment && user.addAchievement) {
user.addAchievement('purchasedEquipment');
checkOnboardingStatus(user);
}
removePinnedGearAddPossibleNewOnes(user, `gear.flat.${item.key}`, item.key); removePinnedGearAddPossibleNewOnes(user, `gear.flat.${item.key}`, item.key);
if (item.last) ultimateGear(user); if (item.last) ultimateGear(user);

View File

@@ -11,6 +11,7 @@ import {
NotFound, NotFound,
} from '../libs/errors'; } from '../libs/errors';
import errorMessage from '../libs/errorMessage'; import errorMessage from '../libs/errorMessage';
import { checkOnboardingStatus } from '../libs/onboarding';
function evolve (user, pet, req) { function evolve (user, pet, req) {
user.items.pets[pet.key] = -1; user.items.pets[pet.key] = -1;
@@ -88,6 +89,11 @@ export default function feed (user, req = {}) {
if (userPets[pet.key] >= 50 && !user.items.mounts[pet.key]) { if (userPets[pet.key] >= 50 && !user.items.mounts[pet.key]) {
message = evolve(user, pet, req); message = evolve(user, pet, req);
} }
if (!user.achievements.fedPet && user.addAchievement) {
user.addAchievement('fedPet');
checkOnboardingStatus(user);
}
} }
user.items.food[food.key] -= 1; user.items.food[food.key] -= 1;

View File

@@ -11,6 +11,7 @@ import {
NotFound, NotFound,
} from '../libs/errors'; } from '../libs/errors';
import errorMessage from '../libs/errorMessage'; import errorMessage from '../libs/errorMessage';
import { checkOnboardingStatus } from '../libs/onboarding';
export default function hatch (user, req = {}) { export default function hatch (user, req = {}) {
const egg = get(req, 'params.egg'); const egg = get(req, 'params.egg');
@@ -49,6 +50,11 @@ export default function hatch (user, req = {}) {
user.markModified('items.hatchingPotions'); user.markModified('items.hatchingPotions');
} }
if (!user.achievements.hatchedPet && user.addAchievement) {
user.addAchievement('hatchedPet');
checkOnboardingStatus(user);
}
forEach(content.animalColorAchievements, achievement => { forEach(content.animalColorAchievements, achievement => {
if (!user.achievements[achievement.petAchievement]) { if (!user.achievements[achievement.petAchievement]) {
const petIndex = findIndex( const petIndex = findIndex(

View File

@@ -9,6 +9,7 @@ import i18n from '../i18n';
import updateStats from '../fns/updateStats'; import updateStats from '../fns/updateStats';
import crit from '../fns/crit'; import crit from '../fns/crit';
import statsComputed from '../libs/statsComputed'; import statsComputed from '../libs/statsComputed';
import { checkOnboardingStatus } from '../libs/onboarding';
const MAX_TASK_VALUE = 21.27; const MAX_TASK_VALUE = 21.27;
const MIN_TASK_VALUE = -47.27; const MIN_TASK_VALUE = -47.27;
@@ -343,5 +344,11 @@ export default function scoreTask (options = {}, req = {}) {
req.yesterDailyScored = task.yesterDailyScored; req.yesterDailyScored = task.yesterDailyScored;
updateStats(user, stats, req); updateStats(user, stats, req);
if (!user.achievements.completedTask && cron === false && direction === 'up' && user.addAchievement) {
user.addAchievement('completedTask');
checkOnboardingStatus(user);
}
return [delta]; return [delta];
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Some files were not shown because too many files have changed in this diff Show More