├── hooks
├── .gitignore
├── after_platform_add
│ └── 010_install_plugins.js
└── README.md
├── .bowerrc
├── .gitignore
├── readme
├── icon.png
├── items.png
├── quests.png
├── app_store.png
├── character.png
├── github_promo.png
├── google_play.png
├── items_green.png
├── quests_green.png
├── character_green.png
└── character_half_green.png
├── www
├── assets
│ ├── img
│ │ ├── arrow.png
│ │ ├── ionic.png
│ │ ├── dagger.png
│ │ ├── fitbit.png
│ │ ├── questIcon.png
│ │ ├── fitbitbutton.png
│ │ ├── jawbone_icon.jpg
│ │ └── jawbonebutton.png
│ ├── fonts
│ │ ├── icomoon.eot
│ │ ├── icomoon.ttf
│ │ ├── icomoon.woff
│ │ └── icomoon.svg
│ └── css
│ │ ├── icostyle.css
│ │ └── style.css
├── app
│ ├── help
│ │ ├── help.js
│ │ ├── help.html
│ │ └── help-controller.js
│ ├── main
│ │ ├── main.js
│ │ ├── main.html
│ │ └── main-controller.js
│ ├── shop
│ │ ├── shop.js
│ │ ├── shop-controller.js
│ │ ├── shop-detail.html
│ │ ├── shop-detail-controller.js
│ │ └── shop.html
│ ├── components
│ │ ├── resource
│ │ │ ├── resource.js
│ │ │ └── resource-service.js
│ │ ├── auth
│ │ │ ├── auth-login-controller.js
│ │ │ ├── auth.js
│ │ │ ├── auth-jawbone-service.js
│ │ │ ├── auth-fitbit-service.js
│ │ │ └── auth-controller.js
│ │ └── utility
│ │ │ └── utility.js
│ ├── battle
│ │ ├── battle.js
│ │ ├── battle.html
│ │ └── battle-controller.js
│ ├── select
│ │ ├── select.js
│ │ ├── select-username.html
│ │ ├── select-class.html
│ │ ├── select-username-controller.js
│ │ └── select-class-controller.js
│ ├── friends
│ │ ├── friends.js
│ │ ├── friends-add.html
│ │ ├── friends.html
│ │ ├── friends-add-controller.js
│ │ └── friends-controller.js
│ ├── feedback
│ │ ├── feedback.js
│ │ ├── feedback.html
│ │ └── feedback-controller.js
│ ├── inventory
│ │ ├── inventory.js
│ │ ├── inventory-controller.js
│ │ ├── inventory-detail.html
│ │ ├── inventory.html
│ │ └── inventory-detail-controller.js
│ ├── leaderboard
│ │ ├── leaderboard.js
│ │ ├── leaderboard.html
│ │ └── leaderboard-controller.js
│ ├── quest
│ │ ├── quest.js
│ │ ├── quest-service.js
│ │ ├── quest-detail.html
│ │ ├── quest-controller.js
│ │ ├── quest.html
│ │ └── quest-detail-controller.js
│ ├── menu
│ │ └── menu.html
│ └── app.js
└── index.html
├── bower.json
├── package.json
├── scss
└── ionic.app.scss
├── config.xml
├── gulpfile.js
└── README.md
/hooks/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "www/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | platforms/
3 | plugins/
4 | www/lib
5 | *.DS_store
6 |
--------------------------------------------------------------------------------
/readme/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/icon.png
--------------------------------------------------------------------------------
/readme/items.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/items.png
--------------------------------------------------------------------------------
/readme/quests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/quests.png
--------------------------------------------------------------------------------
/readme/app_store.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/app_store.png
--------------------------------------------------------------------------------
/readme/character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/character.png
--------------------------------------------------------------------------------
/readme/github_promo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/github_promo.png
--------------------------------------------------------------------------------
/readme/google_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/google_play.png
--------------------------------------------------------------------------------
/readme/items_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/items_green.png
--------------------------------------------------------------------------------
/readme/quests_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/quests_green.png
--------------------------------------------------------------------------------
/www/assets/img/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/arrow.png
--------------------------------------------------------------------------------
/www/assets/img/ionic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/ionic.png
--------------------------------------------------------------------------------
/readme/character_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/character_green.png
--------------------------------------------------------------------------------
/www/assets/img/dagger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/dagger.png
--------------------------------------------------------------------------------
/www/assets/img/fitbit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/fitbit.png
--------------------------------------------------------------------------------
/www/assets/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/fonts/icomoon.eot
--------------------------------------------------------------------------------
/www/assets/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/www/assets/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/fonts/icomoon.woff
--------------------------------------------------------------------------------
/www/assets/img/questIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/questIcon.png
--------------------------------------------------------------------------------
/readme/character_half_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/readme/character_half_green.png
--------------------------------------------------------------------------------
/www/assets/img/fitbitbutton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/fitbitbutton.png
--------------------------------------------------------------------------------
/www/assets/img/jawbone_icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/jawbone_icon.jpg
--------------------------------------------------------------------------------
/www/assets/img/jawbonebutton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/HEAD/www/assets/img/jawbonebutton.png
--------------------------------------------------------------------------------
/www/app/help/help.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.help', [
4 |
5 | 'mobile.help.controllers'
6 | ]);
7 |
8 | angular.module('mobile.help.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/main/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.main', [
4 |
5 | 'mobile.main.controllers'
6 | ]);
7 |
8 | angular.module('mobile.main.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/shop/shop.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.shop', [
4 |
5 | 'mobile.shop.controllers'
6 | ]);
7 |
8 | angular.module('mobile.shop.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/components/resource/resource.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.resource', [
4 |
5 | 'mobile.resource.services'
6 | ]);
7 |
8 | angular.module('mobile.resource.services', ['ngResource']);
--------------------------------------------------------------------------------
/www/app/battle/battle.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.battle', [
4 |
5 | 'mobile.battle.controllers'
6 | ]);
7 |
8 | angular.module('mobile.battle.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/select/select.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.select', [
4 |
5 | 'mobile.select.controllers'
6 | ]);
7 |
8 | angular.module('mobile.select.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/friends/friends.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.friends', [
4 |
5 | 'mobile.friends.controllers'
6 | ]);
7 |
8 | angular.module('mobile.friends.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/feedback/feedback.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.feedback', [
4 |
5 | 'mobile.feedback.controllers'
6 | ]);
7 |
8 | angular.module('mobile.feedback.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/inventory/inventory.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.inventory', [
4 |
5 | 'mobile.inventory.controllers'
6 | ]);
7 |
8 | angular.module('mobile.inventory.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/leaderboard/leaderboard.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.leaderboard', [
4 |
5 | 'mobile.leaderboard.controllers'
6 | ]);
7 |
8 | angular.module('mobile.leaderboard.controllers', ['LocalStorageModule','ionic']);
9 |
--------------------------------------------------------------------------------
/www/app/quest/quest.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.quest', [
4 |
5 | 'mobile.quest.controllers',
6 | 'mobile.quest.services'
7 | ]);
8 |
9 | angular.module('mobile.quest.controllers', ['LocalStorageModule','ionic']);
10 | angular.module('mobile.quest.services', ['LocalStorageModule','ionic']);
11 |
--------------------------------------------------------------------------------
/www/app/components/auth/auth-login-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.authentication.controllers')
2 |
3 | .controller('LoginController', function ($scope, $state, FitbitLoginService, JawboneLoginService) {
4 |
5 | $scope.fitbitlogin = FitbitLoginService.login;
6 | $scope.jawbonelogin = JawboneLoginService.login;
7 |
8 | });
9 |
--------------------------------------------------------------------------------
/www/app/components/auth/auth.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('mobile.authentication', [
4 |
5 | 'mobile.authentication.controllers',
6 | 'mobile.authentication.services'
7 | ]);
8 |
9 | angular.module('mobile.authentication.controllers', ['LocalStorageModule', 'ionic']);
10 | angular.module('mobile.authentication.services', ['LocalStorageModule', 'ionic']);
11 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HelloIonic",
3 | "private": "true",
4 | "devDependencies": {
5 | "ionic": "driftyco/ionic-bower#1.0.0-beta.9"
6 | },
7 | "dependencies": {
8 | "angular-bootstrap": "~0.11.0",
9 | "bootstrap": "~3.1.1",
10 | "angular-local-storage": "~0.0.5",
11 | "angular-resource": "~1.2.16",
12 | "angular-timer": "~1.1.4",
13 | "ngCordova": "~0.1.2-alpha"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/www/app/quest/quest-service.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.quest.services')
2 |
3 | .factory('finishQuest', function() {
4 | return {
5 | winQuest: function(user,userQuest) {
6 | userQuest.status = 'success';
7 | user.userQuest = userQuest;
8 | user.attributes.gold += userQuest.gold;
9 | return user;
10 | },
11 | loseQuest: function(user,userQuest) {
12 | userQuest.status = 'fail';
13 | user.userQuest = userQuest;
14 | user.attributes.gold = user.attributes.gold - Math.floor(userQuest.gold/2);
15 | return user;
16 | }
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic-project",
3 | "version": "1.0.0",
4 | "description": "An Ionic project",
5 | "dependencies": {
6 | "gulp": "^3.5.6",
7 | "gulp-concat": "^2.2.0",
8 | "gulp-minify-css": "^0.3.0",
9 | "gulp-rename": "^1.2.0",
10 | "gulp-sass": "^0.7.1",
11 | "method-override": "^1.0.1",
12 | "jawbone-up": "^0.1.1",
13 | "q": "*",
14 | "bower": "^1.3.3",
15 | "gulp-util": "^2.2.14",
16 | "shelljs": "^0.3.0",
17 | "tiny-lr": "*"
18 | },
19 | "devDependencies": {
20 | "gulp-util": "^2.2.14",
21 | "shelljs": "^0.3.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/www/app/feedback/feedback.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/www/app/select/select-username.html:
--------------------------------------------------------------------------------
1 |
4 |
7 |
18 |
--------------------------------------------------------------------------------
/www/app/select/select-class.html:
--------------------------------------------------------------------------------
1 |
4 |
7 |
20 |
--------------------------------------------------------------------------------
/scss/ionic.app.scss:
--------------------------------------------------------------------------------
1 | /*
2 | To customize the look and feel of Ionic, you can override the variables
3 | in ionic's _variables.scss file.
4 |
5 | For example, you might change some of the default colors:
6 |
7 | $light: #fff !default;
8 | $stable: #f8f8f8 !default;
9 | $positive: #4a87ee !default;
10 | $calm: #43cee6 !default;
11 | $balanced: #66cc33 !default;
12 | $energized: #f0b840 !default;
13 | $assertive: #ef4e3a !default;
14 | $royal: #8a6de9 !default;
15 | $dark: #444 !default;
16 | */
17 |
18 | // The path for our ionicons font files, relative to the built CSS in www/css
19 | $ionicons-font-path: "../lib/ionic/fonts" !default;
20 |
21 | // Include all of Ionic
22 | @import "www/lib/ionic/scss/ionic";
23 |
24 |
--------------------------------------------------------------------------------
/www/app/leaderboard/leaderboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
22 |
23 |
--------------------------------------------------------------------------------
/www/app/feedback/feedback-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.feedback.controllers')
2 |
3 | .controller('FeedbackCtrl', function($scope, $state, $ionicPopup, User, Feedback, localStorageService) {
4 | $scope.feedback = {
5 | email: '',
6 | message: '',
7 | };
8 |
9 | var navTo = function(location) {
10 | $state.go('app.' + location);
11 | };
12 |
13 |
14 | $scope.submit = function(feedback) {
15 | if (!localStorageService.get('rate')) {
16 | localStorageService.set('rate',true);
17 | $scope.user.attributes.gold += 500;
18 | User.update($scope.user);
19 | }
20 |
21 | Feedback.save({email: feedback.email, message: feedback.message, createdAt: new Date()});
22 |
23 | var title = 'Feedback Received';
24 | var body = 'We are constantly implementing new features and improving this app. Your feedback will help us greatly!';
25 |
26 | util.showAlert($ionicPopup,title,body,'OK',function(){
27 | navTo('character');
28 | });
29 |
30 | };
31 | })
32 |
--------------------------------------------------------------------------------
/hooks/after_platform_add/010_install_plugins.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | //this hook installs all your plugins
4 |
5 | // add your plugins to this list--either the identifier, the filesystem location or the URL
6 | var pluginlist = [
7 | // 'org.apache.cordova.console',
8 | 'org.apache.cordova.device',
9 | // 'org.apache.cordova.file',
10 | 'org.apache.cordova.inappbrowser',
11 | // 'org.apache.cordova.media'
12 | 'org.apache.cordova.statusbar',
13 | 'https://github.com/driftyco/ionic-plugins-keyboard.git',
14 | 'https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git',
15 | // 'https://github.com/phonegap-build/PushPlugin.git',
16 | ];
17 |
18 | // no need to configure below
19 |
20 | var fs = require('fs');
21 | var path = require('path');
22 | var sys = require('sys')
23 | var exec = require('child_process').exec;
24 |
25 | function puts(error, stdout, stderr) {
26 | sys.puts(stdout)
27 | }
28 |
29 | pluginlist.forEach(function(plug) {
30 | exec('cordova plugin add ' + plug, puts);
31 | });
32 |
--------------------------------------------------------------------------------
/www/app/components/auth/auth-jawbone-service.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.authentication.services')
2 |
3 | .factory('JawboneLoginService', function ($window, $state, localStorageService) {
4 | var url = 'https://fitrpg.azurewebsites.net/jawbone/auth';
5 | var loginWindow, token, hasToken, userId, hasUserId;
6 |
7 | return {
8 | login: function () {
9 | loginWindow = $window.open(url, '_blank', 'location=no,toolbar=no,hidden=yes');
10 | loginWindow.addEventListener('loadstart', function (event) {
11 | hasToken = event.url.indexOf('?token=');
12 | hasUserId = event.url.indexOf('&userid=');
13 | if (hasToken > -1) {
14 | token = event.url.substring(hasToken + 7);
15 | userId = event.url.substring(hasUserId + 8)
16 | localStorageService.set('jawbone-token', token);
17 | localStorageService.set('userId', userId);
18 | location.href=location.pathname;
19 | loginWindow.close();
20 | }
21 | });
22 | },
23 | };
24 | });
25 |
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | FitRPG
4 |
5 | Get more out of your healthy lifestyle
6 |
7 |
8 | FitRPG Team
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/www/app/leaderboard/leaderboard-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.leaderboard.controllers')
2 |
3 | .controller('LeaderboardCtrl', function($scope, $ionicLoading, User, Leaderboard) {
4 | var loading = setTimeout(function(){
5 | $ionicLoading.show({
6 | template: 'Loading...
'
7 | });
8 | }, 500);
9 |
10 | $scope.allTab = 'button-tab-active';
11 | $scope.all = function() {
12 | $scope.allTab = 'button-tab-active';
13 | $scope.friendsTab = '';
14 | $scope.leaderboard = [];
15 | Leaderboard.query(function(users) {
16 | $scope.leaderboard = users;
17 | clearTimeout(loading);
18 | $ionicLoading.hide();
19 | })
20 | };
21 |
22 | $scope.friends = function() {
23 | $scope.allTab = '';
24 | $scope.friendsTab = 'button-tab-active';
25 | $scope.leaderboard = [];
26 | $scope.leaderboard.push($scope.user);
27 | for(var i = 0; i < $scope.user.friends.length; i++){
28 | User.get({id : $scope.user.friends[i]}, function(user) {
29 | if (user['_id']) {
30 | $scope.leaderboard.push(user);
31 | }
32 | })
33 | }
34 | };
35 |
36 | $scope.all();
37 | });
38 |
--------------------------------------------------------------------------------
/www/app/select/select-username-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.select.controllers')
2 |
3 | .controller('UsernameCtrl',function($rootScope, $scope, $state, User, localStorageService, $cacheFactory, CheckUsername) {
4 |
5 | var localUserId = localStorageService.get('userId');
6 |
7 | $scope.submit = function(username) {
8 |
9 | // attempt to clear cache - may not work necessarily
10 | // var $httpDefaultCache = $cacheFactory.get('$http');
11 | // $httpDefaultCache.removeAll();
12 | // end attempt
13 |
14 | User.get({id : localUserId}, function (user) {
15 | $rootScope.user = user;
16 | CheckUsername.get({username:username}, function (user) { //this will return an object or null
17 | if (user.username === username) {
18 | $window.alert('Sorry! That username already exists.')
19 | } else {
20 | $rootScope.user.username = username;
21 | localStorageService.set('username', username);
22 | }
23 | });
24 | });
25 |
26 | $state.transitionTo('select'); // in the future don't let them transition in case their is a duplicate UN
27 | }
28 |
29 | if(localStorageService.get('username')){
30 | $state.transitionTo('app.character');
31 | }
32 |
33 | });
34 |
--------------------------------------------------------------------------------
/www/app/select/select-class-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.select.controllers')
2 |
3 | .controller('SelectClassCtrl',function($scope, $state, User) {
4 |
5 | $scope.disable = true;
6 | $scope.selected = function(item) {
7 | $scope.disable = false;
8 | $scope.char = item;
9 | };
10 |
11 | $scope.characterClasses = [{'text': 'Shadow Elf','value': 'endurance','description':'Endurance bonus for runners'},
12 | {'text': 'Dwarf King', 'value': 'strength','description':'Strength bonus for gym goers'},
13 | {'text': 'Rune Knight', 'value': 'dexterity','description':'Dexterity bonus for sports players'},
14 | {'text': 'Dream Weaver', 'value': 'vitality','description':'Vitality bonus for those who sleep'}];
15 |
16 | $scope.data = {
17 | clientSide: 'ng'
18 | };
19 |
20 | $scope.submit = function() {
21 | $scope.user.character = $scope.char.text;
22 | $scope.user.characterClass = $scope.char.value;
23 | User.update($scope.user);
24 | $state.transitionTo('app.character');
25 | }
26 |
27 | $scope.disable = function() {
28 | if ($scope.username === '' || $scope.username === undefined ) {
29 | return false;
30 | }
31 | return true;
32 | }
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/www/app/friends/friends-add.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
36 |
37 |
--------------------------------------------------------------------------------
/www/app/shop/shop-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.shop.controllers')
2 |
3 | .controller('ShopCtrl', function($rootScope, $state, $scope, Shop, $ionicLoading) {
4 | var loading = setTimeout(function(){
5 | $ionicLoading.show({
6 | template: 'Loading...
'
7 | });
8 | }, 500);
9 |
10 | $scope.getData = function() {
11 | $scope.shop = [];
12 | Shop.query( function (items) {
13 | var userLvl = $scope.user.attributes.level;
14 | for (var i=0; i= item.level) {
17 | $scope.shop.push(item);
18 | }
19 | }
20 | clearTimeout(loading);
21 | $ionicLoading.hide();
22 | });
23 | };
24 |
25 | $scope.equipmentTab = 'button-tab-active';
26 | $scope.equipment = function() {
27 | $scope.isEquipment = true;
28 | $scope.equipmentTab = 'button-tab-active';
29 | $scope.itemsTab = '';
30 | };
31 |
32 | $scope.potion = function(id) {
33 | $scope.isEquipment = false;
34 | $scope.equipmentTab = '';
35 | $scope.itemsTab = 'button-tab-active';
36 | };
37 |
38 | $scope.getData();
39 | $scope.equipment();
40 |
41 | $scope.showList = {
42 | weapons: true,
43 | armor: true,
44 | accessories: true,
45 | potions: true
46 | };
47 |
48 | $scope.toggleList = function(list) {
49 | $scope.showList[list] = !$scope.showList[list];
50 | };
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/www/app/help/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
43 |
44 |
--------------------------------------------------------------------------------
/www/assets/css/icostyle.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src:url('../fonts/icomoon.eot?-h8ghme');
4 | src:url('../fonts/icomoon.eot?#iefix-h8ghme') format('embedded-opentype'),
5 | url('../fonts/icomoon.woff?-h8ghme') format('woff'),
6 | url('../fonts/icomoon.ttf?-h8ghme') format('truetype'),
7 | url('../fonts/icomoon.svg?-h8ghme#icomoon') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | [class^="icon-"], [class*=" icon-"] {
13 | font-family: 'icomoon';
14 | speak: none;
15 | font-style: normal;
16 | font-weight: normal;
17 | font-variant: normal;
18 | text-transform: none;
19 | line-height: 1;
20 |
21 | /* Better Font Rendering =========== */
22 | -webkit-font-smoothing: antialiased;
23 | -moz-osx-font-smoothing: grayscale;
24 | }
25 |
26 | .icon-battle:before {
27 | content: "\e60c";
28 | }
29 | .icon-fight:before {
30 | content: "\e60b";
31 | }
32 | .icon-plastron:before {
33 | content: "\e602";
34 | }
35 | .icon-plain-dagger:before {
36 | content: "\e608";
37 | }
38 | .icon-locked-fortress:before {
39 | content: "\e609";
40 | }
41 | .icon-wyvern:before {
42 | content: "\e600";
43 | }
44 | .icon-podium:before {
45 | content: "\e601";
46 | }
47 | .icon-checked-shield:before {
48 | content: "\e603";
49 | }
50 | .icon-anvil:before {
51 | content: "\e604";
52 | }
53 | .icon-coin:before {
54 | content: "\e605";
55 | }
56 | .icon-support:before {
57 | content: "\e60a";
58 | }
59 | .icon-lab:before {
60 | content: "\e606";
61 | }
62 | .icon-shield:before {
63 | content: "\e607";
64 | }
65 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var gutil = require('gulp-util');
3 | var bower = require('bower');
4 | var concat = require('gulp-concat');
5 | var sass = require('gulp-sass');
6 | var minifyCss = require('gulp-minify-css');
7 | var rename = require('gulp-rename');
8 | var sh = require('shelljs');
9 |
10 | var paths = {
11 | sass: ['./scss/**/*.scss']
12 | };
13 |
14 | gulp.task('default', ['sass']);
15 |
16 | gulp.task('sass', function(done) {
17 | gulp.src('./scss/ionic.app.scss')
18 | .pipe(sass())
19 | .pipe(gulp.dest('./www/css/'))
20 | .pipe(minifyCss({
21 | keepSpecialComments: 0
22 | }))
23 | .pipe(rename({ extname: '.min.css' }))
24 | .pipe(gulp.dest('./www/css/'))
25 | .on('end', done);
26 | });
27 |
28 | gulp.task('watch', function() {
29 | gulp.watch(paths.sass, ['sass']);
30 | });
31 |
32 | gulp.task('install', ['git-check'], function() {
33 | return bower.commands.install()
34 | .on('log', function(data) {
35 | gutil.log('bower', gutil.colors.cyan(data.id), data.message);
36 | });
37 | });
38 |
39 | gulp.task('git-check', function(done) {
40 | if (sh.which('git')) {
41 | console.log(
42 | ' ' + gutil.colors.red('Git is not installed.'),
43 | '\n Git, the version control system, is required to download Ionic.',
44 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',
45 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.'
46 | );
47 | process.exit(1);
48 | }
49 | done();
50 | });
51 |
--------------------------------------------------------------------------------
/www/app/components/auth/auth-fitbit-service.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.authentication.services')
2 |
3 | .factory('FitbitLoginService', function ($window, $state, $ionicLoading, $location, localStorageService, $location) {
4 | var url = 'http://fitrpg.azurewebsites.net/fitbit/auth';
5 | var usernameUrl = 'http://fitrpg.azurewebsites.net/fitbit/getUsername';
6 | var loginWindow, token, hasToken, userId, hasUserId;
7 |
8 | return {
9 | login: function () {
10 | loginWindow = $window.open(url, '_blank', 'location=no,toolbar=no,hidden=yes');
11 | $ionicLoading.show({
12 | template: 'Contacting Fitbit...
',
13 | animation: 'fade-in',
14 | showBackdrop: false,
15 | maxWidth: 200,
16 | showDelay: 200
17 | });
18 |
19 | loginWindow.addEventListener("loadstop", function(e) {
20 | $ionicLoading.hide();
21 | loginWindow.show();
22 | });
23 |
24 | loginWindow.addEventListener('loadstart', function (event) {
25 | hasToken = event.url.indexOf('?oauth_token=');
26 | hasUserId = event.url.indexOf('&userId=');
27 | if (hasToken > -1 && hasUserId > -1) {
28 | token = event.url.match('oauth_token=(.*)&userId')[1];
29 | userId = event.url.match('&userId=(.*)')[1];
30 | localStorageService.set('fitbit-token', token);
31 | localStorageService.set('token-date', JSON.stringify(new Date()));
32 | localStorageService.set('userId', userId);
33 | loginWindow.close();
34 | location.href=location.pathname;
35 | }
36 | });
37 | },
38 | };
39 | });
40 |
--------------------------------------------------------------------------------
/www/app/components/auth/auth-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.authentication.controllers')
2 |
3 | .controller('AuthenticationController', function ($scope, $state, $ionicLoading, User, localStorageService) {
4 | // Check our local storage for the proper credentials to ensure we are logged in, this means users can't get past app unless they select a username
5 | if (localStorageService.get('username')) {
6 | if (localStorageService.get('fitbit-token') && isTokenInDate(localStorageService)) {
7 | $state.transitionTo('app.character');
8 | $scope.Authenticated = true;
9 | }
10 | } else if (localStorageService.get('fitbit-token') && isTokenInDate(localStorageService)) {
11 |
12 | $ionicLoading.show({
13 | template: 'loading...
',
14 | animation: 'fade-in',
15 | showBackdrop: false,
16 | maxWidth: 200,
17 | showDelay: 100
18 | });
19 |
20 | User.get({id : localStorageService.get('userId') }, function(user) {
21 | if (user.username === undefined) {
22 | $ionicLoading.hide();
23 | $state.transitionTo('username');
24 | $scope.Authenticated = true;
25 | } else {
26 | $ionicLoading.hide();
27 | $state.transitionTo('app.character');
28 | $scope.Authenticated = true;
29 | }
30 | });
31 | } else {
32 | $scope.needsAuthentication = true;
33 | }
34 |
35 | $scope.logout = function () {
36 | localStorageService.clearAll();
37 | location.href=location.pathname;
38 | };
39 |
40 | });
41 |
42 | var isTokenInDate = function(localStorageService){
43 | var tokenDate = new Date(JSON.parse(localStorageService.get('token-date')));
44 | if (tokenDate) {
45 | var today = new Date();
46 | var timeDiff = Math.abs(today.getTime() - tokenDate.getTime());
47 | var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24));
48 | if(diffDays > 10) {
49 | return false;
50 | }
51 | } else {
52 | return false;
53 | }
54 | return true;
55 | };
56 |
--------------------------------------------------------------------------------
/www/app/quest/quest-detail.html:
--------------------------------------------------------------------------------
1 |
2 |
50 |
51 |
--------------------------------------------------------------------------------
/www/app/menu/menu.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/www/app/shop/shop-detail.html:
--------------------------------------------------------------------------------
1 |
2 |
60 |
61 |
--------------------------------------------------------------------------------
/www/app/shop/shop-detail-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.shop.controllers')
2 |
3 | .controller('ShopDetailCtrl', function($scope, $stateParams, Shop, User, $ionicPopup, $q) {
4 | $scope.isWeapon = false;
5 | $scope.shopItem = Shop.get({id : $stateParams.shopId}, function(item){
6 | $scope.shopItem.type = util.capitalize($scope.shopItem.type);
7 | if ($scope.shopItem.size === 1) {
8 | $scope.shopItem.sizeText = 'One-Handed';
9 | } else if ($scope.shopItem.size === 2) {
10 | $scope.shopItem.sizeText = 'Two-Handed';
11 | }
12 | if ($scope.shopItem.type === 'Weapon') {
13 | $scope.isWeapon = true;
14 | }
15 | });
16 |
17 | $scope.addClass = function(attr) {
18 | if (attr >= 0) {
19 | return 'text-green';
20 | } else {
21 | return 'text-red';
22 | }
23 | };
24 |
25 | $scope.buyItem = function() {
26 | if ($scope.user.attributes.gold >= $scope.shopItem.cost) {
27 | $scope.user.attributes.gold = $scope.user.attributes.gold - $scope.shopItem.cost;
28 | // add to inventory
29 | var found = false;
30 | var inventoryId = 0;
31 | if ($scope.user.inventory.length > 0) {
32 | inventoryId = $scope.user.inventory[$scope.user.inventory.length-1].id+1;
33 | }
34 |
35 | if ($scope.shopItem.type.toLowerCase() === 'potion') {
36 | var inventory = $scope.user.inventory;
37 | for (var i=0; iLoading... '
19 | });
20 | }, 500);
21 |
22 | Shop.query( function (storeItems) {
23 | for (var i=0; i
2 |
3 |
5 |
6 |
7 |
9 |
10 |
11 | FitRPG is a mobile app created by [Amira Anuar](https://github.com/aellawind), [Matt Gutierrez](https://github.com/fatchicken007), and [Conor Fennell](https://github.com/conorfennell) at [Hack Reactor](http://www.hackreactor.com/). FitRPG transforms a Fitbit user's data into a character that can fight friends, battle bosses, and go on quests using the steps, distance, and sleep tracked by the Fitbit. The game logic seeks to motivate users to stay fit and challenge themselves to go that extra mile in order to win a battle or complete a quest.
12 |
13 | Featured On
14 | * [Lifehacker](http://lifehacker.com/fitrpg-turns-your-fitbit-into-a-game-you-play-with-frie-1602140820)
15 |
16 | Tech Stack
17 | * [Ionic Framework](http://ionicframework.com/)
18 | * [AngularJS](https://angularjs.org/)
19 | * [Node.js](http://nodejs.org/)
20 | * [Express.js](http://expressjs.com/)
21 | * [MongoDB](http://www.mongodb.org/)
22 |
23 | Code Base
24 | * [Client side](https://github.com/fitrpg/fitrpg-ionic)
25 | * [Server side](https://github.com/fitrpg/fitrpg-server)
26 |
27 | Challenges :
28 | * User flow during fitbit OAuth
29 | * Originally we wanted to do the OAuth login client side on the app. But due to fitbit using OAuth 1.0 and not allowing CORS or JSONP, it had to be done server side. This was a challenge since the server redirects your app during the OAuth process and takes you out of the app context. We had to find a way to keep this redirect within the app and inform the app if the authentication was successful. Read our blog post [here](http://amiraanuar.com/mobile-authentication-in-ionic-with-oauth-through-external-apis-fitbit-pt-2-client/) on how we implemented the client-side portion of authentication via Ionic.
30 | * Game logic design
31 | * Balancing how sleep, steps and other activities relate to the characters attributes and making sure one is not more effective than other attributes.
32 | * User interface design
33 | * A game can have a lot of different options and views, reducing and compressing these views and making them innutaive is a challenge.
34 | * Security
35 | * Implementing json web tokens
36 | * OAuth
37 |
38 | Upcoming Features
39 | * Push Notfications
40 | * Versus Missions
41 | * Jawbone Support
42 |
--------------------------------------------------------------------------------
/www/app/inventory/inventory-detail.html:
--------------------------------------------------------------------------------
1 |
2 |
69 |
70 |
--------------------------------------------------------------------------------
/hooks/README.md:
--------------------------------------------------------------------------------
1 |
21 | # Cordova Hooks
22 |
23 | This directory may contain scripts used to customize cordova commands. This
24 | directory used to exist at `.cordova/hooks`, but has now been moved to the
25 | project root. Any scripts you add to these directories will be executed before
26 | and after the commands corresponding to the directory name. Useful for
27 | integrating your own build systems or integrating with version control systems.
28 |
29 | __Remember__: Make your scripts executable.
30 |
31 | ## Hook Directories
32 | The following subdirectories will be used for hooks:
33 |
34 | after_build/
35 | after_compile/
36 | after_docs/
37 | after_emulate/
38 | after_platform_add/
39 | after_platform_rm/
40 | after_platform_ls/
41 | after_plugin_add/
42 | after_plugin_ls/
43 | after_plugin_rm/
44 | after_plugin_search/
45 | after_prepare/
46 | after_run/
47 | after_serve/
48 | before_build/
49 | before_compile/
50 | before_docs/
51 | before_emulate/
52 | before_platform_add/
53 | before_platform_rm/
54 | before_platform_ls/
55 | before_plugin_add/
56 | before_plugin_ls/
57 | before_plugin_rm/
58 | before_plugin_search/
59 | before_prepare/
60 | before_run/
61 | before_serve/
62 | pre_package/ <-- Windows 8 and Windows Phone only.
63 |
64 | ## Script Interface
65 |
66 | All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67 |
68 | * CORDOVA_VERSION - The version of the Cordova-CLI.
69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71 | * CORDOVA_HOOK - Path to the hook that is being executed.
72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73 |
74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75 |
76 |
77 | ## Writing hooks
78 |
79 | We highly recommend writting your hooks using Node.js so that they are
80 | cross-platform. Some good examples are shown here:
81 |
82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83 |
84 |
--------------------------------------------------------------------------------
/www/app/friends/friends.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
60 |
61 |
--------------------------------------------------------------------------------
/www/app/friends/friends-add-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.friends.controllers')
2 |
3 | .controller('AddFriendsCtrl', function($scope, User, $ionicPopup, $ionicLoading, UserSearch) {
4 | // friends is accessed from $rootScope.user.friends in the template
5 | $scope.friends = [];
6 |
7 | // User.query(function(users){
8 | // for (var i=0; iSearching... '
35 | });
36 | }, 500);
37 |
38 | var stopLoading = function() {
39 | clearTimeout(loading);
40 | $ionicLoading.hide();
41 | };
42 |
43 | UserSearch.query({username: username}, function(users) {
44 | if (users.length === 0) {
45 | $scope.notFound = true;
46 | }
47 |
48 | for (var i=0; i
2 |
3 |
4 |
5 |
6 |
12 |
13 |
74 |
75 |
--------------------------------------------------------------------------------
/www/app/components/resource/resource-service.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.resource.services')
2 |
3 | .constant('SERVER_URL', 'http://fitrpg.azurewebsites.net')
4 |
5 | .factory('authHttpInterceptor', ['localStorageService', function(localStorageService) {
6 | return {
7 | 'request': function(config) {
8 | config.headers = config.headers || {};
9 | // do some API key setting
10 | // Add json web token
11 | if(localStorageService.get('fitbit-token')){
12 | config.headers.Authorization = 'Bearer ' + localStorageService.get('fitbit-token');
13 | }
14 | return config;
15 | }
16 | };
17 | }])
18 |
19 | .config(['$httpProvider', function($httpProvider) {
20 | $httpProvider.interceptors.push('authHttpInterceptor');
21 | }])
22 |
23 | .factory('CheckUsername', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
24 | return $resource(SERVER_URL + '/api/users/check/:username', {username: '@username'});
25 | }])
26 |
27 | .factory('Refresh', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
28 | return $resource(SERVER_URL + '/fitbit/refresh/:id', {id: '@id'});
29 | }])
30 |
31 | .factory('Settings', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
32 | return $resource(SERVER_URL + '/settings/:id', {id: '@id'});
33 | }])
34 |
35 | .factory('User', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
36 | return $resource(SERVER_URL + '/api/users/:id', {id : '@id'}, {
37 | update: { method: 'PUT' }
38 | });
39 | }])
40 |
41 | // get fitbit data based off of a date range
42 | .factory('DatesData', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
43 | return $resource(SERVER_URL + '/fitbit/daterange/:id/:type/:activity/:startDate/:endDate',
44 | {id: '@id', type: '@type', activity: '@activity', startDate: '@startDate', endDate: '@endDate'} );
45 | }])
46 |
47 | // get fitbit data based off of a time range
48 | .factory('NewTimesData', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
49 | return $resource(SERVER_URL + '/fitbit/new/timerange/:id/:activity/:startDate/:startTime/:endTime',
50 | {id: '@id', activity: '@activity', startDate: '@startDate', startTime: '@startTime', endTime: '@endTime'} );
51 | }])
52 |
53 | // OLD RESOURCE WE NEVER USE BECAUSE WE CHANGE IT--LEAVING IT HERE TO REMEMBER FOR BACKWARDS COMPATIABILITY
54 | .factory('TimesData', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
55 | return $resource(SERVER_URL + '/fitbit/timerange/:id/:activity/:startDate/:endDate/:startTime/:endTime',
56 | {id: '@id', activity: '@activity', startDate: '@startDate', endDate: '@endDate', startTime: '@startTime', endTime: '@endTime'} );
57 | }])
58 |
59 |
60 | .factory('Shop', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
61 | return $resource(SERVER_URL + '/api/items/:id', {id : '@id'});
62 | }])
63 |
64 | .factory('Battle', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
65 | return $resource(SERVER_URL + '/api/battles/:id', {id : '@id'});
66 | }])
67 |
68 | .factory('SoloMissions',['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
69 | return $resource(SERVER_URL + '/api/solos/:id', {id : '@id'});
70 | }])
71 |
72 | .factory('Quests',['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
73 | return $resource(SERVER_URL + '/api/quests/:id', {id : '@id'});
74 | }])
75 |
76 | .factory('VersusMissions',['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
77 | return $resource(SERVER_URL + '/api/groups/:id', {id : '@id'});
78 | }])
79 |
80 | .factory('RandomUser',['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
81 | return $resource(SERVER_URL + '/api/users/random/:id/:level',
82 | {id : '@id', level: '@level'});
83 | }])
84 |
85 | .factory('UserSearch',['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
86 | return $resource(SERVER_URL + '/api/users/search/:username', {username : '@username'});
87 | }])
88 |
89 | .factory('Leaderboard',['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
90 | return $resource(SERVER_URL + '/api/users/leaderboard');
91 | }])
92 |
93 | .factory('Feedback',['$resource', 'SERVER_URL', function($resource, SERVER_URL) {
94 | return $resource(SERVER_URL + '/feedback/:id', {id : '@id'});
95 | }]);
96 |
--------------------------------------------------------------------------------
/www/app/quest/quest-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.quest.controllers')
2 |
3 | // This controller handles the list of the quests that show up as all,active,and completed
4 | .controller('QuestCtrl', function($scope, $ionicLoading, $ionicScrollDelegate, localStorageService, SoloMissions, Quests, User, TimesData, DatesData) {
5 |
6 | // Creates the loading screen that only shows up after 500 ms if items have not yet loaded
7 | var loading = setTimeout(function(){
8 | $ionicLoading.show({
9 | template: 'Loading...
'
10 | });
11 | }, 500);
12 |
13 | $scope.allTab = 'button-tab-active';
14 | $scope.all = function() {
15 | $scope.isAll = true;
16 | $scope.isActive = false;
17 | $scope.isComplete = false;
18 | $scope.quests = [];
19 | $scope.availableQuests = [];
20 | $scope.allTab = 'button-tab-active';
21 | $scope.activeTab = '';
22 | $scope.completedTab = '';
23 | $ionicScrollDelegate.scrollTop();
24 |
25 | // Create an array of ids of quests the user is currently doing
26 | var currentQuests = [];
27 | for (var i = 0; i < $scope.user.quests.length; i++ ) {
28 | currentQuests.push($scope.user.quests[i].questId);
29 | }
30 | // Compare all quests against this array and only display them if they're not on it
31 | Quests.query(function(quests){
32 | $scope.quests = quests;
33 | for (var i =0; i<$scope.quests.length;i++) {
34 | if (currentQuests.indexOf($scope.quests[i]._id) < 0) {
35 | $scope.availableQuests.push($scope.quests[i]);
36 | }
37 | }
38 | clearTimeout(loading);
39 | $ionicLoading.hide();
40 | });
41 | };
42 |
43 | // Show all of the quests that are active
44 | $scope.active = function() {
45 | $scope.isAll = false;
46 | $scope.isActive = true;
47 | $scope.isComplete = false;
48 | $scope.allTab = '';
49 | $scope.activeTab = 'button-tab-active';
50 | $scope.completedTab = '';
51 | $ionicScrollDelegate.scrollTop();
52 |
53 | var today = new Date();
54 | $scope.quests = [];
55 | for (var i =0; i< $scope.user.quests.length; i++) {
56 | if($scope.user.quests[i].status === 'active') {
57 | (function(i) {
58 | Quests.get({id : $scope.user.quests[i].questId}, function(q) {
59 | $scope.quests.push(q);
60 | });
61 | }(i));
62 | }
63 | }
64 | };
65 |
66 | // Show all of the quests that have already been completed in the last week
67 | $scope.completed = function() {
68 | $scope.isAll = false;
69 | $scope.isActive = false;
70 | $scope.isComplete = true;
71 | $scope.successfulQuests = [];
72 | $scope.failedQuests = [];
73 | $scope.allTab = '';
74 | $scope.activeTab = '';
75 | $scope.completedTab = 'button-tab-active';
76 | $ionicScrollDelegate.scrollTop();
77 | var today = new Date();
78 | var refreshQuests = [];
79 |
80 | // Iterate over all the quests that the user has stored; maybe eventually just save them
81 | // locally to the user object
82 | for (var j =0; j< $scope.user.quests.length; j++) {
83 | (function(i) {
84 | var quest = $scope.user.quests[i];
85 | var completeDate = new Date(quest.completionTime); //convert date to matched format
86 | // Iterate over all the quests and get the ones that have status of completed
87 | if(quest.status === 'success' || quest.status === 'fail') {
88 | // if 7 days have passed since this was completed, they can do it again so we remove it from their user array
89 | if(completeDate.addDays(7) > today) {
90 | refreshQuests.push(quest);
91 | Quests.get({id : quest.questId}, function(q) {
92 | q.completionTime = quest.completionTime; //add completion time so we can sort them
93 | if (quest.status === 'success') {
94 | $scope.successfulQuests.push(q);
95 | } else {
96 | $scope.failedQuests.push(q);
97 | }
98 | });
99 | }
100 | } else {
101 | refreshQuests.push(quest);
102 | }
103 | }(j));
104 | }
105 |
106 | $scope.user.quests = refreshQuests;
107 | User.update($scope.user);
108 | };
109 |
110 | $scope.all();
111 |
112 | $scope.showList = {
113 | steps: true,
114 | distance: true,
115 | strength: true,
116 | sleep: true,
117 | succeed: true,
118 | fail: true
119 | }
120 |
121 | $scope.toggleList = function (list) {
122 | $scope.showList[list] = !$scope.showList[list];
123 | };
124 |
125 | });
126 |
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
FitRPG
96 |
Get more out of your healthy lifestyle
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/www/app/battle/battle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/www/app/shop/shop.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
93 |
94 |
--------------------------------------------------------------------------------
/www/app/friends/friends-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.friends.controllers')
2 |
3 | .controller('FriendsCtrl', function($scope, $state, User, $ionicLoading, $ionicListDelegate, $ionicPopup, $q) {
4 | // friends is accessed from $rootScope.user
5 | $scope.friends = [];
6 | $scope.friendRequests = [];
7 |
8 | $scope.showList = {
9 | friend: true,
10 | pending: true,
11 | request: true,
12 | };
13 |
14 | $scope.toggleList = function(list) {
15 | $scope.showList[list] = !$scope.showList[list];
16 | };
17 |
18 | var loading = setTimeout(function(){
19 | $ionicLoading.show({
20 | template: 'Loading...
'
21 | });
22 | }, 500);
23 |
24 | var stopLoading = function() {
25 | clearTimeout(loading);
26 | $ionicLoading.hide();
27 | };
28 |
29 | if ($scope.user.friends.length === 0) {
30 | stopLoading();
31 | }
32 |
33 | for (var i=0; i<$scope.user.friends.length; i++) {
34 | var friend = $scope.user.friends[i];
35 | User.get({id: friend}, function(user){
36 | if (user._id) {
37 | $scope.friends.push(user);
38 | $scope.hasFriends = true;
39 | }
40 | stopLoading();
41 | });
42 | }
43 |
44 | if ($scope.user.friendRequests) {
45 | for (var i=0; i<$scope.user.friendRequests.length; i++) {
46 | var friendRequest = $scope.user.friendRequests[i];
47 | var getFriend = function(request) {
48 | User.get({id: friendRequest.id}, function(friend) {
49 | friend.requestStatus = request.status;
50 | $scope.friendRequests.push(friend);
51 | });
52 | };
53 | getFriend(friendRequest);
54 | }
55 | }
56 |
57 | var removeFriendRequest = function(user, friendId) {
58 | var index;
59 | for (var i=0; i 0) {
31 | return 'text-green';
32 | } else {
33 |
34 | return 'text-red';
35 | }
36 | };
37 |
38 | $scope.sellItem = function() {
39 | var title, body, callback;
40 | if (item.equipped === false) {
41 | $scope.user.attributes.gold = $scope.user.attributes.gold + $scope.inventoryItem.sellPrice;
42 | if ($scope.inventoryItem.type.toLowerCase() !== 'potion') {
43 | // remove from inventory
44 | $scope.user.inventory.splice(index, 1);
45 | } else {
46 | if (item.quantity > 1) {
47 | item.quantity -= 1;
48 | } else if (item.quantity === 1) {
49 | $scope.user.inventory.splice(index, 1);
50 | }
51 | }
52 | // save user
53 | User.update($scope.user);
54 | title = 'Item Sold';
55 | body = 'You received ' + $scope.inventoryItem.sellPrice + ' gold for your item.';
56 | callback = function() {
57 | $state.go('app.character');
58 | }
59 | } else {
60 | title = 'Item Equipped';
61 | body = 'You must unequip your item before you can sell it.';
62 | callback = function(){};
63 | }
64 |
65 | util.showAlert($ionicPopup, title, body, 'OK', callback);
66 | };
67 |
68 | var setEquippedItem = function(slot, inventoryItem, name) {
69 | $scope.user.equipped[slot].name = name;
70 | $scope.user.equipped[slot].inventoryId = parseFloat(inventoryItem.id);
71 | return true;
72 | };
73 |
74 | var addItemAttributes = function() {
75 | $scope.user.attributes.strength += $scope.inventoryItem.strength;
76 | $scope.user.attributes.vitality += $scope.inventoryItem.vitality;
77 | $scope.user.attributes.dexterity += $scope.inventoryItem.dexterity;
78 | $scope.user.attributes.endurance += $scope.inventoryItem.endurance;
79 | };
80 |
81 | $scope.equipItem = function() {
82 | var itemSet = false;
83 | if (item.equipped === false) {
84 | if ($scope.inventoryItem.type.toLowerCase() === 'weapon') {
85 | if ($scope.inventoryItem.size === 1) {
86 | if ($scope.user.equipped.weapon1.name === '') {
87 | itemSet = setEquippedItem('weapon1',item,$scope.inventoryItem.name)
88 | } else if ($scope.user.equipped.weapon2.name === '') {
89 | itemSet = setEquippedItem('weapon2',item,$scope.inventoryItem.name)
90 | }
91 | } else if ($scope.inventoryItem.size === 2) {
92 | if ($scope.user.equipped.weapon1.name === '' && $scope.user.equipped.weapon2.name === '') {
93 | itemSet = setEquippedItem('weapon1',item,$scope.inventoryItem.name)
94 | itemSet = setEquippedItem('weapon2',item,$scope.inventoryItem.name)
95 | }
96 | }
97 | } else if ($scope.inventoryItem.type.toLowerCase() === 'armor') {
98 | if ($scope.user.equipped.armor.name === '') {
99 | itemSet = setEquippedItem('armor',item,$scope.inventoryItem.name)
100 | }
101 | } else if ($scope.inventoryItem.type.toLowerCase() === 'accessory') {
102 | if ($scope.user.equipped.accessory1.name === '') {
103 | itemSet = setEquippedItem('accessory1',item,$scope.inventoryItem.name)
104 | } else if ($scope.user.equipped.accessory2.name === '') {
105 | itemSet = setEquippedItem('accessory2',item,$scope.inventoryItem.name)
106 | }
107 | }
108 | if (itemSet) {
109 | item.equipped = true;
110 | addItemAttributes();
111 | User.update($scope.user);
112 | util.showAlert($ionicPopup, 'Item Equipped','You are ready to wage war against the forces of evil.', 'OK', function() {
113 | $state.go('app.character');
114 | })
115 | } else {
116 | util.showAlert($ionicPopup, 'Remove An Item','You are holding too many items. You need to take off an item before you can put this on.', 'OK', function() {
117 | $state.go('app.character');
118 | })
119 | }
120 | } else {
121 | util.showAlert($ionicPopup, 'Item Already Equipped','You are already using this item. Select a different item to equip.', 'OK', function() {
122 | $state.go('app.character');
123 | })
124 | }
125 | };
126 |
127 | $scope.useItem = function() {
128 | var totalVitality = $scope.user.attributes.vitality + $scope.user.fitbit.vitality;
129 | var maxHp = util.vitalityToHp(totalVitality,$scope.user.characterClass);
130 | if (item.quantity > 0) {
131 | $scope.user.attributes.HP += $scope.inventoryItem.hp;
132 | if ($scope.user.attributes.HP > maxHp) {
133 | $scope.user.attributes.HP = maxHp;
134 | }
135 | // subtract quantity from inventory -> remove if quantity = 0
136 | item.quantity -= 1;
137 | }
138 |
139 | if (item.quantity === 0) {
140 | $scope.user.inventory.splice(index, 1);
141 | }
142 |
143 | User.update($scope.user);
144 | util.showAlert($ionicPopup, 'HP Recovered','Your HP is recovering!', 'OK', function() {
145 | $state.go('app.character');
146 | })
147 | };
148 |
149 | $scope.checkType = function() {
150 | if ($scope.inventoryItem) {
151 | if ($scope.inventoryItem.type.toLowerCase() === 'potion') {
152 | return true;
153 | } else {
154 | return false;
155 | }
156 | }
157 | };
158 | });
159 |
--------------------------------------------------------------------------------
/www/app/quest/quest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
115 |
116 |
--------------------------------------------------------------------------------
/www/app/components/utility/utility.js:
--------------------------------------------------------------------------------
1 | var util = {
2 | vitalityToHp: function(vitality, charClass){
3 | var hp;
4 | // change character classes
5 | if (charClass === 'strength' || charClass === 'endurance') {
6 | hp = vitality * 10;
7 | } else if (charClass === 'dexterity') {
8 | hp = vitality * 12;
9 | } else if (charClass === 'vitality') {
10 | hp = vitality * 15;
11 | } else {
12 | hp = vitality * 10;
13 | }
14 |
15 | return hp;
16 | },
17 |
18 | attack: function(first,second,count) {
19 | first.attackBonus = first.attackBonus || 1;
20 | if (Math.floor(count%(100/(first.endurance/20))) === 0) {
21 | if (Math.random() < 1/(1+second.dexterity/25)) {
22 | var strength = first.strength;
23 | if (Math.random() < 0.05) {
24 | strength *= 2;
25 | }
26 | // console.log(strength);
27 | return Math.floor(second.HP - strength*first.attackBonus);
28 | }
29 | }
30 | return second.HP;
31 | },
32 |
33 | battleTurns: function(player1Attr, player2Attr) {
34 | var firstAttack = Math.random();
35 | var count = 0;
36 | var characterBonus = function(player) {
37 | if (player.characterClass === 'strength') {
38 | player.strength *= 1.1;
39 | } else if (player.characterClass === 'dexterity') {
40 | player.dexterity *= 1.4;
41 | } else if (player.characterClass === 'endurance') {
42 | player.endurance *= 1.1;
43 | } else if (player.characterClass === 'vitality') {
44 | player.dexterity *= 1.2;
45 | }
46 | };
47 |
48 | characterBonus(player1Attr);
49 | characterBonus(player2Attr);
50 |
51 | while (player1Attr.HP > 0 && player2Attr.HP > 0) {
52 | count++;
53 | if (firstAttack >= 0.5) {
54 | player2Attr.HP = this.attack(player1Attr,player2Attr,count);
55 | if (player2Attr.HP > 0) {
56 | player1Attr.HP = this.attack(player2Attr,player1Attr,count);
57 | } else {
58 | break;
59 | }
60 | } else {
61 | player1Attr.HP = this.attack(player2Attr,player1Attr,count);
62 | if (player1Attr.HP > 0) {
63 | player2Attr.HP = this.attack(player1Attr,player2Attr,count);
64 | } else {
65 | break;
66 | }
67 | }
68 | }
69 | },
70 |
71 | playerAttr: function(player) {
72 | return {
73 | strength: player.attributes.strength + player.fitbit.strength,
74 | endurance: player.attributes.endurance + player.fitbit.endurance,
75 | dexterity: player.attributes.dexterity + player.fitbit.dexterity,
76 | HP: player.attributes.HP,
77 | attackBonus: player.fitbit.attackBonus,
78 | };
79 | },
80 |
81 | battle: function(player1, player2){
82 | var player1Attr = this.playerAttr(player1);
83 | var player2Attr = this.playerAttr(player2);
84 |
85 |
86 | this.battleTurns(player1Attr,player2Attr);
87 | console.log(player1Attr.HP, player2Attr.HP);
88 |
89 | if (player1Attr.HP > player2Attr.HP) {
90 | return {result:'player 1', hp: player1Attr.HP};
91 | } else {
92 | return {result:'player 2', hp: player2Attr.HP};
93 | }
94 |
95 | },
96 |
97 | bossBattle: function(player,boss) {
98 | var player1 = this.playerAttr(player);
99 | var count = 0;
100 | if (boss.difficulty !== null) {
101 | boss.HP = boss.vitality*5*boss.difficulty;
102 | } else {
103 | boss.HP = boss.vitality*10;
104 | }
105 |
106 | this.battleTurns(player1,boss);
107 | console.log(player1.HP,boss.HP);
108 |
109 | if (player1.HP > boss.HP) {
110 | return {result:'player', hp: player1.HP};
111 | } else {
112 | return {result:'boss', hp: 0};
113 | }
114 | },
115 |
116 | capitalize: function(string) {
117 | return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
118 | },
119 |
120 | showAlert: function(controller, title, body, button, callback) {
121 | var alertPopup = controller.alert({
122 | title: title,
123 | template: body,
124 | okText: button
125 | });
126 | alertPopup.then(function(res) {
127 | callback();
128 | });
129 | },
130 |
131 | showPrompt: function(controller,title,body,okText,cancelText,callbackTrue,callbackFalse) {
132 | var confirmPopup = controller.confirm({
133 | title: title,
134 | template: body,
135 | okText: okText,
136 | cancelText : cancelText
137 | });
138 | confirmPopup.then(function(res) {
139 | if(res) {
140 | callbackTrue();
141 | } else if (callbackFalse) {
142 | callbackFalse();
143 | }
144 | });
145 | },
146 |
147 | showPopup: function(controller,title,body,btn1,btn2,cancelBtn,callbackTrue,callbackFalse) {
148 | var myPopup = controller.show({
149 | title: title,
150 | template: body,
151 | buttons: [
152 | {text: cancelBtn},
153 | {text: btn1,
154 | onTap: function(e) {
155 | return 'btn1';
156 | }
157 | },
158 | {text: btn2,
159 | onTap: function(e) {
160 | return 'btn2';
161 | }
162 | }
163 | ]
164 | });
165 | myPopup.then(function(res) {
166 | if(res === 'btn1') {
167 | callbackFalse();
168 | } else if (res === 'btn2') {
169 | callbackTrue();
170 | }
171 | });
172 | },
173 |
174 | currentLevelExp: function(lvl,exp) {
175 | return exp - (100*Math.pow(lvl-1,3) + 360*Math.pow(lvl-1,2) + 3500*(lvl-1));
176 | },
177 |
178 | nextLevelExp: function(lvl) {
179 | return (100*Math.pow(lvl,3) + 360*Math.pow(lvl,2) + 3500*lvl) - (100*Math.pow(lvl-1,3) + 360*Math.pow(lvl-1,2) + 3500*(lvl-1));
180 | },
181 |
182 | levelExp: function(lvl) {
183 | return 100*Math.pow(lvl-1,3) + 360*Math.pow(lvl-1,2) + 3500*(lvl-1);
184 | },
185 |
186 | calcLevel: function(experience, currLvl) {
187 | var level = currLvl || 1;
188 | var total = experience;
189 | var expToLevel = function(lvl) {
190 | return 100*Math.pow(lvl,3) + 360*Math.pow(lvl,2) + 3500*lvl;
191 | };
192 | while (expToLevel(level) <= total) {
193 | level++;
194 | }
195 | return level;
196 | },
197 |
198 | calcSkillPoints: function(currSkillPts, lvl, currLvl) {
199 | return currSkillPts + (lvl-currLvl)*5;
200 | },
201 |
202 | };
203 |
204 | // Necessary to format to the way Fitbit wants our dates
205 | Date.prototype.yyyymmdd = function() {
206 | var yyyy = this.getFullYear().toString();
207 | var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
208 | var dd = this.getDate().toString();
209 | return yyyy + '-' + (mm[1]?mm:'0'+mm[0]) + '-' + (dd[1]?dd:'0'+dd[0]);
210 | };
211 |
212 | // Useful to add days and hours to the start time/day
213 | Date.prototype.addDays = function(days,hours) {
214 | var date = new Date(this.valueOf());
215 | date.setDate(date.getDate() + days);
216 | if(hours) {
217 | date.setHours(this.getHours()+hours);
218 | }
219 | return date;
220 | };
--------------------------------------------------------------------------------
/www/app/main/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{alertCount}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
151 |
152 |
--------------------------------------------------------------------------------
/www/assets/css/style.css:
--------------------------------------------------------------------------------
1 | /*#f3796a*/
2 | /*#adaeae*/
3 |
4 | /*Quest page*/
5 | .questTimer {
6 | text-align: center;
7 | font-style: italic;
8 | }
9 |
10 | .battleStatus {
11 | float:right;
12 | }
13 |
14 | /* FAQ & HELP PAGE */
15 | .item.faq {
16 | font-size:17px;
17 | white-space: normal;
18 | font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
19 | padding:11px 11px 11px 11px;
20 | }
21 |
22 | .list.marg {
23 | margin:3px 10px -5px 10px;
24 | }
25 |
26 | .scrollUp {
27 | position:absolute;
28 | right:-36px;
29 | top:15px;
30 | }
31 |
32 | /* Make sure text wraps around */
33 | .item.list-heading.faqanswer h1 {
34 | white-space:normal;
35 | padding-right:13px;
36 | }
37 |
38 | .item.list-heading.faqanswer p {
39 | white-space:normal;
40 | }
41 |
42 | .item.item-padding.noquest {
43 | white-space:normal;
44 | }
45 |
46 | /* MAIN CHARACTER SCREEN */
47 | .bar-custom {
48 | background-color: #fff;
49 | border-bottom: 2px solid #f3796a;
50 | }
51 |
52 | .icon-gray {
53 | color: #777;
54 | }
55 |
56 | .bar .title {
57 | color: #f3796a;
58 | text-align: left;
59 | left: 52px !important;
60 | }
61 |
62 | .item-main {
63 | background-color: #f3796a;
64 | padding-left: 85px;
65 | }
66 |
67 | .item-main h3 {
68 | font-weight: bold;
69 | color: #fff;
70 | }
71 |
72 | .item-main p {
73 | color: #fff;
74 | font-size: 12px;
75 | }
76 |
77 | .item-main > img:first-child {
78 | top: 0;
79 | left: 0;
80 | max-width: 70px;
81 | max-height: 100%;
82 | border-radius: 0;
83 | }
84 |
85 | .tab-main-left {
86 | text-align: left;
87 | padding-left: 20px;
88 | }
89 |
90 | .tab-main-right {
91 | text-align: right;
92 | padding-right: 20px;
93 | }
94 |
95 | .title-red {
96 | font-size: 20px;
97 | color: #f3796a;
98 | }
99 |
100 | .small-text {
101 | font-size: 10px;
102 | }
103 |
104 | .medium-text {
105 | font-size: 11px;
106 | }
107 |
108 | .main-attributes {
109 | text-align: center;
110 | }
111 |
112 | .main-attributes i {
113 | position: relative;
114 | float: right;
115 | top: 0px;
116 | right: 0px;
117 | }
118 |
119 | .main-attributes h1 {
120 | font-size: 20px;
121 | color: #777;
122 | margin-top: 5px;
123 | margin-bottom: 0;
124 | }
125 |
126 | .main-attributes p {
127 | font-size: 11px;
128 | }
129 |
130 | .progress-bar {
131 | background-color: #f3796a;
132 | }
133 |
134 | .card-title {
135 | background-color: #f3796a;
136 | font-weight: bold;
137 | color: #fff;
138 | }
139 |
140 | .list-heading {
141 | color: #777;
142 | border-top: none;
143 | border-bottom: none;
144 | }
145 |
146 | .list-heading h1 {
147 | font-size: 16px;
148 | font-weight: normal;
149 | color: #777;
150 | padding-left: 10px;
151 | padding-bottom: 5px;
152 | border-bottom: thin solid #eee;
153 | }
154 |
155 | .list-heading p {
156 | font-size: 14px;
157 | padding-left: 15px;
158 | padding-bottom: 10px;
159 | }
160 |
161 | .button-custom {
162 | background-color: #f3796a;
163 | text-align: center;
164 | color: #fff;
165 | border: 0;
166 | font-size: 20px;
167 | font-style: normal;
168 | padding: 7px 30px 7px 30px;
169 | }
170 |
171 | .no-bottom-margin {
172 | margin-bottom: 0px;
173 | }
174 |
175 | /* END CHARACTER SCREEN */
176 |
177 | /* BEGIN LIST SCREEN */
178 |
179 | .button-tab {
180 | padding: 0;
181 | margin: 0;
182 | border: none;
183 | }
184 |
185 | .button-bar > .button-tab-active {
186 | border-bottom: 2px solid #f3796a;
187 | }
188 |
189 | .bar-light .button {
190 | font-size: 14px;
191 | color: #f3796a;
192 | }
193 |
194 | .item-divider {
195 | padding: 4px 25px;
196 | min-height: 18px;
197 | font-size: 14px;
198 | font-weight: 700;
199 | color: #f3796a;
200 | }
201 |
202 | .has-subheader {
203 | top: 84px;
204 | }
205 |
206 | .custom-subheader {
207 | padding-bottom: 0;
208 | height: 39px;
209 | }
210 |
211 | .item p {
212 | margin: 0;
213 | }
214 |
215 | .item-padding {
216 | padding: 10px 25px;
217 | }
218 |
219 | /* END LIST SCREEN */
220 |
221 | /* BEGIN BATTLE STATS */
222 | .battle-stats {
223 | text-align: center;
224 | }
225 |
226 | .battle-stats h1 {
227 | font-size: 20px;
228 | color: #777;
229 | margin-top: 5px;
230 | margin-bottom: 0;
231 | }
232 |
233 | .battle-stats p {
234 | font-size: 11px;
235 | }
236 | /* END BATTLE STATS */
237 |
238 | /* MISC */
239 |
240 | .alert-dismissable .close {
241 | width: 33px;
242 | height: 41px;
243 | top: -12px;
244 | right: -31px;
245 | }
246 |
247 | /* END MISC */
248 |
249 | ion-item.refresh.item {
250 | border-style:none;
251 | width: 15px;
252 | right:14px;
253 | position:absolute;
254 | background-color: #EEEEEE;
255 | margin-bottom:20px;
256 | }
257 |
258 | i.ion-refresh.refresh {
259 | position: absolute;
260 | right:13px;
261 | font-size:19px;
262 | color:gray;
263 | top: 0px;
264 | }
265 |
266 | .username {
267 | height: 46px;
268 | padding: 10px 16px;
269 | font-size: 18px;
270 | line-height: 1.33;
271 | border-radius: 6px;
272 | border-style: double;
273 | border-color: black;
274 | }
275 |
276 | .padding-horizontal {
277 | padding: 30px;
278 | }
279 |
280 | .row {
281 | margin-left: 0;
282 | margin-right: 0;
283 | }
284 |
285 | .dashboard {
286 | background-color: #EEEEEE;
287 | }
288 |
289 | .text-red {
290 | color: red;
291 | font-weight: bold;
292 | }
293 |
294 | .text-green {
295 | color: green;
296 | font-weight: bold;
297 | }
298 |
299 | .donut {
300 | margin: 0 auto;
301 | width: 200px;
302 | }
303 |
304 | .popup {
305 | border-radius: 12px;
306 | }
307 |
308 | .popup-head {
309 | background-color: #ef4e3a;
310 | border-top-left-radius: 12px;
311 | border-top-right-radius: 12px;
312 | }
313 |
314 | .popup-title {
315 | color: #fff;
316 | }
317 |
318 | .popup-body {
319 | text-align: center;
320 | min-height: 100px;
321 | }
322 |
323 | .button {
324 | border-width: 0;
325 | }
326 |
327 | .popup-buttons.row {
328 | padding: 10px 40px;
329 | }
330 |
331 | .popup-buttons .button {
332 | border-radius: 45px;
333 | }
334 |
335 | .button.button-positive {
336 | background-color: #777;
337 | }
338 |
339 | .button-swipe {
340 | padding-top: 15px;
341 | }
342 |
343 | .item-input {
344 | margin: 0;
345 | }
346 |
347 | .card .item:last-child, .list-inset .item:last-child {
348 | margin: 0;
349 | }
350 |
351 | #login {
352 | background-color: #f3796a
353 | }
354 |
355 | .login-text {
356 | position: absolute;
357 | width: 250px;
358 | text-align: center;
359 | left: 50%;
360 | top: 20%;
361 | margin-left: -125px;
362 | }
363 |
364 | .login-text h1{
365 | color: #fff;
366 | font-size: 50px;
367 | }
368 |
369 | .login-text p{
370 | color: #fff;
371 | font-size: 21px;
372 | line-height: 1.2em;
373 | }
374 |
375 | .loginbutton {
376 | position: absolute;
377 | left: 50%;
378 | margin-left: -150px;
379 | max-width: 300px;
380 | bottom: 15%;
381 | }
382 |
383 | .scroll-content {
384 | padding-bottom: 20px;
385 | }
386 |
387 | .icon-btn {
388 | padding: 0 10px;
389 | }
--------------------------------------------------------------------------------
/www/app/help/help-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.help.controllers')
2 |
3 | .controller('HelpCtrl', function($scope, $stateParams, $location, $ionicScrollDelegate) {
4 |
5 | $scope.scrollTo = function(id){
6 | $location.hash(id);
7 | $ionicScrollDelegate.anchorScroll(true)
8 | };
9 |
10 | $scope.scrollTop = function() {
11 | $ionicScrollDelegate.scrollTop(true);
12 | };
13 |
14 |
15 | $scope.questions = [
16 | {question: 'What is the purpose of FitRPG?', goTo: 'purpose' },
17 | {question: 'How does FitRPG turn my fitness data into my character attributes?', goTo: 'convert' },
18 | {question: 'How can I improve my character\'s attributes?', goTo: 'attributes' },
19 | {question: 'How can I level up my character?', goTo: 'levelUp' },
20 | {question: 'What is a battle and how do I battle someone?', goTo: 'battle' },
21 | {question: 'What is a quest and how does it work?', goTo: 'quest' },
22 | {question: 'How often can I do a specific quest?', goTo: 'questFrequency' },
23 | {question: 'What are skill points and what can I do with them?', goTo: 'skillPts' },
24 | {question: 'How do attributes help me in battle?', goTo: 'attributeDef' },
25 | {question: 'What are weapons, equipment, and potions? How do I buy them?', goTo: 'purchasing' },
26 | {question: 'What can I do with the weapons I buy?', goTo: 'weapons' },
27 | {question: 'What is weapon size?', goTo: 'weaponsize' },
28 | {question: 'How do I equip my character with weapons I\'ve bought?', goTo: 'equip' },
29 | {question: 'How do I add friends that don\'t show up in search?', goTo: 'addFriends' },
30 | {question: 'How does FitRPG retrieve my Fitbit data, and how do I make sure it is synced?', goTo: 'fitbitdata' },
31 | {question: 'How much of my personal fitness data can my friends actually see?', goTo: 'privacy' },
32 | {question: 'How can I contact you with further questions or feedback?', goTo: 'feedback' }
33 | ];
34 |
35 | $scope.answers = [
36 | { question: 'What is the purpose of FitRPG?', id: 'purpose',
37 | answer: 'FitRPG encourages a healthier lifestyle by coupling it with an RPG that turns your fitness data into skills, experience points, and HP that you can use to engage in battles against your friends. You can also go on quests to earn more gold and experience points.'
38 | },
39 | { question: 'How does FitRPG turn my fitness data into my character attributes?', id: 'convert',
40 | answer: 'We only start using the data you have from the day you signed up with FitRPG. We don\'t want people to come in with too much of an advantage. We use fun and fancy algorithms to turn your data into points. Steps are converted to experience; sleep is converted to vitality and HP recovery; distance is converted to endurance; and the workouts you log manually are converted to strength or dexterity. Your level is based off of your attributes and the points you win in the game.'
41 | },
42 | { question: 'How can I improve my character\'s attributes?', id: 'attributes',
43 | answer: 'More steps! Also more sleep and logging your workouts will improve your character\'s other attributes. Going on quests or battling friends/bosses will also win you skill points you can convert to attribute points.'
44 | },
45 | { question: 'How can I level up my character?', id: 'levelUp',
46 | answer: 'You level up when you gain experience through steps, battles, or quests.'
47 | },
48 | { question: 'What is a battle and how do I battle someone?', id: 'battle',
49 | answer: 'A battle is a one-to-one fight where your attributes are stacked up against your friend or the boss\'s attributes. Our game decides who is the most FIT and who deserves to win.'
50 | },
51 | { question: 'What is a quest and how does it work?', id: 'quest',
52 | answer: 'Quests are ways to motivate you to accomplish specific fitness goals. There are four categories of quests: Steps, distance, sleep, and strength training. They all have their own time limits that range from a few hours to a week. You choose what you want to do and you can continuously check your progress. Once your time has expired, if you\'ve completed the quest, you gain gold and experience points. If not, you lose some.'
53 | },
54 | { question: 'How often can I do a specific quest?', id: 'questFrequency',
55 | answer: 'We only let you do a quest once a week, and we only let you do one of each type of quest at a time.'
56 | },
57 | { question: 'What are skill points and what can I do with them?', id: 'skillPts',
58 | answer: 'You gain skill points through leveling up. Each point you get is one you can add to your endurance, dexterity, strength, or vitality. When you have skill points, you will see a small plus button next to your attributes on your dashboard, and you can tap it to increase that attribute.'
59 | },
60 | { question: 'How do attributes help me in battle?', id: 'attributeDef',
61 | answer: 'Strength increases your attack damage. Dexterity increases your evasion rate. Endurance increases your attack rate. Vitality increases your max HP.'
62 | },
63 | { question: 'What are weapons, equipment, and potions? How do I buy them?', id: 'purchasing' ,
64 | answer: 'You can equip your character with weapons and equipment to give yourself a better chance to beat bosses or friends. To buy them you have to have gold that you\'ve won from quests or battles. Go to the store from the side menu. You can buy multiples of the same item'
65 | },
66 | { question: 'What can I do with the weapons I buy?', id: 'weapons',
67 | answer: 'Weapons will increase some of your attributes. Depending on the weapon, your strength/dexterity/endurance/vitality may increase by a certain number, to give you an advantage when battling a stronger friend or boss.'
68 | },
69 | { question: 'What is weapon size?', id: 'weaponsize',
70 | answer: 'The weapon size is the amount of slots a weapon will take up when equipped. A two-handed weapon will take two slots while a one-handed weapon will take only one slot and therefore two one-handed weapons can be equipped.'
71 | },
72 | { question: 'How do I equip my character with weapons I\'ve bought?', id: 'equip' ,
73 | answer: 'You can equip your character with weapons and equipment to give yourself a better chance to beat bosses or friends. To buy them you have to have gold that you\'ve won from quests or battles. Go to the store from the side menu.'
74 | },
75 | { question: 'How do I add friends that don\'t show up in search?', id: 'addFriends',
76 | answer: 'You can either add friends through Fitbit and if they have our app, they will show up in your friends page. Otherwise, as long as they also have FitRPG installed, you can find them through going to friends and tapping the icon in the top right to search.'
77 | },
78 | { question: 'How does FitRPG retrieve my Fitbit data, and how do I make sure it is synced?', id: 'fitbitdata',
79 | answer: 'Every time you log in, we retrieve new data from Fitbit. You also have to refresh to see updated data when you sync with Fitbit. To refresh, just drag down the page and the refresh icon will appear at the top, while the app retrieves your updated data.'
80 | },
81 | { question: 'How much of my personal fitness data can my friends actually see?', id: 'privacy',
82 | answer: 'Your friends cannot see any of your actual fitness data or even your attributes right now. They can only see what level you are, your username, and your picture (and how you rank in the leaderboard ;)!)'
83 | },
84 | { question: 'How can I contact you with further questions or feedback?', id: 'feedback',
85 | answer: 'Email us at FitRPG@gmail.com for anything we may have missed!'
86 | }
87 | ];
88 |
89 | });
90 |
--------------------------------------------------------------------------------
/www/app/app.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile', [
2 | 'ionic',
3 | 'timer',
4 | 'ui.bootstrap',
5 | 'ngCordova',
6 | 'mobile.battle',
7 | 'mobile.authentication',
8 | 'mobile.resource',
9 | 'mobile.feedback',
10 | 'mobile.friends',
11 | 'mobile.help',
12 | 'mobile.inventory',
13 | 'mobile.leaderboard',
14 | 'mobile.main',
15 | 'mobile.quest',
16 | 'mobile.select',
17 | 'mobile.shop',
18 | ])
19 |
20 | .run(function($rootScope,$ionicPlatform,$state,$ionicNavBarDelegate,$cordovaPush,$window) {
21 | $ionicPlatform.ready(function() {
22 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
23 | // for form inputs)
24 | if(window.cordova && window.cordova.plugins.Keyboard) {
25 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
26 | }
27 |
28 | ionic.Platform.fullScreen();
29 |
30 | if(window.cordova) {
31 | var device = {
32 | isApple: ionic.Platform.isIOS(),
33 | isGoogle: ionic.Platform.isAndroid(),
34 | };
35 |
36 | var androidConfig = {
37 | 'senderID':'replace_with_sender_id',
38 | 'ecb':'onNotification'
39 | };
40 |
41 | var iosConfig = {
42 | 'badge':'true',
43 | 'sound':'true',
44 | 'alert':'true',
45 | 'ecb':'onNotificationAPN'
46 | };
47 |
48 | var config;
49 |
50 | if (device.isApple) {
51 | config = iosConfig;
52 | } else if (device.isGoogle) {
53 | config = androidConfig;
54 | }
55 |
56 | // $cordovaPush.register(config).then(function(result) {
57 | // console.log(result);
58 | // }, function(err) {
59 | // console.log(err);
60 | // });
61 | }
62 |
63 | });
64 |
65 | $ionicPlatform.registerBackButtonAction(function () {
66 | if ($state.current.name === 'app.character') {
67 | navigator.app.exitApp();
68 | } else if($state.current.name === 'app.feedback' || $state.current.name === 'app.inventory' || $state.current.name === 'app.shop' || $state.current.name === 'app.friends' || $state.current.name === 'app.battle' || $state.current.name === 'app.quest' || $state.current.name === 'app.leaderboard' || $state.current.name === 'app.help') {
69 | $state.go('app.character');
70 | } else {
71 | $ionicNavBarDelegate.back();
72 | }
73 | }, 100);
74 | })
75 |
76 | .config(function($stateProvider, $urlRouterProvider) {
77 |
78 | $stateProvider
79 |
80 | .state('select', {
81 | url: '/select',
82 | templateUrl: 'app/select/select-class.html',
83 | controller: 'SelectClassCtrl'
84 | })
85 |
86 | .state('username', {
87 | url: '/username',
88 | templateUrl: 'app/select/select-username.html',
89 | controller: 'UsernameCtrl'
90 | })
91 |
92 | .state('app', {
93 | url: "/app",
94 | abstract: true,
95 | templateUrl: "app/menu/menu.html"
96 | })
97 |
98 | .state('app.feedback', {
99 | url: '/feedback',
100 | views: {
101 | 'menuContent': {
102 | templateUrl: 'app/feedback/feedback.html',
103 | controller: 'FeedbackCtrl'
104 | }
105 | }
106 | })
107 |
108 | .state('app.character', {
109 | url: '/character',
110 | views: {
111 | 'menuContent': {
112 | templateUrl: 'app/main/main.html',
113 | controller: 'CharacterCtrl'
114 | }
115 | }
116 | })
117 |
118 | .state('app.inventory', {
119 | url: '/inventory',
120 | views: {
121 | 'menuContent': {
122 | templateUrl: 'app/inventory/inventory.html',
123 | controller: 'InventoryCtrl'
124 | }
125 | }
126 | })
127 | .state('app.inventory-detail', {
128 | url: '/inventory/:inventoryId',
129 | views: {
130 | 'menuContent': {
131 | templateUrl: 'app/inventory/inventory-detail.html',
132 | controller: 'InventoryDetailCtrl'
133 | }
134 | }
135 | })
136 |
137 | .state('app.shop', {
138 | url: '/shop',
139 | views: {
140 | 'menuContent': {
141 | templateUrl: 'app/shop/shop.html',
142 | controller: 'ShopCtrl'
143 | }
144 | }
145 | })
146 |
147 | .state('app.shop-detail', {
148 | url: '/shop/:shopId',
149 | views: {
150 | 'menuContent': {
151 | templateUrl: 'app/shop/shop-detail.html',
152 | controller: 'ShopDetailCtrl'
153 | }
154 | }
155 | })
156 |
157 | .state('app.friends', {
158 | url: '/friends',
159 | views: {
160 | 'menuContent': {
161 | templateUrl: 'app/friends/friends.html',
162 | controller: 'FriendsCtrl'
163 | }
164 | }
165 | })
166 |
167 | .state('app.addfriends', {
168 | url: '/friends/add',
169 | views: {
170 | 'menuContent': {
171 | templateUrl: 'app/friends/friends-add.html',
172 | controller: 'AddFriendsCtrl'
173 | }
174 | }
175 | })
176 |
177 | .state('app.battle', {
178 | url: '/battle',
179 | views: {
180 | 'menuContent': {
181 | templateUrl: 'app/battle/battle.html',
182 | controller: 'BattleCtrl'
183 | }
184 | }
185 | })
186 |
187 | .state('app.quest', {
188 | url: '/quests',
189 | views: {
190 | 'menuContent': {
191 | templateUrl: 'app/quest/quest.html',
192 | controller: 'QuestCtrl'
193 | }
194 | }
195 | })
196 |
197 | .state('app.quest-detail', {
198 | url: '/quest/:questId',
199 | views: {
200 | 'menuContent': {
201 | templateUrl: 'app/quest/quest-detail.html',
202 | controller: 'QuestDetailCtrl'
203 | }
204 | }
205 | })
206 |
207 | .state('app.leaderboard', {
208 | url: '/leaderboard',
209 | views: {
210 | 'menuContent': {
211 | templateUrl: 'app/leaderboard/leaderboard.html',
212 | controller: 'LeaderboardCtrl'
213 | }
214 | }
215 | })
216 |
217 | .state('app.help', {
218 | url: '/help',
219 | views: {
220 | 'menuContent': {
221 | templateUrl: 'app/help/help.html',
222 | controller: 'HelpCtrl'
223 | }
224 | }
225 | })
226 | })
227 |
228 | //PUSH NOTIFICATION FUNCTIONS
229 | function onNotificationAPN (event) {
230 | if ( event.alert ) {
231 | navigator.notification.alert(event.alert);
232 | }
233 |
234 | if ( event.sound ) {
235 | var snd = new Media(event.sound);
236 | snd.play();
237 | }
238 |
239 | if ( event.badge ) {
240 | $cordovaPush.setBadgeNumber(2).then(function(result) {
241 | // Success!
242 | }, function(err) {
243 | // An error occured. Show a message to the user
244 | });
245 | }
246 | };
247 |
248 | function onNotification(e) {
249 | console.log(e.event);
250 |
251 | switch( e.event ) {
252 | case 'registered':
253 | if ( e.regid.length > 0 )
254 | {
255 | // Your GCM push server needs to know the regID before it can push to this device
256 | // here is where you might want to send it the regID for later use.
257 | console.log("regID = " + e.regid);
258 | }
259 | break;
260 |
261 | case 'message':
262 | // if this flag is set, this notification happened while we were in the foreground.
263 | // you might want to play a sound to get the user's attention, throw up a dialog, etc.
264 | if ( e.foreground )
265 | {
266 | var soundfile = e.soundname || e.payload.sound;
267 | var my_media = new Media("/android_asset/www/"+ soundfile);
268 | my_media.play();
269 | }
270 | else
271 | { // otherwise we were launched because the user touched a notification in the notification tray.
272 | if ( e.coldstart ) {
273 | }
274 | else {
275 | }
276 | }
277 | console.log(e.payload.message, e.payload.msgcnt, e.payload.timeStamp)
278 | break;
279 |
280 | case 'error':
281 | break;
282 |
283 | default:
284 | break;
285 | }
286 | };
287 |
--------------------------------------------------------------------------------
/www/app/quest/quest-detail-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.quest.controllers')
2 |
3 | .constant('daysWeek', {
4 | 0: 'Sunday',
5 | 1: 'Monday',
6 | 2: 'Tuesday',
7 | 3: 'Wedneday',
8 | 4: 'Thursday',
9 | 5: 'Friday',
10 | 6: 'Saturday'
11 | })
12 |
13 | // This particular controller handles the individual pages of each quest
14 | .controller('QuestDetailCtrl', function($scope, $state, $stateParams, Quests, $ionicPopup, User, TimesData, DatesData, daysWeek, finishQuest, NewTimesData, $cordovaSocialSharing) {
15 | var questId = $stateParams.questId
16 | $scope.quest = Quests.get({id: questId});
17 | $scope.availableQuest = true;
18 | $scope.activeQuest = false;
19 | $scope.completedQuest = false;
20 | $scope.userQuest;
21 |
22 | // Show 1-5 stars on the screen depending on the difficulty
23 | $scope.difficulty = function(num) {
24 | return num <= $scope.quest.difficulty;
25 | };
26 |
27 | var sendTweet = function (message) {
28 | $cordovaSocialSharing.shareViaTwitter(message).then(function(result) {
29 | // Success!
30 | }, function(err) {
31 | // An error occured. Show a message to the user
32 | });
33 | };
34 |
35 | // Check if the user currently has this quest active or completed
36 | var pickOutQuest = function() {
37 | for (var i = 0; i < $scope.user.quests.length; i++) {
38 | $scope.winGoal = $scope.user.quests[i].winGoal;
39 | if ($scope.user.quests[i].questId === questId) {
40 | $scope.availableQuest = false;
41 | $scope.userQuest = $scope.user.quests[i];
42 | evalQuest();
43 | return; //stop looping
44 | }
45 | }
46 | };
47 |
48 | // Called in pickOutQuest, and when we refresh to show updated data
49 | // We also return true or false for whether or not the quest has been completed
50 | var evalQuest = function(cb) {
51 | if ($scope.userQuest.status === 'active') {
52 | $scope.activeQuest = true;
53 | $scope.$broadcast('timer-set-countdown');
54 | $scope.parsedDate = Date.parse($scope.userQuest.completionTime);
55 | if ($scope.userQuest.numDays < 1) {
56 | if ($scope.userQuest.getObj.startDate !== $scope.userQuest.getObj.endDate) {
57 | var newGetObjectBeforeMidnight = {};
58 | newGetObjectBeforeMidnight.startTime = $scope.userQuest.getObj.startTime;
59 | newGetObjectBeforeMidnight.endTime = '23:59';
60 | newGetObjectBeforeMidnight.startDate = $scope.userQuest.getObj.startDate;
61 | newGetObjectBeforeMidnight.id = $scope.userQuest.getObj.id;
62 | newGetObjectBeforeMidnight.activity = $scope.userQuest.getObj.activity;
63 | NewTimesData.get(newGetObjectBeforeMidnight, function(result) {
64 | var preMidnightTotal = parseInt(result.total) || 0;
65 | var newGetObjectAfterMidnight = {};
66 | newGetObjectAfterMidnight.startTime = '00:00';
67 | newGetObjectAfterMidnight.endTime = $scope.userQuest.getObj.endTime;
68 | newGetObjectAfterMidnight.startDate = $scope.userQuest.getObj.endDate;
69 | newGetObjectAfterMidnight.id = $scope.userQuest.getObj.id;
70 | newGetObjectAfterMidnight.activity = $scope.userQuest.getObj.activity;
71 | NewTimesData.get(newGetObjectAfterMidnight, function(result2) {
72 | var postMidnightTotal = parseInt(result2.total) || 0;
73 | $scope.progress = preMidnightTotal + postMidnightTotal;
74 | var completed = $scope.progress >= $scope.winGoal;
75 | console.log('completed',completed);
76 | if (cb) { cb(completed) };
77 | });
78 | });
79 | } else {
80 | TimesData.get($scope.userQuest.getObj, function(result) {
81 | $scope.progress = result.total || 0; //current progress
82 | var completed = $scope.progress >= $scope.winGoal;
83 | if (cb) { cb(completed) };
84 | });
85 | }
86 | } else if ($scope.userQuest.numDays > 0 ) { // multiday quests
87 | DatesData.get($scope.userQuest.getObj, function(result) {
88 | $scope.progress = result.total || 0; // current progress
89 | var completed = $scope.progress >= $scope.winGoal;
90 | if (cb) { cb(completed) };
91 | });
92 | }
93 | } else if ($scope.userQuest.status === 'success' || $scope.userQuest.status === 'fail') {
94 | $scope.completedQuest = true;
95 | if (cb) {cb()};
96 | }
97 | };
98 |
99 | // function that helps us format the times for dates to make calls to fitbit, so '5' is '05'
100 | var timify = function(time) {
101 | if (time.length === 1) {
102 | time = '0' + time;
103 | }
104 | return time;
105 | }
106 |
107 | $scope.startQuest = function() {
108 |
109 | var numDays = $scope.quest.numDays;
110 | var numHours = $scope.quest.numHours;
111 | var winGoal = $scope.quest.winGoals;
112 | var gold = $scope.quest.gold;
113 | var desc = $scope.quest.shortDescription;
114 | var start = new Date(); // start date
115 | var addD = numDays-1 >= 0 ? numDays-1 : 0; // the number of days we're adding to get to 'end'
116 | var startTime = timify(start.getHours()) + ':' + timify(start.getMinutes());
117 | var title = 'Embark On A New Quest!';
118 | var endString,end,body;
119 |
120 | // for multi-day quests, there's a type, MAY LATER ON HAVE TO ACCOUNT FOR DIFFERENT SLEEP QUESTS
121 | if (numDays >=1) {
122 | end = start.addDays(addD); // end date
123 | end.setHours(23);
124 | end.setMinutes(59); // timer always ends at midnight
125 | endString = daysWeek[end.getDay()] + ' at 11:59PM';
126 | body = 'This is the mission you\'ve chosen:' + $scope.quest.shortDescription +
127 | ' You will have from today until ' + endString +
128 | ' to complete this quest. Do you accept?';
129 | } else { // for one-day quests, we want to keep the times, otherwise we don't care
130 | end = start.addDays(addD,numHours); // end date
131 | endString =end.toLocaleTimeString();
132 | body = 'This is the mission you\'ve chosen:' + $scope.quest.shortDescription +
133 | ' You will have until ' + endString +
134 | ' to complete this quest. Do you accept?';
135 | }
136 |
137 | var startTheQuest = function() {
138 |
139 | // Generate the quest object to be saved to the user's quests array
140 | var questObj = {
141 | questId: questId,
142 | numDays : numDays,
143 | numHours : numHours,
144 | completionTime: end,
145 | status: 'active',
146 | winGoal: winGoal,
147 | gold : gold,
148 | shortDesc : desc,
149 | endString : endString,
150 | getObj: { // the query object to be used when calling $resource
151 | id : $scope.user._id,
152 | activity : $scope.quest.activity,
153 | startDate : start.yyyymmdd(),
154 | endDate : end.yyyymmdd(),
155 | }
156 | };
157 |
158 | if (numDays >= 1 ) {
159 | questObj.getObj.type = $scope.quest.type;
160 | } else {
161 | questObj.getObj.startTime = timify(start.getHours()) + ':' +timify(start.getMinutes());
162 | questObj.getObj.endTime = timify(end.getHours()) + ':' +timify(end.getMinutes());
163 | }
164 |
165 | $scope.availableQuest = false;
166 | $scope.user.quests.push(questObj);
167 | User.update($scope.user);
168 | $state.go('app.quest');
169 |
170 | };
171 |
172 | util.showPrompt($ionicPopup, title, body, 'I accept', 'Too scared', startTheQuest);
173 |
174 | };
175 |
176 | $scope.refresh = function() {
177 | evalQuest($scope.quest, function() {
178 | $scope.$broadcast('scroll.refreshComplete');
179 | });
180 | };
181 |
182 | $scope.manualCompleteQuest = function() {
183 | var title, body, endQuest, message;
184 | evalQuest(function(completed) {
185 | console.log('completed',completed);
186 | if(completed) {
187 | title = 'Success!';
188 | body = 'Congratulations! You completed this quest. You won ' + $scope.userQuest.gold + ' gold pieces.';
189 | message = 'I completed my quest: ' + $scope.userQuest.shortDesc + ' I\'m super fit! @fitrpg';
190 | endQuest = finishQuest.winQuest;
191 | } else {
192 | title = 'Fail!';
193 | body = 'Sorry, you did not complete your quest on time. You lost gold. Try again later.';
194 | message = 'I didn\'t complete my quest: ' + $scope.userQuest.shortDesc + ' I need to train more. @fitrpg';
195 | endQuest = finishQuest.loseQuest;
196 | }
197 | User.update(endQuest($scope.user,$scope.userQuest));
198 | util.showPrompt($ionicPopup, title, body, 'Share', 'Continue',
199 | function() {
200 | sendTweet(message);
201 | },
202 | function() {
203 | $state.go('app.quest');
204 | }
205 | );
206 | });
207 | };
208 |
209 | // Every time we load the page, we check to see if the quest is completed or not
210 | pickOutQuest();
211 | });
--------------------------------------------------------------------------------
/www/assets/fonts/icomoon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generated by IcoMoon
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/www/app/main/main-controller.js:
--------------------------------------------------------------------------------
1 | angular.module('mobile.main.controllers')
2 |
3 | .controller('CharacterCtrl', function(
4 | $rootScope,
5 | $scope,
6 | $state,
7 | $ionicLoading,
8 | $ionicNavBarDelegate,
9 | $ionicPopup,
10 | $ionicPlatform,
11 | User,
12 | Shop,
13 | Quests,
14 | TimesData,
15 | DatesData,
16 | Refresh,
17 | Settings,
18 | localStorageService,
19 | $window )
20 | {
21 | // initialize $rootScope.user to eliminate console errors before authentication
22 | var loading = setTimeout(function(){
23 | $ionicLoading.show({
24 | template: 'Loading...
'
25 | });
26 | }, 500);
27 |
28 | $scope.calculatedData = {};
29 | $scope.alertCount = 0;
30 | $scope.showAlert = false;
31 |
32 | var device = {
33 | isApple: ionic.Platform.isIOS(),
34 | isGoogle: ionic.Platform.isAndroid(),
35 | };
36 |
37 | var addAlert = function(status, name) {
38 | name = name || '';
39 | var type, msg;
40 | if (status === 'loss') {
41 | type = 'danger';
42 | msg = 'Looks like you need to work out more. You lost to ' + name + '.';
43 | } else if (status === 'win') {
44 | type = 'success';
45 | msg = 'You beat ' + name + '. You gained experience and gold.'
46 | } else if (status === 'request') {
47 | type = '';
48 | msg = 'Someone wants to battle you.';
49 | }
50 | $scope.alertCount++;
51 | $scope.alerts.push({type: type, msg: msg});
52 | };
53 |
54 | var addLevelUpAlert = function() {
55 | var type, msg;
56 | type = 'success';
57 | msg = 'You leveled up! You\'ve gained skill points to increase your attributes.';
58 | $scope.alertCount++;
59 | $scope.levelUpAlerts.push({type: type, msg: msg});
60 | };
61 |
62 | var addFriendRequestAlert = function() {
63 | var type, msg;
64 | type = 'success';
65 | msg = 'You have a new friend request. Click here to view the requests.';
66 | $scope.alertCount++;
67 | $scope.friendRequestAlerts.push({type: type, msg: msg});
68 | };
69 |
70 | var addQuestAlert = function(quest) {
71 | var type, msg;
72 | if (quest.status === 'success') {
73 | type = 'success';
74 | msg = 'You completed your quest to ' + quest.shortDesc.toLowerCase() + ' You won ' + quest.gold + " pieces!";
75 | } else if (quest.status === 'fail') {
76 | type = 'danger';
77 | msg = 'Sorry, you didn\'t finish your quest to ' + quest.shortDesc.toLowerCase() + ' You lost gold. Try again in a few days.'
78 | }
79 | $scope.alertCount++;
80 | $scope.questAlerts.push({type: type, msg: msg});
81 | };
82 |
83 | $scope.displayAlerts = function() {
84 | $scope.alertCount = 0;
85 | $scope.showAlert = true;
86 | }
87 |
88 | $scope.closeAlert = function(index) {
89 | $scope.alerts.splice(index, 1);
90 | };
91 |
92 | $scope.closeLevelUpAlert = function(index) {
93 | $scope.levelUpAlerts.splice(index, 1);
94 | };
95 |
96 | $scope.closeFriendRequestAlert = function(index) {
97 | $scope.friendRequestAlerts.splice(index, 1);
98 | };
99 |
100 | $scope.closeQuestAlert = function(index) {
101 | $scope.questAlerts.splice(index, 1);
102 | };
103 |
104 | var calculateData = function(user) {
105 | $scope.calculatedData.currentXp = Math.floor(util.currentLevelExp(user.attributes.level, user.fitbit.experience + user.attributes.experience));
106 | $scope.calculatedData.requiredXp = util.nextLevelExp(user.attributes.level);
107 | $scope.calculatedData.strength = user.attributes.strength + user.fitbit.strength;
108 | $scope.calculatedData.vitality = user.attributes.vitality + user.fitbit.vitality;
109 | $scope.calculatedData.dexterity = user.attributes.dexterity + user.fitbit.dexterity;
110 | $scope.calculatedData.endurance = user.attributes.endurance + user.fitbit.endurance;
111 | $scope.calculatedData.maxHp = util.vitalityToHp($scope.calculatedData.vitality,$scope.user.characterClass); //change to $scope.user.characterClass
112 | user.attributes.HP += user.fitbit.HPRecov;
113 | user.fitbit.HPRecov = 0;
114 | if (user.attributes.HP > $scope.calculatedData.maxHp) {
115 | user.attributes.HP = $scope.calculatedData.maxHp;
116 | }
117 | if (user.attributes.gold < 0) {
118 | user.attributes.gold = 0;
119 | }
120 | };
121 |
122 | var alertBattleStatus = function() {
123 | $scope.alerts = [];
124 | var listOfIndices = [];
125 | var alertWin = false;
126 | var alertLoss = false;
127 | var alertRequest = false;
128 | for (var i=0; i<$rootScope.user.missionsVersus.length; i++) {
129 | var alertMission = function(index){
130 | var mission = $rootScope.user.missionsVersus[index];
131 | if (mission.type === 'battle') {
132 | if (mission.status === 'win' || mission.status === 'loss') {
133 | User.get({id: mission.enemy}, function(enemy){
134 | addAlert(mission.status,enemy.profile.displayName);
135 | });
136 | listOfIndices.push(index);
137 | } else if (mission.status === 'request' && !alertRequest) {
138 | alertRequest = true;
139 | addAlert(mission.status);
140 | }
141 | }
142 | };
143 | alertMission(i);
144 | }
145 |
146 | var removeMission = function(index,count) {
147 | if (count < listOfIndices.length) {
148 | $rootScope.user.missionsVersus.splice(index-count,1);
149 | removeMission(listOfIndices[count+1],count+1);
150 | }
151 | };
152 |
153 | if (listOfIndices.length > 0) {
154 | removeMission(listOfIndices[0],0);
155 | }
156 |
157 | };
158 |
159 | var alertLevelUpStatus = function() {
160 | $scope.levelUpAlerts = [];
161 | var userLevel = localStorageService.get('level');
162 | var currentLevel = $rootScope.user.attributes.level;
163 | if (!userLevel) {
164 | userLevel = localStorageService.set('level',1);
165 | }
166 | if (userLevel < currentLevel) {
167 | localStorageService.set('level', currentLevel);
168 | addLevelUpAlert();
169 | }
170 | };
171 |
172 | var alertFriendRequestStatus = function() {
173 | $scope.friendRequestAlerts = [];
174 | var alertRequest = false;
175 | if ($rootScope.user.friendRequests) {
176 | for (var i=0; i<$rootScope.user.friendRequests.length; i++) {
177 | var request = $rootScope.user.friendRequests[i];
178 | if (request.status === 'request' && !alertRequest) {
179 | alertRequest = true;
180 | addFriendRequestAlert();
181 | }
182 | }
183 | }
184 | };
185 |
186 | var alertQuestStatus = function() {
187 | $scope.questAlerts = [];
188 | var today = parseInt(Date.parse(new Date()));
189 | for (var j =0; j< $rootScope.user.quests.length; j++) {
190 | (function(i) {
191 | var quest = $rootScope.user.quests[i];
192 | if (quest.status === 'active') {
193 | var completeDate = parseInt(Date.parse(quest.completionTime));
194 | if (today >= completeDate) {
195 | if (quest.numDays < 1) {
196 | TimesData.get(quest.getObj, function(result) {
197 | var total = result.total;
198 | if (total >= quest.winGoal) {
199 | $rootScope.user.quests[i].status = 'success';
200 | $rootScope.user.attributes.gold += quest.gold;
201 | } else {
202 | $rootScope.user.quests[i].status = 'fail';
203 | $rootScope.user.attributes.gold = $rootScope.user.attributes.gold - Math.floor(quest.gold/3);
204 | }
205 | User.update($rootScope.user);
206 | addQuestAlert(quest);
207 | });
208 | } else if (quest.numDays > 0 ) {
209 | DatesData.get(quest.getObj, function(result) {
210 | var total = result.total;
211 | if (total >= quest.winGoal) {
212 | $rootScope.user.quests[i].status = 'success';
213 | $rootScope.user.attributes.gold += quest.gold;
214 | } else {
215 | $rootScope.user.quests[i].status = 'fail';
216 | $rootScope.user.attributes.gold = $rootScope.user.attributes.gold - Math.floor(quest.gold/3);
217 | }
218 | User.update($rootScope.user);
219 | addQuestAlert(quest);
220 | });
221 | }
222 | }
223 | }
224 | }(j));
225 | }
226 | };
227 |
228 | var setWeapons = function() {
229 | var defaultWeapon = function(location) {
230 | $rootScope.user.equipped[location] = {};
231 | $rootScope.user.equipped[location].name = '';
232 | $rootScope.user.equipped[location].inventoryId = null;
233 | };
234 |
235 | if (!$rootScope.user.equipped) {
236 | $rootScope.user.equipped = {};
237 | defaultWeapon('weapon1');
238 | defaultWeapon('weapon2');
239 | defaultWeapon('armor');
240 | defaultWeapon('accessory1');
241 | defaultWeapon('accessory2');
242 | }
243 | }
244 |
245 | var localUserId = localStorageService.get('userId'); //'2Q2TVT'; //
246 |
247 | var checkNewData = function() {
248 | User.get({id : localUserId}, function (user) {
249 | $rootScope.user = user;
250 | setWeapons();
251 | getSettings();
252 | if (user.needsUpdate === true) {
253 | Refresh.get({id:localUserId}, function() {
254 | User.get({id:localUserId}, function(user) {
255 | console.log('needed a new update');
256 | $rootScope.user = user;
257 | calculateData($rootScope.user);
258 | alertBattleStatus();
259 | alertLevelUpStatus();
260 | alertFriendRequestStatus();
261 | alertQuestStatus();
262 | $rootScope.user.needsUpdate = false;
263 | User.update($rootScope.user);
264 | clearTimeout(loading);
265 | $ionicLoading.hide();
266 | });
267 | });
268 | } else {
269 | console.log('did not need a new update');
270 | alertBattleStatus();
271 | calculateData($rootScope.user);
272 | User.update($rootScope.user);
273 | clearTimeout(loading);
274 | $ionicLoading.hide();
275 | }
276 | });
277 | }
278 |
279 | var refresh = function() {
280 | console.log('refreshing');
281 | Refresh.get({id: localUserId}, function() { // this will tell fitbit to get new data
282 | User.get({id : localUserId}, function (user) { // this will retrieve that new data
283 | $rootScope.user = user;
284 | calculateData($rootScope.user);
285 | $scope.alertCount = 0;
286 | $scope.showAlert = false;
287 | alertBattleStatus();
288 | alertLevelUpStatus();
289 | alertFriendRequestStatus();
290 | User.update($rootScope.user);
291 | $scope.$broadcast('scroll.refreshComplete');
292 | });
293 | });
294 | getSettings();
295 | };
296 |
297 | $scope.refresh = refresh;
298 |
299 | checkNewData();
300 | document.addEventListener("resume", checkNewData, false); //whenever we resume the app, retrieve new data if there is any
301 |
302 | $scope.hasSkillPoints = function() {
303 | if ($rootScope.user && $rootScope.user.attributes.skillPts > 0) {
304 | return true;
305 | } else {
306 | return false;
307 | }
308 | };
309 |
310 | $scope.applyAttributes = function(attr) {
311 | if ($rootScope.user.attributes.skillPts > 0) {
312 | $rootScope.user.attributes[attr]++;
313 | $rootScope.user.attributes.skillPts--;
314 | if (attr === 'vitality') {
315 | // change char class from warrior to user class
316 | // $rootScope.user.attributes.hp = util.vitalityToHp($rootScope.user.attributes.vitality,'warrior');
317 | $scope.calculatedData.maxHp = util.vitalityToHp($rootScope.user.attributes.vitality,'warrior');
318 | }
319 | calculateData($rootScope.user);
320 | // update database
321 | User.update($rootScope.user);
322 | }
323 | };
324 |
325 | $scope.isEquipped = function(slot) {
326 | var user = $rootScope.user;
327 | if (user && user.equipped[slot].inventoryId !== null) {
328 | return true;
329 | } else {
330 | return false;
331 | }
332 | };
333 |
334 | $scope.unequip = function(slot){
335 | var inventory = $rootScope.user.inventory;
336 | var inventoryId = $rootScope.user.equipped[slot].inventoryId;
337 |
338 | var empty = function(location) {
339 | $rootScope.user.equipped[location].name = '';
340 | $rootScope.user.equipped[location].inventoryId = null;
341 | };
342 |
343 | if (slot === 'weapon1' || slot === 'weapon2') {
344 | if ($rootScope.user.equipped['weapon1'].inventoryId === $rootScope.user.equipped['weapon2'].inventoryId) {
345 | empty('weapon1');
346 | empty('weapon2');
347 | }
348 | }
349 |
350 | empty(slot);
351 |
352 | var storeId;
353 | for (var i=0; iLoading... '
67 | });
68 | }, 500);
69 |
70 | var stopLoading = function() {
71 | clearTimeout(loading);
72 | $ionicLoading.hide();
73 | };
74 |
75 | var checkLevel = function(user) {
76 | var newLevel = util.calcLevel(user.fitbit.experience + user.attributes.experience);
77 | user.attributes.skillPts = util.calcSkillPoints(user.attributes.skillPts,newLevel,user.attributes.level);
78 | user.attributes.level = newLevel;
79 | };
80 |
81 | // TEXT PROMPTS
82 | var healthWarning = function() {
83 | var title = 'Unfit for Battle';
84 | var body = 'You don\'t look so good. You need to recover some of your health before you can battle again.';
85 |
86 | util.showAlert($ionicPopup, title, body, 'OK', function() {
87 | $ionicListDelegate.closeOptionButtons();
88 | });
89 | };
90 |
91 | var listOfBattles = function() {
92 | // make a copy of the $scope.user.missionsVersus
93 | for (var i=0; i<$scope.user.missionsVersus.length; i++) {
94 | battles[i] = {};
95 | for (var key in $scope.user.missionsVersus[i]) {
96 | battles[i][key] = $scope.user.missionsVersus[i][key];
97 | }
98 | }
99 |
100 | if ($scope.user.friends.length === 0) {
101 | stopLoading();
102 | };
103 |
104 | var getFriendData = function(id) {
105 | User.get({id: id}, function(user){
106 | if (user._id) {
107 | $scope.friends.push(user);
108 | var friend = $scope.friends[$scope.friends.length-1];
109 | for (var j=0; j 0) {
222 | $scope.startBattle(enemy._id);
223 | } else {
224 | title = 'No Matches Found';
225 | body = 'We couldn\'t find a suitable match for you. You must be too strong.';
226 | util.showAlert($ionicPopup, title, body, 'OK', function() {});
227 | }
228 | });
229 | };
230 |
231 | title = 'Random Battle';
232 | body = 'Battle a randomly matched player. Are you up for the challenge?'
233 | util.showPrompt($ionicPopup, title, body, 'Start', 'Cancel', findRandomBattle);
234 | };
235 |
236 | $scope.startBattle = function(id) {
237 | var title, body;
238 | if ($scope.user.attributes.HP === 0) {
239 | healthWarning();
240 | } else {
241 | var battlePending = false;
242 | var battleRequest = false;
243 |
244 | for (var i=0; i<$scope.user.missionsVersus.length; i++) {
245 | var mission = $scope.user.missionsVersus[i];
246 | if (mission.enemy === id && mission.status.toLowerCase() === 'pending') {
247 | battlePending = true;
248 | } else if (mission.enemy === id && mission.status.toLowerCase() === 'request') {
249 | battleRequest = true;
250 | }
251 | }
252 | if (battleRequest) {
253 | console.log('get ready for battle!');
254 | // get the correct battle
255 | var battle;
256 | var indexOfBattle;
257 | for(var i = 0; i < $scope.user.missionsVersus.length; i++){
258 | if ($scope.user.missionsVersus[i].enemy === id) {
259 | indexOfBattle = i;
260 | battle = $scope.user.missionsVersus[i];
261 | }
262 | }
263 |
264 | // get user attributes from database
265 | User.get({id : id}, function(enemy){
266 | var enemyBattle;
267 | for(var i = 0; i < enemy.missionsVersus.length; i++){
268 | if (enemy.missionsVersus[i].enemy === $scope.user._id) {
269 | enemyBattle = enemy.missionsVersus[i];
270 | }
271 | }
272 |
273 | // use game logic to determine winner of battle
274 | // post battle results to database for both players
275 |
276 | var winner = util.battle($scope.user,enemy);
277 |
278 | var updateExp = function(player1,player2,status) {
279 | var player1Xp = player1.attributes.experience + player1.fitbit.experience;
280 | var player2Xp = player2.attributes.experience + player2.fitbit.experience;
281 | var player1AttrXp = player1.attributes.experience;
282 | var player2AttrXp = player2.attributes.experience;
283 | var diff = Math.abs(player2Xp-player1Xp);
284 | if (status === 'win') {
285 | if (player2Xp >= player1Xp) {
286 | return player1AttrXp + diff*0.2;
287 | } else {
288 | return player1AttrXp + diff*(1/Math.log(diff)*0.5);
289 | }
290 | } else if (status === 'loss') {
291 | if (player2Xp >= player1Xp) {
292 | return player1AttrXp - diff*(1/Math.log(diff)*0.5);
293 | } else {
294 | return player1AttrXp - diff*0.2;
295 | }
296 | }
297 | };
298 |
299 | var saveBattleResult = function(winnerId, loserId) {
300 | var battle = {
301 | winner : winnerId,
302 | loser : loserId,
303 | createdAt : new Date()
304 | }
305 |
306 | Battle.save(battle);
307 | }
308 |
309 | var adjustAttr = function(playerWin,playerLose) {
310 | playerLose.attributes.HP = 0;
311 | playerWin.attributes.HP = winner.hp;
312 | playerWin.attributes.gold += Math.floor(playerLose.attributes.gold * 0.1);
313 | playerLose.attributes.gold = Math.floor(playerLose.attributes.gold *= 0.9);
314 | playerWin.attributes.experience = Math.floor(updateExp(playerWin,playerLose,'win'));
315 | playerLose.attributes.experience = Math.floor(updateExp(playerLose,playerWin,'loss'));
316 | saveBattleResult(playerWin._id,playerLose._id);
317 | };
318 |
319 | var handleNegXp = function(player,level){
320 | var playerXp = player.fitbit.experience + player.attributes.experience;
321 | var levelXp = util.levelExp(level);
322 | if (playerXp < 0) {
323 | player.attributes.experience = -player.fitbit.experience;
324 | } else if (playerXp < levelXp) {
325 | player.attributes.experience += (levelXp - playerXp);
326 | }
327 | };
328 |
329 | if (winner.result === 'player 1') {
330 | adjustAttr($scope.user,enemy);
331 | handleNegXp(enemy, enemy.attributes.level);
332 | enemyBattle.status = 'loss';
333 | battle.status = 'win';
334 | checkLevel($scope.user);
335 | } else if (winner.result === 'player 2') {
336 | adjustAttr(enemy,$scope.user);
337 | handleNegXp($scope.user, $scope.user.attributes.level);
338 | enemyBattle.status = 'win';
339 | battle.status = 'loss';
340 | }
341 |
342 | util.showAlert($ionicPopup,'Challenge Accepted','Your duel to the death with '+ enemy.profile.displayName+ ' is in progress. Who will come out on top?', 'Results', function() {
343 | battleResults(battle.status, enemy.username);
344 | });
345 |
346 | $scope.user.missionsVersus.splice(indexOfBattle,1);
347 |
348 | for (var i=0; i<$scope.friends.length; i++) {
349 | var friend = $scope.friends[i];
350 | if (friend._id === id) {
351 | delete friend.battleData;
352 | }
353 | }
354 |
355 | User.update($scope.user);
356 | User.update(enemy);
357 | });
358 | } else if (battlePending) {
359 | title = 'Battle Pending';
360 | body = 'You are already have a request to do battle with this friend.';
361 |
362 | util.showAlert($ionicPopup, title, body, 'OK', function() {
363 | $ionicListDelegate.closeOptionButtons();
364 | });
365 | } else {
366 | var checkMissionExists = function(player,enemyId) {
367 | var missions = player.missionsVersus;
368 | for (var i=0; i 0) {
532 | title = 'Mission Started';
533 | body = 'You are waging war against the forces of evil...',
534 |
535 | util.showAlert($ionicPopup, title, body, 'Continue', startBossBattle);
536 | } else {
537 | healthWarning();
538 | }
539 | };
540 | })
--------------------------------------------------------------------------------