Merge branch 'develop' into sabe/attributes

This commit is contained in:
Sabe Jones
2014-01-01 12:42:53 -06:00
34 changed files with 439 additions and 160 deletions
+4 -2
View File
@@ -1,8 +1,10 @@
language: node_js
node_js:
- '0.10'
services:
- mongodb
before_script:
- 'npm install -g bower grunt-cli'
- 'bower install'
- 'npm install -g grunt-cli'
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- cp config.json.example config.json
+3 -3
View File
@@ -21,7 +21,6 @@
"angular-sanitize": "~1.2.1",
"angular-resource": "~1.2.1",
"angular-ui": "~0.4.0",
"angular-ui-utils": "~0.0.4",
"angular-bootstrap": "~0.5.0",
"angular-ui-router": "ca3b4777a603df8f86cfd653c8f6c38b2ae05d89",
"angular-loading-bar": "~0.0.5",
@@ -38,13 +37,14 @@
"js-emoji": "git://github.com/snicker/js-emoji#master",
"gemoji": "git://github.com/github/gemoji",
"sticky": "*",
"bootstrap-tour": "~0.8.0"
"bootstrap-tour": "~0.8.0",
"angular-ui-utils": "~0.1.0"
},
"resolutions": {
"jquery": "~2.0.3",
"bootstrap": "v2.3.2",
"angular": "~1.2.1",
"bootstrap-tour": "~0.8.0"
"angular-ui-utils": "~0.1.0"
},
"devDependencies": {
"angular-mocks": "~1.2.1"
-54
View File
@@ -1,54 +0,0 @@
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function(config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['ng-scenario'],
// list of files / patterns to load in the browser
files: [
'test/e2e/**/*.js'
],
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['Chrome'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false
// Uncomment the following lines if you are using grunt's server to run the tests
// proxies: {
// '/': 'http://localhost:9000/'
// },
// URL root prevent conflicts with the site root
// urlRoot: '_karma_'
});
};
+2 -2
View File
@@ -58,12 +58,12 @@
"npm": "1.2.x"
},
"scripts": {
"test": "grunt karma:continuous",
"test": "./test/run_tests.sh",
"start": "grunt run:dev",
"postinstall": "./node_modules/bower/bin/bower install -f"
},
"devDependencies": {
"karma-ng-scenario": "~0.1.0",
"protractor": "~0.14.0",
"grunt-karma": "~0.6.2",
"karma-script-launcher": "~0.1.0",
"karma-chrome-launcher": "~0.1.0",
+24
View File
@@ -0,0 +1,24 @@
// An example configuration file.
exports.config = {
// The address of a running selenium server.
seleniumAddress: 'http://localhost:4444/wd/hub',
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'firefox'
},
// Spec patterns are relative to the current working directly when
// protractor is called.
specs: ['test/e2e/e2e.js'],
// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: 'http://localhost:3001',
// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000
}
};
+2 -1
View File
@@ -1,4 +1,5 @@
@import "nib/vendor"
@import "nib"
//@import "nib/vendor"
// Vendor Includes - include first so we can override
// Import only styles that do not have urls to images! Include them directly in the page!
+31 -1
View File
@@ -407,4 +407,34 @@ form
color: #333
&:hover
border-top: 0
margin-top: 2px
margin-top: 2px
// Checklists
// --------
.checklist-form
/* Make an input blend into its parent */
input.inline-edit
reset-box-model()
reset-font()
line-height: inherit
text-align: inherit
background-color:inherit
box-shadow: none
/* Add interaction cues on hover and focus */
input.inline-edit:hover,input.inline-edit:focus
background-color: #FFD
transition: background-color 0.5s
.checklist-icons
opacity:0
li
min-height: 20px
padding-left: 10px
margin-bottom: 5px
[type="checkbox"]
margin: 0px 10px 3px 0px
li:hover .checklist-icons
opacity:1
+6 -1
View File
@@ -33,7 +33,7 @@ habitrpg.controller('NotificationCtrl',
$rootScope.$watch('user.stats.mp', function(after,before) {
if (after == before) return;
if (User.user.stats.lvl == 0) return;
if (!User.user.flags.classSelected || User.user.preferences.disableClasses) return;
var mana = after - before;
Notification.mp(mana);
});
@@ -68,6 +68,11 @@ habitrpg.controller('NotificationCtrl',
$rootScope.modals.achievements.beastMaster = true;
}, true);
$rootScope.$watch('user.achievements.rebirths', function(after, before){
if(after === before) return;
$rootScope.modals.achievements.rebirth = true;
});
/*_.each(['weapon', 'head', 'chest', 'shield'], function(watched){
$rootScope.$watch('user.items.' + watched, function(before, after){
if (after == before) return;
+6
View File
@@ -59,6 +59,12 @@ habitrpg.controller('SettingsCtrl',
$rootScope.$state.go('tasks');
}
$scope.rebirth = function(){
User.user.ops.rebirth({});
$rootScope.modals.rebirth = false;
$rootScope.$state.go('tasks');
}
$scope.changePassword = function(changePass){
if (!changePass.oldPassword || !changePass.newPassword || !changePass.confirmNewPassword) {
return alert("Please fill out all fields");
+56 -4
View File
@@ -1,7 +1,7 @@
"use strict";
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'API_URL',
function($scope, $rootScope, $location, User, Notification, $http, API_URL) {
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'API_URL', '$timeout',
function($scope, $rootScope, $location, User, Notification, $http, API_URL, $timeout) {
$scope.obj = User.user; // used for task-lists
$scope.user = User.user;
@@ -42,9 +42,11 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
User.user.ops.deleteTask({params:{id:list[$index].id}})
};
$scope.saveTask = function(task) {
$scope.saveTask = function(task, stayOpen) {
if (task.checklist && task.checklist[0])
task.checklist = _.filter(task.checklist,function(i){return !!i.text});
User.user.ops.updateTask({params:{id:task.id},body:task});
task._editing = false;
if (!stayOpen) task._editing = false;
};
/**
@@ -68,6 +70,56 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
});
};
/*
------------------------
To-Dos
------------------------
*/
$scope._today = moment().add('days',1);
/*
------------------------
Checklists
------------------------
*/
function focusChecklist(task,index) {
window.setTimeout(function(){
$('#task-'+task.id+' .checklist-form .inline-edit')[index].focus();
});
}
$scope.addChecklist = function(task) {
task.checklist = [{completed:false,text:""}];
focusChecklist(task,0);
}
$scope.addChecklistItem = function(task,$event,$index) {
if ($index == task.checklist.length-1){
User.user.ops.updateTask({params:{id:task.id},body:task}); // don't preen the new empty item
task.checklist.push({completed:false,text:''});
focusChecklist(task,task.checklist.length-1);
} else {
$scope.saveTask(task,true);
focusChecklist(task,$index+1);
}
}
$scope.removeChecklistItem = function(task,$index,force){
var backspaced = !force && !task.checklist[$index].text
if (force || backspaced) {
task.checklist.splice($index,1);
$scope.saveTask(task,true);
if (backspaced) focusChecklist(task,$index-1);
}
}
$scope.navigateChecklist = function(task,$index,$event){
focusChecklist(task, $event.keyCode == '40' ? $index+1 : $index-1);
}
$scope.checklistCompletion = function(checklist){
return _.reduce(checklist,function(m,i){return m+(i.completed ? 1 : 0);},0)
}
$scope.collapseChecklist = function(task) {
task.collapseChecklist = !task.collapseChecklist;
$scope.saveTask(task,true);
}
/*
------------------------
Items
-6
View File
@@ -8,10 +8,4 @@ angular.module('habitrpg')
return function (gp) {
return Math.floor((gp - Math.floor(gp))*100);
}
})
.filter('completedFilter', function(){
return function(tasks,_showCompleted) {
if (!tasks[0] || tasks[0].type != 'todo') return tasks;
return _.where(tasks, {completed:!!_showCompleted});
}
})
+5 -2
View File
@@ -139,7 +139,10 @@ angular.module('guideServices', []).
}
});
$rootScope.$watch('user.flags.rebirthEnabled', function(after, before) {
if (alreadyShown(before, after)) return;
$rootScope.modals.rebirthEnabled = true;
});
/**
@@ -174,7 +177,7 @@ angular.module('guideServices', []).
}, {
element: ".meter.mana",
title: "Spells",
content: "You can now unlock class-specific spells. You'll see your first at level 6. Your mana replenishes 10 points per day, plus 1 point per completed <a target='_blank' href='http://habitrpg.wikia.com/wiki/Todos'>To-Do</a>."
content: "You can now unlock class-specific spells. You'll see your first at level 11. Your mana replenishes 10 points per day, plus 1 point per completed <a target='_blank' href='http://habitrpg.wikia.com/wiki/Todos'>To-Do</a>."
}, {
orphan: true,
title: "Read More",
+2 -2
View File
@@ -6,11 +6,11 @@ angular.module("notificationServices", [])
function growl(html, type) {
$.bootstrapGrowl(html, {
ele: '#notification-area',
type: type, //(null, 'info', 'error', 'success', 'gp', 'xp', 'hp', 'lvl','death')
type: type, //(null, 'text', 'error', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp')
top_offset: 20,
align: 'right', //('left', 'right', or 'center')
width: 250, //(integer, or 'auto')
delay: 3000,
delay: 7000,
allow_dismiss: true,
stackup_spacing: 10 // spacing between consecutive stacecked growls.
});
+1 -1
View File
@@ -11,7 +11,7 @@
"bower_components/angular-ui-router/release/angular-ui-router.js",
"bower_components/angular-resource/angular-resource.min.js",
"bower_components/angular-ui/build/angular-ui.js",
"bower_components/angular-ui-utils/modules/keypress/keypress.js",
"bower_components/angular-ui-utils/ui-utils.min.js",
"bower_components/angular-loading-bar/build/loading-bar.js",
"bower_components/Angular-At-Directive/src/at.js",
"bower_components/Angular-At-Directive/src/caret.js",
+1 -1
View File
@@ -371,7 +371,7 @@ api.cast = function(req, res) {
if (group) {
series.push(function(cb2){
var message = '`'+user.profile.name+' casts '+spell.text + (type=='user' ? ' on @'+found.profile.name : ' for the party')+'.`';
var message = '`'+user.profile.name+' casts '+spell.text + (type=='user' ? ' on '+found.profile.name : ' for the party')+'.`';
group.sendChat(message);
group.save(cb2);
})
+2 -2
View File
@@ -182,7 +182,7 @@ GroupSchema.statics.collectQuest = function(user, progress, cb) {
return m;
}, []);
foundText = foundText ? foundText.join(', ') : 'nothing';
group.sendChat("`<" + user.profile.name + "> found "+foundText+".`");
group.sendChat("`" + user.profile.name + " found "+foundText+".`");
group.markModified('quest.progress.collect');
// Still needs completing
@@ -210,7 +210,7 @@ GroupSchema.statics.bossQuest = function(user, progress, cb) {
var down = progress.down * quest.boss.str; // multiply by boss strength
group.quest.progress.hp -= progress.up;
group.sendChat("`<" + user.profile.name + "> attacks <" + quest.boss.name + "> for " + (progress.up.toFixed(1)) + " damage, <" + quest.boss.name + "> attacks party for " + Math.abs(down).toFixed(1) + " damage.`");
group.sendChat("`" + user.profile.name + " attacks " + quest.boss.name + " for " + (progress.up.toFixed(1)) + " damage, " + quest.boss.name + " attacks party for " + Math.abs(down).toFixed(1) + " damage.`");
// Everyone takes damage
var series = [
+13 -1
View File
@@ -39,6 +39,14 @@ var HabitSchema = new Schema(
, { _id: false }
);
var collapseChecklist = {type:Boolean, 'default':false};
var checklist = [{
completed:{type:Boolean,'default':false},
text: String,
_id:false,
id: {type:String,'default':shared.uuid}
}];
var DailySchema = new Schema(
_.defaults({
type: {type:String, 'default': 'daily'},
@@ -53,6 +61,8 @@ var DailySchema = new Schema(
s: {type: Boolean, 'default': true},
su: {type: Boolean, 'default': true}
},
collapseChecklist:collapseChecklist,
checklist:checklist,
streak: {type: Number, 'default': 0}
}, TaskSchema)
, { _id: false }
@@ -62,7 +72,9 @@ var TodoSchema = new Schema(
_.defaults({
type: {type:String, 'default': 'todo'},
completed: {type: Boolean, 'default': false},
date: String // due date for todos // FIXME we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date
date: String, // due date for todos // FIXME we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date
collapseChecklist:collapseChecklist,
checklist:checklist
}, TaskSchema)
, { _id: false }
);
+5 -2
View File
@@ -46,7 +46,9 @@ var UserSchema = new Schema({
snowball: Number,
streak: Number,
challenges: Array,
quests: Schema.Types.Mixed
quests: Schema.Types.Mixed,
rebirths: Number,
rebirthLevel: Number
},
auth: {
facebook: Schema.Types.Mixed,
@@ -95,7 +97,8 @@ var UserSchema = new Schema({
partyEnabled: Boolean, // FIXME do we need this?
contributor: Boolean,
classSelected: {type: Boolean, 'default': false},
mathUpdates: Boolean
mathUpdates: Boolean,
rebirthEnabled: {type: Boolean, 'default': false}
},
history: {
exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined
+2 -1
View File
@@ -65,7 +65,8 @@ router['delete']('/user', auth.auth, user['delete']);
router.post('/user/revive', auth.auth, cron, user.revive); // Shared.ops
router.post('/user/reroll', auth.auth, cron, user.reroll); // Shared.ops
router.post('/user/reset', auth.auth, user.reset); // Shared.ops
router.post('/user/sleep', auth.auth, cron, user.sleep); //Shared.opss
router.post('/user/sleep', auth.auth, cron, user.sleep); //Shared.ops
router.post('/user/rebirth', auth.auth, cron, user.rebirth); //Shared.ops
router.post('/user/class/change', auth.auth, cron, user.changeClass); //Shared.ops | query={class}
router.post('/user/class/allocate', auth.auth, cron, user.allocate); //Shared.ops | query={stat}
+3 -1
View File
@@ -1,6 +1,7 @@
require('coffee-script') // for habitrpg-shared
var nconf = require('nconf');
require('./config');
var utils = require('./utils');
utils.setupConfig();
var async = require('async');
var mongoose = require('mongoose');
User = require('./models/user').model;
@@ -31,4 +32,5 @@ async.waterfall([
],function(err){
if (err) throw err;
console.log("Done initializing database");
mongoose.disconnect();
})
+61
View File
@@ -0,0 +1,61 @@
'use strict';
describe('front page', function() {
beforeEach(function(){
browser.ignoreSynchronization = true;
browser.get('/');
browser.sleep(1000);
});
// based on https://github.com/angular/protractor/issues/114#issuecomment-29046939
afterEach(function(){
var currentSpec = jasmine.getEnv().currentSpec;
var passed = currentSpec.results().passed();
if(!passed){
var filename = 'exception_' + currentSpec.description + '.png';
browser.takeScreenshot().then(function(png){
var fs = require('fs');
var buffer = new Buffer(png, 'base64');
var stream = fs.createWriteStream(filename);
stream.write(buffer);
stream.end();
});
}
});
it('shows the front page', function(){
var button = element(by.className('btn'));
expect(button.getText()).toEqual('Play');
});
it("don't login when using wrong credentials", function(){
var button = element(by.className('btn'));
button.click();
browser.sleep(1000);
element(by.model('loginUsername')).sendKeys('username');
element(by.model('loginPassword')).sendKeys('pass');
var login = element(by.css("#login-tab input[value='Login']"));
login.click();
var alertDialog = browser.switchTo().alert();
expect(alertDialog.getText()).toMatch(/Username 'username' not found/);
alertDialog.accept();
});
it('registers a new user', function(){
var button = element(by.className('btn'));
button.click();
browser.sleep(1000);
var registerTab = element(by.linkText('Register'));
registerTab.click();
element(by.model('registerVals.username')).sendKeys('user');
element(by.model('registerVals.email')).sendKeys('user@example.com');
element(by.model('registerVals.password')).sendKeys('pass');
element(by.model('registerVals.confirmPassword')).sendKeys('pass');
var register = element(by.css("#register-tab input[value='Register']"));
register.click();
browser.sleep(1000);
browser.getCurrentUrl().then(function(url){
expect(url).not.toMatch(/static\/front/);
});
});
});
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# Configuration
TEST_DB=habitrpg_test
TEST_DB_URI="mongodb://localhost/$TEST_DB"
TEST_SERVER_PORT=3001
# Build assets
grunt build:dev
# Launch Node server and Selenium
echo "Recreating test database"
mongo "$TEST_DB" --eval "db.dropDatabase()"
./node_modules/protractor/bin/webdriver-manager update
./node_modules/protractor/bin/webdriver-manager start > /dev/null &
NODE_DB_URI="$TEST_DB_URI" PORT=$TEST_SERVER_PORT node ./src/server.js > /dev/null &
NODE_PID=$!
trap "kill $NODE_PID && curl http://localhost:4444/selenium-server/driver/?cmd=shutDownSeleniumServer" EXIT
sleep 3 # Wait for Selenium
grunt karma:continuous && ./node_modules/protractor/bin/protractor protractor.conf.js
+5
View File
@@ -137,3 +137,8 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
p
| {{Content.spells.special.snowball.value}}
span.Pet_Currency_Gem1x.inline-gems
div(ng-show='user.flags.rebirthEnabled')
button.customize-option(popover='Begin a new character at Level 1 while retaining achievements, collectibles, and tasks with history.', popover-title='Orb of Rebirth', popover-trigger='mouseenter', popover-placement='left', ng-click='modals.rebirth = true', class='rebirth_orb')
p
| 8
span.Pet_Currency_Gem1x.inline-gems
+4 -4
View File
@@ -167,25 +167,25 @@ script(id='partials/options.profile.stats.html', type='text/ng-template')
table.table.table-striped
tr
td
i.icon-question-sign(popover-title='Strength', popover='Strength increases damage you inflict to tasks (reducing redness), and increases critical hit chance.', popover-trigger='mouseenter')
i.icon-question-sign(popover-title='Strength', popover='Strength reduces task threat (redness), increases the Gold and Experience boost from random "critical hits," and helps deal damage to boss monsters.', popover-trigger='mouseenter')
| &nbsp;STR: {{user.stats.str}}
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")') +
tr
td
i.icon-question-sign(popover-title='Constitution', popover='Constitution decreases damage inflicted by tasks, and increases the effectiveness of healing and buff spells.', popover-trigger='mouseenter')
i.icon-question-sign(popover-title='Constitution', popover='Constitution reduces the damage you take, whether from negative Habits, missed Dailies, or the attacks of boss monsters.', popover-trigger='mouseenter')
| &nbsp;CON: {{user.stats.con}}
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")') +
tr
td
i.icon-question-sign(popover-title='Perception', popover='Perception increases drops and gold, and can help with critical hits by spells.', popover-trigger='mouseenter')
i.icon-question-sign(popover-title='Perception', popover='Perception increases how much Gold you earn, and increases the chance of finding items when scoring tasks.', popover-trigger='mouseenter')
| &nbsp;PER: {{user.stats.per}}
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")') +
tr
td
i.icon-question-sign(popover-title='Intelligence', popover='Intelligence increases your overall mana, spell effectiveness, and experience gain.', popover-trigger='mouseenter')
i.icon-question-sign(popover-title='Intelligence', popover='Intelligence increases how much Experience you earn, and determines your maximum Mana available for class abilities.', popover-trigger='mouseenter')
| &nbsp;INT: {{user.stats.int}}
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")') +
+2 -1
View File
@@ -48,7 +48,8 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
div(ng-if='!user.preferences.disableClasses')
button.btn(ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover="Show the tour for using the class system.") Show Classes Tour
div(ng-show='user.auth.local')
//- Why is ng-if='user.auth.local' validating for users *without* user.auth.local (facebook users)? adding .username here for extra
div(ng-if='user.auth.local.username')
hr
h4 Change Password
form(ng-submit='changePassword(changePass)', ng-show='user.auth.local')
+1
View File
@@ -46,3 +46,4 @@ figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember(
.avatar-level(ng-class='userLevelStyle(profile,"label")')
i.icon-circle-arrow-up.icon-white(ng-show='profile.stats.buffs.str || profile.stats.buffs.per || profile.stats.buffs.con || profile.stats.buffs.int || profile.stats.buffs.stealth', tooltip='Buffed', style='margin-right:5px;')
| Lvl {{profile.stats.lvl}}
i.icon-plus-sign.icon-white(ng-show='profile.achievements.rebirths', tooltip='Reborn, max level {{profile.achievements.rebirthLevel}}', style='margin-left:5px')
+13 -5
View File
@@ -7,7 +7,7 @@ div(modal='modals.achievements.streak')
.achievement.achievement-thermometer
| You have stacked your "Streaker" Achievement! Every 21 days of streak, you gain 1 achievement point here.
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.streak = false') Ok
button.btn.btn-default.cancel(ng-click='modals.achievements.streak = false') OK
// Max Gear
div(modal='modals.achievements.ultimateGear')
@@ -17,7 +17,7 @@ div(modal='modals.achievements.ultimateGear')
.achievement.achievement-armor
| You have earned the "Ultimate Gear" Achievement for upgrading to the maximum gear set!
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.ultimateGear = false') Ok
button.btn.btn-default.cancel(ng-click='modals.achievements.ultimateGear = false') OK
// Beast Master
div(modal='modals.achievements.beastMaster')
@@ -28,7 +28,7 @@ div(modal='modals.achievements.beastMaster')
.achievement.achievement-rat
| You have earned the "Beast Master" Achievement for collecting all the pets!
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.beastMaster = false') Ok
button.btn.btn-default.cancel(ng-click='modals.achievements.beastMaster = false') OK
// Contributor
div(modal='user.flags.contributor')
@@ -40,7 +40,15 @@ div(modal='user.flags.contributor')
{{user.profile.name}}, you awesome person! You're now a level {{user.contributor.level}} contributor for helping HabitRPG. See <a href='http://habitrpg.wikia.com/wiki/Contributor_Rewards' target='_blank'>what prizes you've earned for your contribution!</a>
.modal-footer
button.btn.btn-default.cancel(ng-click='set({"flags.contributor":false})') Ok
button.btn.btn-default.cancel(ng-click='set({"flags.contributor":false})') OK
//Rebirth
div(modal='modals.achievements.rebirth')
.modal-header
h3 Achievement!
.modal-body
.achievement.achievement-sun
| You've begun a new adventure! This is Rebirth {{user.achievements.rebirths}} for you, and the highest Level you've attained is {{user.achievements.rebirthLevel}}. To stack this Achievement, begin your next new adventure when you've reached an even higher Level!
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.rebirth = false') OK
+9 -10
View File
@@ -1,11 +1,10 @@
.modal(ng-if='!user.flags.classSelected && user.stats.lvl >= 10', data-backdrop=true, ng-controller='UserCtrl')
.modal-header
h3 Choose Your Class!
h3 Choose Your <a href='http://habitrpg.wikia.com/wiki/Class_System' target='_blank'>Class</a>!
.modal-body.select-class
p Select your class. Click each class for more information. See <a href="http://habitrpg.wikia.com/wiki/Class_System" target="_blank">Wikia</a> for thorough details.
.row-fluid
.span3(ng-click='selectedClass = "warrior"')
h5 Warrior
h5 <a href='http://habitrpg.wikia.com/wiki/Warrior' target='_blank'>Warrior</a>
figure.herobox(ng-class='{"selected-class": selectedClass=="warrior"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
@@ -19,7 +18,7 @@
span(class='shield_warrior_5')
span(class='weapon_warrior_6')
.span3(ng-click='selectedClass = "wizard"')
h5 Mage
h5 <a href='http://habitrpg.wikia.com/wiki/Mage' target='_blank'>Mage</a>
figure.herobox(ng-class='{"selected-class": selectedClass=="wizard"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
@@ -33,7 +32,7 @@
span(class='shield_wizard_5')
span(class='weapon_wizard_6')
.span3(ng-click='selectedClass = "rogue"')
h5 Rogue
h5 <a href='http://habitrpg.wikia.com/wiki/Rogue' target='_blank'>Rogue</a>
figure.herobox(ng-class='{"selected-class": selectedClass=="rogue"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
@@ -47,7 +46,7 @@
span(class='shield_rogue_6')
span(class='weapon_rogue_6')
.span3(ng-click='selectedClass = "healer"')
h5 Healer
h5 <a href='http://habitrpg.wikia.com/wiki/Healer' target='_blank'>Healer</a>
figure.herobox(ng-class='{"selected-class": selectedClass=="healer"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
@@ -61,10 +60,10 @@
span(class='shield_healer_5')
span(class='weapon_healer_6')
br
.well(ng-show='selectedClass=="warrior"') Warriors deal damage to tasks (reducing redness), have moderate defense, and improved critical hits.
.well(ng-show='selectedClass=="wizard"') Mages deal damage to tasks (reducing redness), can debuff tasks, and they gain experience rapidly.
.well(ng-show='selectedClass=="rogue"') Rogues finds more drops and gold. They can also "go stealth" to avoid damage at the end of a day.
.well(ng-show='selectedClass=="healer"') Healers have high defense against damage, and can heal themselves and other players in the party, as well as buff players.
.well(ng-show='selectedClass=="warrior"') Warriors keep tasks in line, each success making later slip-ups less harsh. They also score impressive "critical hits," which randomly give bonus Gold and Experience for scoring a task. Play a Warrior if you don't want to be punished too hard for inconsistency, or if you find strong motivation from unpredictable jackpot-style rewards!
.well(ng-show='selectedClass=="wizard"') Mages learn swiftly, gaining Experience and Levels faster than other classes. They also get a great deal of Mana for using special abilities. Play a Mage if you enjoy the tactical game aspects of Habit, or if you are strongly motivated by leveling up and unlocking advanced features!
.well(ng-show='selectedClass=="rogue"') Rogues love to accumulate wealth, gaining more Gold than anyone else, and are adept at finding random items. Their iconic Stealth ability lets them duck the consequences of missed Dailies. Play a Rogue if you find strong motivation from Rewards and Achievements, striving for loot and badges!
.well(ng-show='selectedClass=="healer"') Healers stand impervious against harm, and extend that protection to others. Missed Dailies and bad Habits don't faze them much, and they have ways to recover Health from failure. Play a Healer if you enjoy assisting others in your Party, or if the idea of cheating Death through hard work inspires you!
.modal-footer
button.btn.btn-small.btn-danger.cancel(ng-click='user.ops.disableClasses({})', popover-placement='top', popover-trigger='mouseenter', popover="Can't be bothered with classes? Opt out - you'll be a warrior and your points handled automatically. You can enable classes later under Settings") Opt Out
+2 -1
View File
@@ -8,4 +8,5 @@ include ./members
include ./settings
include ./drops
include ./classes
include ./quests
include ./quests
include ./rebirth
+60 -35
View File
@@ -26,48 +26,73 @@ div(modal='modals.newStuff')
a(target='_blank', href='https://twitter.com/Mihakuu') Bailey
.popover-content
h3 Winter Wonderland Event!
h4 Part 2: Rescue the Bears
h4 Winter Wonderland Event Part 3: Party!
table.table.table-striped
tr
td
h5 Quests & Bosses!
p A beast is roaring in the distant mountains, mysterious tracks have appeared in the snow. A new feature has been unlocked, <a href='https://trello.com/c/VPPjVRlF/212-quests-bosses' target='_blank'>Quests & Bosses</a>. As a holiday present, HabitRPG gives you your first quest: "Trapper Santa". Check your inventory, you have until Jan 31 to complete it!
h5 Happy New Year!
p Happy New Year! Join the NPCs and Staff in showing off your new Absurd party hat.... and have a great night!
small.muted by @lemoness
tr
td
h5 Rebirth
p Nothing says New Year like a fresh start. Now when you reach level 50, Ultimate Gear, or BeastMaster, you can begin anew with the most prestigious of achievements: Rebirth. <a href='https://trello.com/c/SLiq4enr/333-rebirth-new-game' target='_blank'>Read more here</a>. But take heed! Scouts have reported <a href='https://github.com/HabitRPG/habitrpg/issues/945#issuecomment-31355229' target='_blank'>monster sightings</a>, harbinged by Trapper Santa. You may need all the strength you can muster come late January, Rebirth is for the hard-core.
small.muted by @SabreCat
tr
td
h5 Checklists
p <a href='https://trello.com/c/PJ1iJ413/65-checklists' target='_blank'>Checklists</a> are here! You can break your Dailies and To-Dos down into bite-size chunks. Their game mechanic takes some learning, so <a href='http://habitrpg.wikia.com/wiki/Checklist' target='_blank'>read more here</a>.
small.muted by @lefnire
tr
td
h5 Task Icons & Markdown
p Task titles now support Markdown and Emoji, so you can create something <a href='http://gyazo.com/f2021674925a79a1dec22101ef74a63c' target='_blank'>like this</a>. Read more <a href='https://trello.com/c/FCVdjdUd/102-task-reward-icons' target='_blank'>here</a>.
small.muted by @lefnire
p By @lefnire, @pandoro, @Shaners
small.muted 12/25/2013
small.muted 12/31/2013
div.muted
hr
p It's time for HabitRPG's biggest event yet - Winter Wonderland! The fun starts today, on the first day of winter, and ends on January 31st - HabitRPG's birthday.
p Get prepared to build new habits, earn fun drops, hold your party members accountable for their tasks, and decorate your avatar. Various features will be rolling out over the course of the event, so expect many updates! For starters...
h4 Part 1: The Great Snowball Fight
table.table.table-striped
tr
td
h5 NPC Decorations
p Looks like everyone is really getting into the winter spirit! Check out the new NPC sprites. (And I heard a rumor that the final NPC might show up, just in time for the new year...)
tr
td
.customize-option.hair_bangs_1_winternight.pull-right
h5 Limited-Edition Holiday Hair-Colors
p Now your avatar can dye their hair Candy Cane, Frost, Winter Sky, or Holly! You'll only be able to purchase these hair colors until January 31st, when they will be retired.
tr
td
.shop_snowball.pull-right
h5 The Great HabitRPG Snowball Fight
p Yes, you can now buy snowballs and hurl them at all your friends... to, uh, help them improve their habits. How? Weeeeellll, let's just say that after getting walloped, they might find themselves needing some extra gold to escape their predicament...
//-span.shop_head_special_candycane.item-img.shop-sprite
tr
td
h5 More to Come
p A beast is roaring in the distant mountains, mysterious tracks have appeared in the snow, and Lemoness is furiously crocheting something sparkly.
p It's going to be a wild winter.
p By @lemoness
small.muted 12/20/2013
hr
h5 12/25/2013
h4 Winter Wonderland Event Part 2: Rescue the Bears
table.table.table-striped
tr
td
h5 Quests & Bosses!
p A beast is roaring in the distant mountains, mysterious tracks have appeared in the snow. A new feature has been unlocked, <a href='https://trello.com/c/VPPjVRlF/212-quests-bosses' target='_blank'>Quests & Bosses</a>. As a holiday present, HabitRPG gives you your first quest: "Trapper Santa". Check your inventory, you have until Jan 31 to complete it!
p By @lefnire, @pandoro, @Shaners
hr
h5 12/20/2013
h4 Winter Wonderland Event Part 1: The Great Snowball Fight
p It's time for HabitRPG's biggest event yet - Winter Wonderland! The fun starts today, on the first day of winter, and ends on January 31st - HabitRPG's birthday.
p Get prepared to build new habits, earn fun drops, hold your party members accountable for their tasks, and decorate your avatar. Various features will be rolling out over the course of the event, so expect many updates! For starters...
table.table.table-striped
tr
td
h5 NPC Decorations
p Looks like everyone is really getting into the winter spirit! Check out the new NPC sprites. (And I heard a rumor that the final NPC might show up, just in time for the new year...)
tr
td
.customize-option.hair_bangs_1_winternight.pull-right
h5 Limited-Edition Holiday Hair-Colors
p Now your avatar can dye their hair Candy Cane, Frost, Winter Sky, or Holly! You'll only be able to purchase these hair colors until January 31st, when they will be retired.
tr
td
.shop_snowball.pull-right
h5 The Great HabitRPG Snowball Fight
p Yes, you can now buy snowballs and hurl them at all your friends... to, uh, help them improve their habits. How? Weeeeellll, let's just say that after getting walloped, they might find themselves needing some extra gold to escape their predicament...
//-span.shop_head_special_candycane.item-img.shop-sprite
tr
td
h5 More to Come
p A beast is roaring in the distant mountains, mysterious tracks have appeared in the snow, and Lemoness is furiously crocheting something sparkly.
p It's going to be a wild winter.
p By @lemoness
hr
+45
View File
@@ -0,0 +1,45 @@
// Created by Sabe on 12/22/13.
div(modal='modals.rebirthEnabled')
.modal-header
h3 Rebirth: New Adventure Available!
.modal-body
.rebirth_orb.pull-left(style='margin-right: 5px')
p
span.
You've unlocked Rebirth! This special Market item allows you to begin a new game at level 1 while keeping your tasks, achievements, pets, and more. Use it to breathe new life into HabitRPG if you feel you've achieved it all, or to experience new features with the fresh eyes of a beginning character!
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.rebirthEnabled = false') Close
div(modal='modals.rebirth')
.modal-header
h3 Rebirth: Begin a New Adventure
.modal-body
include ../gems
.rebirth_orb.pull-left(style='margin-right: 5px')
p Rebirth starts your character over from Level 1, as if you had created a new account.
br
p Your adventure begins anew with a clean slate:
.unordered
li You return to full Health.
li You have no Experience, Gold, or equipment.
li Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset.
li You have the starting class of Warrior until you earn a new class.
br
p Your new character inherits a few things from their predecessor:
.unordered
li Tasks, history, and settings remain.
li Challenge, Guild, and Party memberships remain.
li Gems, backer tiers, and contributor levels remain.
li Items obtained from Gems or drops (such as pets and mounts) remain, though you cannot access them until you unlock them again.
br
.achievement-sun.pull-left(style='margin-right: 5px')
p You also earn an Achievement for beginning a new adventure!
.modal-footer
button.btn.btn-large.cancel(ng-click='modals.rebirth = false') Never mind
span(ng-if='user.balance < 2')
a.btn.btn-success.btn-large(ng-click="modals.buyGems = true") Buy More Gems
span.gem-cost Not enough Gems
span(ng-if='user.balance >= 2', ng-controller='SettingsCtrl')
a.btn.btn-danger.btn-large(ng-click='rebirth()') Be Reborn
span.gem-cost 8 Gems
+9 -2
View File
@@ -3,7 +3,6 @@
// <div class='achievement achievement-tree'></div>
// <div class='achievement achievement-comment'></div>
// <div class='achievement achievement-cave'></div>
// <div class='achievement achievement-sun'></div>
// <div class='achievement achievement-boot'></div>
// <div class='achievement achievement-coffin'></div>
// <div class='achievement achievement-alien'></div>
@@ -57,6 +56,14 @@
small Has found all 90 pets (insanely difficult, give this user props!)
hr
div(ng-if='profile.achievements.rebirths')
.achievement.achievement-sun
h5(ng-if='profile.achievements.rebirths == 1') Began a New Adventure
h5(ng-if='profile.achievements.rebirths > 1') Began {{profile.achievements.rebirths}} New Adventures
small.
Used an Orb of Rebirth to start over after attaining Level {{profile.achievements.rebirthLevel}}.
hr
div(ng-if='profile.achievements.helpedHabit')
.achievement.achievement-tree
h5 Helped Habit Grow
@@ -100,4 +107,4 @@
.achievement.achievement-snowball
h5 Annoying Friends
small Got snowballed {{profile.achievements.snowball}} times by party members.
hr
hr
+8 -8
View File
@@ -46,10 +46,10 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
// Static Rewards
ul.items.rewards(ng-if='main && list.type=="reward" && user.flags.itemsEnabled')
li.task.reward-item(ng-repeat='item in itemStore')
li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.notes}}')
// right-hand side control buttons
.task-meta-controls
span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{item.notes}}', popover-title='{{item.text}}')
span.task-notes
i.icon-comment
//left-hand size commands
.task-controls
@@ -62,9 +62,9 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
// Winter Event
ul.items.rewards(ng-if='main && list.type=="reward" && (user.items.special.snowball>0 || user.stats.buffs.snowball)')
li.task.reward-item(ng-if='user.items.special.snowball>0')
li.task.reward-item(ng-if='user.items.special.snowball>0',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.snowball.notes}}')
.task-meta-controls
span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{Content.spells.special.snowball.notes}}', popover-title='{{Content.spells.special.snowball.text}}')
span.task-notes
i.icon-comment
//left-hand size commands
.task-controls
@@ -73,9 +73,9 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
span.shop_snowball(style='display:inline-block;vertical-align:top;')
// main content
p.task-text {{Content.spells.special.snowball.text}}
li.task.reward-item(ng-if='user.stats.buffs.snowball')
li.task.reward-item(ng-if='user.stats.buffs.snowball',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.salt.notes}}')
.task-meta-controls
span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{Content.spells.special.salt.notes}}', popover-title='{{Content.spells.special.salt.text}}')
span.task-notes
i.icon-comment
//left-hand size commands
.task-controls
@@ -88,9 +88,9 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
// Spells
ul.items(ng-if='main && list.type=="reward" && user.stats.class && !user.preferences.disableClasses')
li.task.reward-item(ng-repeat='(k,spell) in Content.spells[user.stats.class]', ng-show='user.stats.lvl >= spell.lvl')
li.task.reward-item(ng-repeat='(k,spell) in Content.spells[user.stats.class]', ng-if='user.stats.lvl >= spell.lvl',popover-trigger='mouseenter', popover-placement='top', popover='{{spell.notes}}')
.task-meta-controls
span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{spell.notes}}', popover-title='{{spell.text}}')
span.task-notes
i.icon-comment
//left-hand size commands
.task-controls
+32 -7
View File
@@ -1,10 +1,10 @@
li(bindonce='list', ng-repeat='task in obj[list.type+"s"] | completedFilter: list.showCompleted', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && castEnd(task, "task", $event)', ng-class='{"cast-target":spell}')
li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s"]', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && castEnd(task, "task", $event)', ng-class='{"cast-target":spell}', popover-trigger='mouseenter', popover-placement='top', popover='{{task.notes}}')
// right-hand side control buttons
.task-meta-controls
// Due Date
span.task-date(ng-if='task.type=="todo" && task.date')
| {{task.date}}
span(ng-class='{"label label-important":moment(task.date).isBefore(_today)}') {{moment(task.date).format('MM/DD')}}
// Streak
span(ng-show='task.streak') {{task.streak}}
span(tooltip='Streak Counter')
@@ -12,6 +12,9 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"] | completedFilter: lis
// Icons only available if you own the tasks (aka, hidden from challenge stats)
span(ng-if='!obj._locked')
a.badge(ng-if='task.checklist[0]', ng-class='{"badge-success":checklistCompletion(task.checklist) == task.checklist.length}', ng-click='collapseChecklist(task)')
{{checklistCompletion(task.checklist)}}/{{task.checklist.length}}
i.icon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
// edit
a(ng-hide='task._editing', ng-click='editTask(task)', tooltip='Edit')
@@ -36,7 +39,7 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"] | completedFilter: lis
a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip='Progress')
i.icon-signal
// notes
span.task-notes(ng-show='task.notes && !task._editing', popover-trigger='mouseenter', popover-placement='left', popover='{{task.notes}}', popover-title='{{task.text}}')
span.task-notes(ng-show='task.notes && !task._editing')
i.icon-comment
// left-hand side checkbox
@@ -60,10 +63,16 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"] | completedFilter: lis
input.visuallyhidden.focusable(ng-if='!$state.includes("tasks")', id='box-{{obj._id}}_{{task.id}}', type='checkbox')
label(for='box-{{obj._id}}_{{task.id}}')
// main content
p.task-text
div.task-text(ng-dblclick='task._editing ? saveTask(task) : editTask(task)')
markdown(ng-model='task.text',target='_blank')
//-| {{task.text}}
div(ng-if='task.checklist && !$state.includes("options.social.challenges") && !task.collapseChecklist && !task._editing')
fieldset.option-group
label.checkbox(ng-repeat='item in task.checklist')
input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
| {{item.text}}
// edit/options dialog
div(ng-if='task._editing')
.task-options
@@ -96,6 +105,23 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"] | completedFilter: lis
| &nbsp;|&nbsp;
a(ng-click="unlink(task, 'remove-all')") Remove Them
// Checklists
a.btn.btn-small(ng-if='!task.checklist[0] && (task.type=="daily" || task.type=="todo")',ng-click='addChecklist(task)')
i.icon-tasks
| Add Checklist
form.checklist-form(ng-if='task.checklist')
fieldset.option-group.task-checklist(ng-if='!$state.includes("options.social.challenges")')
legend.option-title
| Checklist&nbsp;
i.icon.icon-question-sign(popover='For Dailies, partially-completed Dailies will cut damage (eg, a 4-item checklist with 3 completed, your Daily will hurt you 25% of the damage). For To-Dos, when completed, your points will be multiplied by the number of checklist-items (eg, a 4-item To-Do will be 4x the Exp & GP).',popover-trigger='mouseenter',popover-placement='bottom')
ul.unstyled
li(ng-repeat='item in task.checklist')
a.pull-right.checklist-icons(ng-click='removeChecklistItem(task,$index,true)')
i.icon-trash(tooltip='Delete')
input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
input.inline-edit(type='text',ng-model='item.text',ui-keyup="{13:'addChecklistItem(task,$event,$index)','8 49':'removeChecklistItem(task,$index)','38 40':'navigateChecklist(task,$index,$event)'}")//-,ng-blur='saveTask(task,true)')
form(ng-submit='saveTask(task)')
// text & notes
fieldset.option-group
@@ -138,7 +164,7 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"] | completedFilter: lis
// if Todos, the due date
fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
legend.option-title Due Date
input.option-content.datepicker(type='text', data-date-format='mm/dd/yyyy', ng-model='task.date')
input.option-content.datepicker(type='text', datepicker-popup='MM/dd/yyyy', ng-model='task.date')
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
legend.option-title Tags
@@ -159,9 +185,8 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"] | completedFilter: lis
button.task-action-btn.tile(type='button', ng-class='{active: task.priority==1.5}', ng-click='task.challenge.id || (task.priority=1.5)') Medium
button.task-action-btn.tile(type='button', ng-class='{active: task.priority==2}', ng-click='task.challenge.id || (task.priority=2)') Hard
//span(ng-if='task.type=="daily" && !task.challenge.id')
br
span(ng-if='task.type=="daily"')
legend.option-title Restore Streak
legend.option-title.pull-left Restore Streak
input.option-content(type='number', ng-model='task.streak')
legend.option-title Attributes