Merge branch 'develop' into separate_mocha_tests
2
common/dist/sprites/habitrpg-shared.css
vendored
1184
common/dist/sprites/spritesmith0.css
vendored
BIN
common/dist/sprites/spritesmith0.png
vendored
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 283 KiB |
1480
common/dist/sprites/spritesmith1.css
vendored
BIN
common/dist/sprites/spritesmith1.png
vendored
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 72 KiB |
1816
common/dist/sprites/spritesmith2.css
vendored
BIN
common/dist/sprites/spritesmith2.png
vendored
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
748
common/dist/sprites/spritesmith3.css
vendored
BIN
common/dist/sprites/spritesmith3.png
vendored
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 147 KiB |
532
common/dist/sprites/spritesmith4.css
vendored
BIN
common/dist/sprites/spritesmith4.png
vendored
|
Before Width: | Height: | Size: 536 KiB After Width: | Height: | Size: 536 KiB |
736
common/dist/sprites/spritesmith5.css
vendored
BIN
common/dist/sprites/spritesmith5.png
vendored
|
Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 339 KiB |
734
common/dist/sprites/spritesmith6.css
vendored
BIN
common/dist/sprites/spritesmith6.png
vendored
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
|
@ -87,5 +87,13 @@
|
||||||
"backgroundFloralMeadowText": "Flowering Meadow",
|
"backgroundFloralMeadowText": "Flowering Meadow",
|
||||||
"backgroundFloralMeadowNotes": "Picnic in a Flowering Meadow.",
|
"backgroundFloralMeadowNotes": "Picnic in a Flowering Meadow.",
|
||||||
"backgroundGumdropLandText": "Gumdrop Land",
|
"backgroundGumdropLandText": "Gumdrop Land",
|
||||||
"backgroundGumdropLandNotes": "Nibble the scenery of Gumdrop Land."
|
"backgroundGumdropLandNotes": "Nibble the scenery of Gumdrop Land.",
|
||||||
|
|
||||||
|
"backgrounds052015": "SET 12: Released May 2015",
|
||||||
|
"backgroundMarbleTempleText": "Marble Temple",
|
||||||
|
"backgroundMarbleTempleNotes": "Pose in front of a Marble Temple.",
|
||||||
|
"backgroundMountainLakeText": "Mountain Lake",
|
||||||
|
"backgroundMountainLakeNotes": "Dip your toes in a Mountain Lake.",
|
||||||
|
"backgroundPagodasText": "Pagodas",
|
||||||
|
"backgroundPagodasNotes": "Climb to the top of Pagodas."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,5 +96,9 @@
|
||||||
"abuseReported": "Thank you for reporting this violation. The moderators have been notified.",
|
"abuseReported": "Thank you for reporting this violation. The moderators have been notified.",
|
||||||
"abuseAlreadyReported": "You have already reported this message.",
|
"abuseAlreadyReported": "You have already reported this message.",
|
||||||
"needsText": "Please type a message.",
|
"needsText": "Please type a message.",
|
||||||
"needsTextPlaceholder": "Type your message here."
|
"needsTextPlaceholder": "Type your message here.",
|
||||||
|
"copyMessageAsToDo": "Copy message as To-Do",
|
||||||
|
"messageAddedAsToDo": "Message copied as To-Do.",
|
||||||
|
"messageWroteIn": "<%= user %> wrote in <%= group %>",
|
||||||
|
"msgPreviewHeading": "Message Preview"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
"invitedParty": "Invited To Party",
|
"invitedParty": "Invited To Party",
|
||||||
"invitedGuild": "Invited To Guild",
|
"invitedGuild": "Invited To Guild",
|
||||||
"inactivityEmails": "Your account is inactive",
|
"inactivityEmails": "Your account is inactive",
|
||||||
"weeklyRecaps": "Summaries of your acccount activity in the past week",
|
"weeklyRecaps": "Summaries of your account activity in the past week",
|
||||||
"questStarted": "Your Quest has Begun",
|
"questStarted": "Your Quest has Begun",
|
||||||
"invitedQuest": "Invited to Quest",
|
"invitedQuest": "Invited to Quest",
|
||||||
"kickedGroup": "Kicked from group",
|
"kickedGroup": "Kicked from group",
|
||||||
|
|
|
||||||
|
|
@ -1798,6 +1798,16 @@ api.backgrounds =
|
||||||
gumdrop_land:
|
gumdrop_land:
|
||||||
text: t('backgroundGumdropLandText')
|
text: t('backgroundGumdropLandText')
|
||||||
notes: t('backgroundGumdropLandNotes')
|
notes: t('backgroundGumdropLandNotes')
|
||||||
|
backgrounds052015:
|
||||||
|
marble_temple:
|
||||||
|
text: t('backgroundMarbleTempleText')
|
||||||
|
notes: t('backgroundMarbleTempleNotes')
|
||||||
|
mountain_lake:
|
||||||
|
text: t('backgroundMountainLakeText')
|
||||||
|
notes: t('backgroundMountainLakeNotes')
|
||||||
|
pagodas:
|
||||||
|
text: t('backgroundPagodasText')
|
||||||
|
notes: t('backgroundPagodasNotes')
|
||||||
|
|
||||||
api.subscriptionBlocks =
|
api.subscriptionBlocks =
|
||||||
basic_earned: months:1, price:5
|
basic_earned: months:1, price:5
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#jslint node: true
|
#jslint node: true
|
||||||
|
# @TODO: refactor tests
|
||||||
#global describe, before, beforeEach, it
|
#global describe, before, beforeEach, it
|
||||||
"use strict"
|
"use strict"
|
||||||
_ = require("lodash")
|
_ = require("lodash")
|
||||||
|
|
@ -72,6 +72,14 @@ describe "API", ->
|
||||||
.set("X-API-Key", apiToken)
|
.set("X-API-Key", apiToken)
|
||||||
cb null, res.body
|
cb null, res.body
|
||||||
|
|
||||||
|
registerManyUsers = (number, callback) ->
|
||||||
|
async.times number, (n, next) ->
|
||||||
|
registerNewUser (err, user) ->
|
||||||
|
next(err, user)
|
||||||
|
, false
|
||||||
|
, (err, users) ->
|
||||||
|
callback(err, users)
|
||||||
|
|
||||||
before (done) ->
|
before (done) ->
|
||||||
require "../website/src/server" # start the server
|
require "../website/src/server" # start the server
|
||||||
# then wait for it to do it's thing. TODO make a cb-compatible export of server
|
# then wait for it to do it's thing. TODO make a cb-compatible export of server
|
||||||
|
|
@ -227,7 +235,7 @@ describe "API", ->
|
||||||
).end (res) ->
|
).end (res) ->
|
||||||
expectCode res, 200
|
expectCode res, 200
|
||||||
body = res.body
|
body = res.body
|
||||||
expect(body).to.be.empty
|
expect(body).to.be.empty
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "Does not delete already deleted todo", (done) ->
|
it "Does not delete already deleted todo", (done) ->
|
||||||
|
|
@ -257,15 +265,127 @@ describe "API", ->
|
||||||
describe "Groups", ->
|
describe "Groups", ->
|
||||||
group = undefined
|
group = undefined
|
||||||
before (done) ->
|
before (done) ->
|
||||||
request.post(baseURL + "/groups").send(
|
request.post(baseURL + "/groups").send(
|
||||||
name: "TestGroup"
|
name: "TestGroup"
|
||||||
type: "party"
|
type: "party"
|
||||||
).end (res) ->
|
).end (res) ->
|
||||||
expectCode res, 200
|
expectCode res, 200
|
||||||
group = res.body
|
group = res.body
|
||||||
expect(group.members.length).to.be 1
|
expect(group.members.length).to.be 1
|
||||||
expect(group.leader).to.be user._id
|
expect(group.leader).to.be user._id
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
describe "Guilds", ->
|
||||||
|
|
||||||
|
before (done) ->
|
||||||
|
User.findByIdAndUpdate user._id,
|
||||||
|
$set:
|
||||||
|
"balance": 4
|
||||||
|
, (err, _user) ->
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe "Private Guilds", ->
|
||||||
|
guild = undefined
|
||||||
|
before (done) ->
|
||||||
|
request.post(baseURL + "/groups").send(
|
||||||
|
name: "TestPrivateGroup"
|
||||||
|
type: "guild"
|
||||||
|
privacy: "private"
|
||||||
|
).end (res) ->
|
||||||
|
expectCode res, 200
|
||||||
|
guild = res.body
|
||||||
|
expect(guild.members.length).to.be 1
|
||||||
|
expect(guild.leader).to.be user._id
|
||||||
|
#Add members to guild
|
||||||
|
async.waterfall [
|
||||||
|
(cb) ->
|
||||||
|
registerManyUsers 15, cb
|
||||||
|
|
||||||
|
(_members, cb) ->
|
||||||
|
members = _members
|
||||||
|
|
||||||
|
joinGuild = (member, callback) ->
|
||||||
|
request.post(baseURL + "/groups/" + guild._id + "/join")
|
||||||
|
.set("X-API-User", member._id)
|
||||||
|
.set("X-API-Key", member.apiToken)
|
||||||
|
.end ->
|
||||||
|
callback(null, null)
|
||||||
|
|
||||||
|
async.map members, joinGuild, (err, results) -> cb()
|
||||||
|
|
||||||
|
], done
|
||||||
|
|
||||||
|
it "includes user in private group member list when user is a member", (done) ->
|
||||||
|
|
||||||
|
request.get(baseURL + "/groups/" + guild._id)
|
||||||
|
.end (res) ->
|
||||||
|
g = res.body
|
||||||
|
userInGroup = _.find g.members, (member) -> return member._id == user._id
|
||||||
|
expect(userInGroup).to.not.be undefined
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "excludes user from viewing private group member list when user is not a member", (done) ->
|
||||||
|
|
||||||
|
request.post(baseURL + "/groups/" + guild._id + "/leave")
|
||||||
|
.end (res) ->
|
||||||
|
request.get(baseURL + "/groups/" + guild._id)
|
||||||
|
.end (res) ->
|
||||||
|
expect res, 404
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe "Public Guilds", ->
|
||||||
|
guild = undefined
|
||||||
|
before (done) ->
|
||||||
|
request.post(baseURL + "/groups").send(
|
||||||
|
name: "TestPublicGroup"
|
||||||
|
type: "guild"
|
||||||
|
privacy: "public"
|
||||||
|
).end (res) ->
|
||||||
|
expectCode res, 200
|
||||||
|
guild = res.body
|
||||||
|
expect(guild.members.length).to.be 1
|
||||||
|
expect(guild.leader).to.be user._id
|
||||||
|
#Add members to guild
|
||||||
|
async.waterfall [
|
||||||
|
(cb) ->
|
||||||
|
registerManyUsers 15, cb
|
||||||
|
|
||||||
|
(_members, cb) ->
|
||||||
|
members = _members
|
||||||
|
|
||||||
|
joinGuild = (member, callback) ->
|
||||||
|
request.post(baseURL + "/groups/" + guild._id + "/join")
|
||||||
|
.set("X-API-User", member._id)
|
||||||
|
.set("X-API-Key", member.apiToken)
|
||||||
|
.end ->
|
||||||
|
callback(null, null)
|
||||||
|
|
||||||
|
async.map members, joinGuild, (err, results) -> cb()
|
||||||
|
], done
|
||||||
|
|
||||||
|
it "includes user in public group member list when user is a member", (done) ->
|
||||||
|
|
||||||
|
request.get(baseURL + "/groups/" + guild._id)
|
||||||
|
.end (res) ->
|
||||||
|
g = res.body
|
||||||
|
expect(g.members.length).to.be 15
|
||||||
|
userInGroup = _.find g.members, (member) -> return member._id == user._id
|
||||||
|
expect(userInGroup).to.not.be undefined
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
|
it "excludes user in public group member list when user is not a member", (done) ->
|
||||||
|
#Remove user from group
|
||||||
|
request.post(baseURL + "/groups/" + guild._id + "/leave")
|
||||||
|
.end (res) ->
|
||||||
|
#Verfiy that when a user query's for a group they are in the group if they are a member
|
||||||
|
request.get(baseURL + "/groups/" + guild._id)
|
||||||
|
.end (res) ->
|
||||||
|
g = res.body
|
||||||
|
expect(g.members.length).to.be 15
|
||||||
|
userInGroup = _.find g.members, (member) -> return member._id == user._id
|
||||||
|
expect(userInGroup).to.be undefined
|
||||||
|
done()
|
||||||
|
|
||||||
describe "Party", ->
|
describe "Party", ->
|
||||||
it "can be found by querying for party", (done) ->
|
it "can be found by querying for party", (done) ->
|
||||||
|
|
@ -273,7 +393,6 @@ describe "API", ->
|
||||||
type: "party"
|
type: "party"
|
||||||
).end (res) ->
|
).end (res) ->
|
||||||
expectCode res, 200
|
expectCode res, 200
|
||||||
|
|
||||||
party = res.body[0]
|
party = res.body[0]
|
||||||
expect(party._id).to.be group._id
|
expect(party._id).to.be group._id
|
||||||
expect(party.leader).to.be user._id
|
expect(party.leader).to.be user._id
|
||||||
|
|
@ -282,6 +401,133 @@ describe "API", ->
|
||||||
expect(party.memberCount).to.be group.memberCount
|
expect(party.memberCount).to.be group.memberCount
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
it "includes user in a party member list when user is a member", (done) ->
|
||||||
|
party = []
|
||||||
|
|
||||||
|
#Invite some members
|
||||||
|
async.waterfall [
|
||||||
|
|
||||||
|
# Register new users
|
||||||
|
(cb) ->
|
||||||
|
registerManyUsers 15, cb
|
||||||
|
|
||||||
|
# Send them invitations
|
||||||
|
(_party, cb) ->
|
||||||
|
party = _party
|
||||||
|
|
||||||
|
joinParty = (member, callback) ->
|
||||||
|
request.post(baseURL + "/groups/" + group._id + "/join")
|
||||||
|
.set("X-API-User", member._id)
|
||||||
|
.set("X-API-Key", member.apiToken)
|
||||||
|
.end ->
|
||||||
|
callback(null, null)
|
||||||
|
|
||||||
|
async.map party, joinParty, (err, results) -> cb()
|
||||||
|
|
||||||
|
# Accept / Reject
|
||||||
|
(cb) ->
|
||||||
|
# series since they'll be modifying the same group record
|
||||||
|
series = _.reduce(party, (m, v, i) ->
|
||||||
|
m.push (cb2) ->
|
||||||
|
request.post(baseURL + "/groups/" + group._id + "/join")
|
||||||
|
.set("X-API-User", party[i]._id)
|
||||||
|
.set("X-API-Key", party[i].apiToken)
|
||||||
|
.end ->
|
||||||
|
cb2()
|
||||||
|
m
|
||||||
|
, [])
|
||||||
|
async.series series, cb
|
||||||
|
|
||||||
|
# Make sure the invites stuck
|
||||||
|
(result, cb) ->
|
||||||
|
request.get(baseURL + "/groups/" + group._id)
|
||||||
|
.send()
|
||||||
|
.end (res) ->
|
||||||
|
g = res.body
|
||||||
|
userInGroup = _.find g.members, (member) -> return member._id == user._id
|
||||||
|
expect(userInGroup).to.not.be undefined
|
||||||
|
cb()
|
||||||
|
|
||||||
|
# Remove all previous members
|
||||||
|
(cb) ->
|
||||||
|
|
||||||
|
joinParty = (member, callback) ->
|
||||||
|
request.post(baseURL + "/groups/" + group._id + "/leave")
|
||||||
|
.set("X-API-User", member._id)
|
||||||
|
.set("X-API-Key", member.apiToken)
|
||||||
|
.end ->
|
||||||
|
callback(null, null)
|
||||||
|
|
||||||
|
async.map party, joinParty, (err, results) -> cb()
|
||||||
|
|
||||||
|
], done
|
||||||
|
|
||||||
|
it "excludes user in a party member list when user is not a member", (done) ->
|
||||||
|
party = []
|
||||||
|
|
||||||
|
#Invite some members
|
||||||
|
async.waterfall [
|
||||||
|
|
||||||
|
# Register new users
|
||||||
|
(cb) ->
|
||||||
|
registerManyUsers 15, cb
|
||||||
|
|
||||||
|
# Send them invitations
|
||||||
|
(_party, cb) ->
|
||||||
|
party = _party
|
||||||
|
|
||||||
|
joinParty = (member, callback) ->
|
||||||
|
request.post(baseURL + "/groups/" + group._id + "/join")
|
||||||
|
.set("X-API-User", member._id)
|
||||||
|
.set("X-API-Key", member.apiToken)
|
||||||
|
.end ->
|
||||||
|
callback(null, null)
|
||||||
|
|
||||||
|
async.map party, joinParty, (err, results) -> cb()
|
||||||
|
|
||||||
|
# Accept / Reject
|
||||||
|
(cb) ->
|
||||||
|
# series since they'll be modifying the same group record
|
||||||
|
series = _.reduce(party, (m, v, i) ->
|
||||||
|
m.push (cb2) ->
|
||||||
|
request.post(baseURL + "/groups/" + group._id + "/join")
|
||||||
|
.set("X-API-User", party[i]._id)
|
||||||
|
.set("X-API-Key", party[i].apiToken)
|
||||||
|
.end ->
|
||||||
|
cb2()
|
||||||
|
m
|
||||||
|
, [])
|
||||||
|
async.series series, cb
|
||||||
|
|
||||||
|
(result, cb) ->
|
||||||
|
#Remove a user from group
|
||||||
|
request.post(baseURL + "/groups/" + group._id + "/leave")
|
||||||
|
.set("X-API-User", party[0]._id)
|
||||||
|
.set("X-API-Key", party[0].apiToken)
|
||||||
|
.send()
|
||||||
|
.end (res) ->
|
||||||
|
request.get(baseURL + "/groups/" + group._id)
|
||||||
|
.set("X-API-User", party[0]._id)
|
||||||
|
.set("X-API-Key", party[0].apiToken)
|
||||||
|
.send()
|
||||||
|
.end (res) ->
|
||||||
|
expect res, 404
|
||||||
|
cb()
|
||||||
|
|
||||||
|
# Remove all previous members
|
||||||
|
(cb) ->
|
||||||
|
|
||||||
|
joinParty = (member, callback) ->
|
||||||
|
request.post(baseURL + "/groups/" + group._id + "/leave")
|
||||||
|
.set("X-API-User", member._id)
|
||||||
|
.set("X-API-Key", member.apiToken)
|
||||||
|
.end ->
|
||||||
|
callback(null, null)
|
||||||
|
|
||||||
|
async.map party, joinParty, (err, results) -> cb()
|
||||||
|
|
||||||
|
], done
|
||||||
|
|
||||||
describe "Chat", ->
|
describe "Chat", ->
|
||||||
chat = undefined
|
chat = undefined
|
||||||
it "Posts a message to party chat", (done) ->
|
it "Posts a message to party chat", (done) ->
|
||||||
|
|
@ -527,14 +773,7 @@ describe "API", ->
|
||||||
|
|
||||||
# Register new users
|
# Register new users
|
||||||
(cb) ->
|
(cb) ->
|
||||||
async.parallel [
|
registerManyUsers 3, cb
|
||||||
(cb2) ->
|
|
||||||
registerNewUser cb2, false
|
|
||||||
(cb2) ->
|
|
||||||
registerNewUser cb2, false
|
|
||||||
(cb2) ->
|
|
||||||
registerNewUser cb2, false
|
|
||||||
], cb
|
|
||||||
|
|
||||||
# Send them invitations
|
# Send them invitations
|
||||||
(_party, cb) ->
|
(_party, cb) ->
|
||||||
|
|
@ -612,7 +851,7 @@ describe "API", ->
|
||||||
party[0] = res.body
|
party[0] = res.body
|
||||||
request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end (res) ->
|
request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end (res) ->
|
||||||
|
|
||||||
#expect(res.body.stats.mp).to.be.below(mp);
|
#expect(res.body.stats.mp).to.be.below(mp)
|
||||||
request.get(baseURL + "/members/" + party[0]._id).end (res) ->
|
request.get(baseURL + "/members/" + party[0]._id).end (res) ->
|
||||||
member = res.body
|
member = res.body
|
||||||
expect(member.achievements.snowball).to.be 1
|
expect(member.achievements.snowball).to.be 1
|
||||||
|
|
@ -747,8 +986,8 @@ describe "API", ->
|
||||||
cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp
|
cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp
|
||||||
|
|
||||||
#//FIXME check that user got exp, but user is leveling up making the exp check difficult
|
#//FIXME check that user got exp, but user is leveling up making the exp check difficult
|
||||||
# expect(user.stats.exp).to.be.above(cummExp);
|
# expect(user.stats.exp).to.be.above(cummExp)
|
||||||
# expect(user.stats.gp).to.be.above(cummGp);
|
# expect(user.stats.gp).to.be.above(cummGp)
|
||||||
async.parallel [
|
async.parallel [
|
||||||
|
|
||||||
# Tavern Boss
|
# Tavern Boss
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,76 @@ describe('Groups Controller', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Chat Controller", function() {
|
||||||
|
var scope, ctrl, user, $rootScope, $controller;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module(function($provide) {
|
||||||
|
$provide.value('User', {});
|
||||||
|
});
|
||||||
|
|
||||||
|
inject(function(_$rootScope_, _$controller_){
|
||||||
|
user = specHelper.newUser();
|
||||||
|
user._id = "unique-user-id";
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
|
||||||
|
scope = _$rootScope_.$new();
|
||||||
|
|
||||||
|
$controller = _$controller_;
|
||||||
|
|
||||||
|
// Load RootCtrl to ensure shared behaviors are loaded
|
||||||
|
$controller('RootCtrl', {$scope: scope, User: {user: user}});
|
||||||
|
|
||||||
|
ctrl = $controller('ChatCtrl', {$scope: scope});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('copyToDo', function() {
|
||||||
|
it('when copying a user message it opens modal with information from message', function() {
|
||||||
|
scope.group = {
|
||||||
|
name: "Princess Bride"
|
||||||
|
};
|
||||||
|
|
||||||
|
var modalSpy = sinon.spy($rootScope, "openModal");
|
||||||
|
var message = {
|
||||||
|
uuid: 'the-dread-pirate-roberts',
|
||||||
|
user: 'Wesley',
|
||||||
|
text: 'As you wish'
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.copyToDo(message);
|
||||||
|
|
||||||
|
modalSpy.should.have.been.calledOnce;
|
||||||
|
|
||||||
|
modalSpy.should.have.been.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
|
||||||
|
return callArgToMatch.controller == 'CopyMessageModalCtrl'
|
||||||
|
&& callArgToMatch.scope.text == message.text
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when copying a system message it opens modal with information from message', function() {
|
||||||
|
scope.group = {
|
||||||
|
name: "Princess Bride"
|
||||||
|
};
|
||||||
|
|
||||||
|
var modalSpy = sinon.spy($rootScope, "openModal");
|
||||||
|
var message = {
|
||||||
|
uuid: 'system',
|
||||||
|
text: 'Wesley attacked the ROUS in the Fire Swamp'
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.copyToDo(message);
|
||||||
|
|
||||||
|
modalSpy.should.have.been.calledOnce;
|
||||||
|
|
||||||
|
modalSpy.should.have.been.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
|
||||||
|
return callArgToMatch.controller == 'CopyMessageModalCtrl'
|
||||||
|
&& callArgToMatch.scope.text == message.text
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Autocomplete controller", function() {
|
describe("Autocomplete controller", function() {
|
||||||
var scope, ctrl, user, $rootScope, $controller;
|
var scope, ctrl, user, $rootScope, $controller;
|
||||||
|
|
||||||
|
|
@ -172,3 +242,57 @@ describe("Autocomplete controller", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("CopyMessageModal controller", function() {
|
||||||
|
var scope, ctrl, user, Notification, $rootScope, $controller;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module(function($provide) {
|
||||||
|
$provide.value('User', {});
|
||||||
|
});
|
||||||
|
|
||||||
|
inject(function($rootScope, _$controller_, _Notification_){
|
||||||
|
user = specHelper.newUser();
|
||||||
|
user._id = "unique-user-id";
|
||||||
|
user.ops = {
|
||||||
|
addTask: sinon.spy()
|
||||||
|
};
|
||||||
|
|
||||||
|
scope = $rootScope.$new();
|
||||||
|
scope.$close = sinon.spy();
|
||||||
|
|
||||||
|
$controller = _$controller_;
|
||||||
|
|
||||||
|
// Load RootCtrl to ensure shared behaviors are loaded
|
||||||
|
$controller('RootCtrl', {$scope: scope, User: {user: user}});
|
||||||
|
|
||||||
|
ctrl = $controller('CopyMessageModalCtrl', {$scope: scope, User: {user: user}});
|
||||||
|
|
||||||
|
Notification = _Notification_;
|
||||||
|
Notification.text = sinon.spy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveTodo", function() {
|
||||||
|
it('saves todo', function() {
|
||||||
|
|
||||||
|
scope.text = "A Tavern msg";
|
||||||
|
scope.notes = "Some notes";
|
||||||
|
var payload = {
|
||||||
|
body: {
|
||||||
|
text: scope.text,
|
||||||
|
type: 'todo',
|
||||||
|
notes: scope.notes
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.saveTodo();
|
||||||
|
|
||||||
|
user.ops.addTask.should.have.been.calledOnce;
|
||||||
|
user.ops.addTask.should.have.been.calledWith(payload);
|
||||||
|
Notification.text.should.have.been.calledOnce;
|
||||||
|
Notification.text.should.have.been.calledWith(window.env.t('messageAddedAsToDo'));
|
||||||
|
scope.$close.should.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -187,3 +187,6 @@ a.label
|
||||||
color: #fff !important
|
color: #fff !important
|
||||||
.line-through
|
.line-through
|
||||||
text-decoration line-through
|
text-decoration line-through
|
||||||
|
|
||||||
|
.markdown-preview markdown code
|
||||||
|
white-space inherit
|
||||||
|
|
@ -142,6 +142,14 @@ for $stage in $stages
|
||||||
padding: 0
|
padding: 0
|
||||||
font-weight: 300
|
font-weight: 300
|
||||||
|
|
||||||
|
.task-column.preview
|
||||||
|
padding: 0
|
||||||
|
background: transparent
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
.task:hover
|
||||||
|
cursor: auto
|
||||||
|
|
||||||
// 50% width columns with scrollbars for tablets
|
// 50% width columns with scrollbars for tablets
|
||||||
@media (min-width: 768px) and (max-width: 970px)
|
@media (min-width: 768px) and (max-width: 970px)
|
||||||
.task-column
|
.task-column
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,13 @@ angular.module('habitrpg')
|
||||||
|
|
||||||
var runAuth = function(id, token) {
|
var runAuth = function(id, token) {
|
||||||
User.authenticate(id, token, function(err) {
|
User.authenticate(id, token, function(err) {
|
||||||
|
if(!err) $scope.registrationInProgress = false;
|
||||||
$window.location.href = ('/' + window.location.hash);
|
$window.location.href = ('/' + window.location.hash);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function errorAlert(data, status, headers, config) {
|
function errorAlert(data, status, headers, config) {
|
||||||
|
$scope.registrationInProgress = false;
|
||||||
if (status === 0) {
|
if (status === 0) {
|
||||||
$window.alert(window.env.t('noReachServer'));
|
$window.alert(window.env.t('noReachServer'));
|
||||||
} else if (!!data && !!data.err) {
|
} else if (!!data && !!data.err) {
|
||||||
|
|
@ -29,6 +31,8 @@ angular.module('habitrpg')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.registrationInProgress = false;
|
||||||
|
|
||||||
$scope.register = function() {
|
$scope.register = function() {
|
||||||
/*TODO highlight invalid inputs
|
/*TODO highlight invalid inputs
|
||||||
we have this as a workaround for https://github.com/HabitRPG/habitrpg-mobile/issues/64
|
we have this as a workaround for https://github.com/HabitRPG/habitrpg-mobile/issues/64
|
||||||
|
|
@ -36,6 +40,8 @@ angular.module('habitrpg')
|
||||||
var scope = angular.element(document.getElementById('registrationForm')).scope();
|
var scope = angular.element(document.getElementById('registrationForm')).scope();
|
||||||
if (scope.registrationForm.$invalid) return;
|
if (scope.registrationForm.$invalid) return;
|
||||||
|
|
||||||
|
$scope.registrationInProgress = true;
|
||||||
|
|
||||||
var url = ApiUrl.get() + "/api/v2/register";
|
var url = ApiUrl.get() + "/api/v2/register";
|
||||||
if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code;
|
if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code;
|
||||||
$http.post(url, scope.registerVals).success(function(data, status, headers, config) {
|
$http.post(url, scope.registerVals).success(function(data, status, headers, config) {
|
||||||
|
|
|
||||||
|
|
@ -362,7 +362,25 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
$scope.copyToDo = function(message) {
|
||||||
|
var taskNotes = env.t("messageWroteIn", {
|
||||||
|
user: message.uuid == 'system'
|
||||||
|
? 'system'
|
||||||
|
: '[' + message.user + '](' + env.BASE_URL + '/static/front/#?memberId=' + message.uuid + ')',
|
||||||
|
group: '[' + $scope.group.name + '](' + window.location.href + ')'
|
||||||
|
});
|
||||||
|
|
||||||
|
var newScope = $scope.$new();
|
||||||
|
newScope.text = message.text;
|
||||||
|
newScope.notes = taskNotes;
|
||||||
|
|
||||||
|
$rootScope.openModal('copyChatToDo',{
|
||||||
|
controller:'CopyMessageModalCtrl',
|
||||||
|
scope: newScope
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.sync = function(group){
|
$scope.sync = function(group){
|
||||||
group.$get();
|
group.$get();
|
||||||
|
|
@ -567,3 +585,20 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification',
|
||||||
|
function($scope, User, Notification){
|
||||||
|
$scope.saveTodo = function() {
|
||||||
|
var newTask = {
|
||||||
|
text: $scope.text,
|
||||||
|
type: 'todo',
|
||||||
|
notes: $scope.notes
|
||||||
|
};
|
||||||
|
|
||||||
|
User.user.ops.addTask({body:newTask});
|
||||||
|
Notification.text(window.env.t('messageAddedAsToDo'));
|
||||||
|
|
||||||
|
$scope.$close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ function($rootScope, User, $timeout, $state) {
|
||||||
});
|
});
|
||||||
|
|
||||||
var goto = function(chapter, page, force) {
|
var goto = function(chapter, page, force) {
|
||||||
|
return; // TODO temporarily remove old tutorial system while experimenting with leslie's new gettup
|
||||||
var curr = User.user.flags.tour[chapter];
|
var curr = User.user.flags.tour[chapter];
|
||||||
if (page != curr+1 && !force) return;
|
if (page != curr+1 && !force) return;
|
||||||
var updates = {};updates['flags.tour.'+chapter] = page;
|
var updates = {};updates['flags.tour.'+chapter] = page;
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,23 @@ api.get = function(req, res, next) {
|
||||||
q.exec(function(err, group){
|
q.exec(function(err, group){
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (!group && gid!=='party') return res.json(404,{err: "Group not found or you don't have access."});
|
if (!group && gid!=='party') return res.json(404,{err: "Group not found or you don't have access."});
|
||||||
res.json(group);
|
//Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group
|
||||||
|
var userInGroup = _.find(group.members, function(member){ return member._id == user._id; });
|
||||||
|
//If the group is private or the group is a party, then the user must be a member of the group based on access restrictions above
|
||||||
|
if (group.privacy === 'private' || gid === 'party') {
|
||||||
|
//If the user is not in the group query, add them
|
||||||
|
if (!userInGroup) { group.members.push(user); }
|
||||||
|
res.json(group);
|
||||||
|
} else if ( group.privacy === "public" ) { //The group is public, we must do an extra check to see if the user is already in the group query
|
||||||
|
//We must see how to check if a user is a member of a public group, so we requery
|
||||||
|
var q2 = Group.findOne({ _id: group._id, privacy:'public', members: {$in:[user._id]} });
|
||||||
|
q2.exec(function(err, group2){
|
||||||
|
if (err) return next(err);
|
||||||
|
if (group2 && !userInGroup) { group.members.push(user); }
|
||||||
|
res.json(group);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
gid = null;
|
gid = null;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -398,7 +414,8 @@ api.likeChatMessage = function(req, res, next) {
|
||||||
|
|
||||||
api.join = function(req, res, next) {
|
api.join = function(req, res, next) {
|
||||||
var user = res.locals.user,
|
var user = res.locals.user,
|
||||||
group = res.locals.group;
|
group = res.locals.group,
|
||||||
|
isUserInvited = false;
|
||||||
|
|
||||||
if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) {
|
if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) {
|
||||||
User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter
|
User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter
|
||||||
|
|
@ -409,16 +426,25 @@ api.join = function(req, res, next) {
|
||||||
group.quest.members[user._id] = undefined;
|
group.quest.members[user._id] = undefined;
|
||||||
group.markModified('quest.members');
|
group.markModified('quest.members');
|
||||||
}
|
}
|
||||||
}
|
isUserInvited = true;
|
||||||
else if (group.type == 'guild' && user.invitations && user.invitations.guilds) {
|
} else if (group.type == 'guild' && user.invitations && user.invitations.guilds) {
|
||||||
var i = _.findIndex(user.invitations.guilds, {id:group._id});
|
var i = _.findIndex(user.invitations.guilds, {id:group._id});
|
||||||
if (~i) user.invitations.guilds.splice(i,1);
|
if (~i){
|
||||||
user.save();
|
isUserInvited = true;
|
||||||
|
user.invitations.guilds.splice(i,1);
|
||||||
|
user.save();
|
||||||
|
}else{
|
||||||
|
isUserInvited = group.privacy === 'private' ? false : true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!isUserInvited) return res.json(401, {err: "Can't join a group you're not invited to."});
|
||||||
|
|
||||||
if (!_.contains(group.members, user._id)){
|
if (!_.contains(group.members, user._id)){
|
||||||
group.members.push(user._id);
|
group.members.push(user._id);
|
||||||
group.invites.splice(_.indexOf(group.invites, user._id), 1);
|
if (group.invites.length > 0) {
|
||||||
|
group.invites.splice(_.indexOf(group.invites, user._id), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async.series([
|
async.series([
|
||||||
|
|
@ -430,7 +456,6 @@ api.join = function(req, res, next) {
|
||||||
}
|
}
|
||||||
], function(err, results){
|
], function(err, results){
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
// Return the group? Or not?
|
// Return the group? Or not?
|
||||||
res.json(results[1]);
|
res.json(results[1]);
|
||||||
group = null;
|
group = null;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ mixin chatMessages(inbox)
|
||||||
|
|
|
|
||||||
a(ng-click="flagChatMessage(group._id, message)", ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")')
|
a(ng-click="flagChatMessage(group._id, message)", ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")')
|
||||||
span.glyphicon.glyphicon-flag(tooltip="{{message.flags[user._id] ? env.t('abuseAlreadyReported') : env.t('abuseFlag')}}" ng-class='message.flags[user._id] ? "text-danger" : ""')
|
span.glyphicon.glyphicon-flag(tooltip="{{message.flags[user._id] ? env.t('abuseAlreadyReported') : env.t('abuseFlag')}}" ng-class='message.flags[user._id] ? "text-danger" : ""')
|
||||||
|
|
|
||||||
|
a(ng-click="copyToDo(message)")
|
||||||
|
span.glyphicon.glyphicon-share(tooltip=env.t('copyMessageAsToDo'))
|
||||||
span.float-label(ng-class='::contribText(message.contributor, message.backer).length > 30 ? "long-title" : ""')
|
span.float-label(ng-class='::contribText(message.contributor, message.backer).length > 30 ? "long-title" : ""')
|
||||||
a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)')
|
a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)')
|
||||||
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')
|
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')
|
||||||
|
|
|
||||||
|
|
@ -113,3 +113,33 @@ script(type='text/ng-template', id='partials/options.social.html')
|
||||||
.tab-content
|
.tab-content
|
||||||
.tab-pane.active
|
.tab-pane.active
|
||||||
div(ui-view)
|
div(ui-view)
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='modals/copyChatToDo.html')
|
||||||
|
.modal-header
|
||||||
|
h4=env.t('copyMessageAsToDo')
|
||||||
|
.modal-body
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='text',ng-model='text', ng-model-options="{debounce: 1000}")
|
||||||
|
.form-group
|
||||||
|
textarea.form-control(rows='5',ng-model='notes', ng-model-options="{debounce: 1000}", focus-me)
|
||||||
|
|
||||||
|
hr
|
||||||
|
|
||||||
|
div.task-column.preview
|
||||||
|
div(ng-init='popoverOpen = false', class='task todo uncompleted color-neutral', popover-trigger='mouseenter', data-popover-html="{{popoverOpen ? '' : notes | markdown}}", popover-placement="top")
|
||||||
|
// right-hand side control buttons
|
||||||
|
.task-meta-controls
|
||||||
|
// Icons only available if you own the tasks (aka, hidden from challenge stats)
|
||||||
|
span(ng-if='!obj._locked')
|
||||||
|
// notes
|
||||||
|
span.task-notes(ng-show='notes', ng-click='popoverOpen = !popoverOpen', popover-trigger='click', data-popover-html="{{notes | markdown}}", popover-placement="top")
|
||||||
|
span.glyphicon.glyphicon-comment
|
||||||
|
|
|
||||||
|
|
||||||
|
// main content
|
||||||
|
div.task-text
|
||||||
|
markdown(text='text',target='_blank')
|
||||||
|
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-default(ng-click='$close()')=env.t('close')
|
||||||
|
button.btn.btn-primary(ng-click='saveTodo()')=env.t('submit')
|
||||||
|
|
@ -11,3 +11,9 @@ mixin aLink(url, label)
|
||||||
a(href="", ng-click="externalLink('#{url}')")= label
|
a(href="", ng-click="externalLink('#{url}')")= label
|
||||||
else
|
else
|
||||||
a(href='#{url}', target='_blank')= label
|
a(href='#{url}', target='_blank')= label
|
||||||
|
|
||||||
|
mixin previewMarkdown(text)
|
||||||
|
.panel.panel-warning
|
||||||
|
.panel-heading=env.t('msgPreviewHeading')
|
||||||
|
.panel-body.markdown-preview
|
||||||
|
markdown(text='#{text}')
|
||||||
|
|
@ -1,36 +1,44 @@
|
||||||
h5 5/01/2015 - Skills Rebalancing
|
h5 5/06/2015 - MAY BACKGROUNDS REVEALED
|
||||||
hr
|
hr
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
p After over a year's worth of complaints on Trello and countless bug reports on Github, and after almost five months of dedicated debating, coding, and testing, we have finally rolled out an overhaul of the skills and mana system. Thank you so much to everyone who contributed opinions, time, and effort! Here's what happened:
|
.background_mountain_lake.pull-right
|
||||||
tr
|
h5 May Backgrounds Revealed
|
||||||
td
|
p There are three new avatar backgrounds in the <a href='https://habitrpg.com/#/options/profile/backgrounds' target='_blank'>Background Shop</a>! Now your avatar can climb to the top of Pagodas, pose in front of a Marble Temple, or dip toes in a Mountain Lake!
|
||||||
h5 HABITS DAMAGE BOSSES
|
p.small.muted by (in order) Teto is Great, Painter de Cluster, and Hazel40
|
||||||
p Now it's even easier to damage a Boss without skills! It was definitely high time that HabitRPG counted Habits towards battles. We were not living up to our name.
|
|
||||||
tr
|
|
||||||
td
|
|
||||||
h5 MANA DIRECTLY LINKED TO PROGRESS
|
|
||||||
p Mana was pretty broken: you gained it overnight no matter what you'd done the day before, and it was only linked to To-Dos. This meant that using skills was pretty detached from how well you were doing in your real life, which defeated the purpose of the game!
|
|
||||||
p Now you can gain Mana from Dailies and positive Habits as well as To-Dos! You still gain Mana overnight, but how much you gain depends on how many of your Dailies you did.
|
|
||||||
p You can also lose Mana by hitting negative Habits. Yikes! Better not indulge...
|
|
||||||
tr
|
|
||||||
td
|
|
||||||
h5 SKILLS STRENGTHENED AND WEAKENED
|
|
||||||
p Some notoriously overpowered skills have been weakened, like Backstab (which often granted over 1000GP per cast), Burst of Flames (which allowed you to 1-hit KO most bosses regardless of how much or how little you'd done that day), and Valorous Presence (which caused the notorious "I checked off one Daily and gained 997 levels" bug reports).
|
|
||||||
p Some painfully UNDERpowered skills have been strengthened, such as Tools of the Trade and Searing Brightness. We think you'll like these much better now!
|
|
||||||
p For a full description of what changed, <a href='http://habitrpg.wikia.com/wiki/User_blog:LadyAlys/Skills_Rebalancing_and_Other_Changes' target='_blank'>check out this blog post</a>.
|
|
||||||
tr
|
|
||||||
td
|
|
||||||
h5 GIVING FEEDBACK
|
|
||||||
p We understand that these changes may be disruptive to some of you. We're sorry! We hope that with time, you will find them to be a drastic improvement. We will be waiting two weeks (so that everyone can get used to the new system), and then on May 15th will open up a new Trello card for comments and feedback.
|
|
||||||
p For those of you who want to reallocate stats now that you have the new system, please post your User ID in this <a href='https://github.com/HabitRPG/habitrpg/issues/5082' target='_blank'>Github ticket</a>. (Your UUID is found under Settings > API; do NOT post your API Token.) That ticket is also being used to debate a possibility to make it so that reallocating stats does not cost gems. If that's a feature you would like (or would hate), definitely drop by that ticket and let us know!
|
|
||||||
p.small.muted by Alys, Verabird, brandonjreid, ShilohT, betaveros, SabreCat, chimericdream, and everyone who commented with feedback on Trello and Github. You guys are great!
|
|
||||||
|
|
||||||
hr
|
hr
|
||||||
a(href='/static/old-news', target='_blank') Read older news
|
a(href='/static/old-news', target='_blank') Read older news
|
||||||
|
|
||||||
mixin oldNews
|
mixin oldNews
|
||||||
|
|
||||||
|
h5 5/01/2015 - Skills Rebalancing
|
||||||
|
hr
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
p After over a year's worth of complaints on Trello and countless bug reports on Github, and after almost five months of dedicated debating, coding, and testing, we have finally rolled out an overhaul of the skills and mana system. Thank you so much to everyone who contributed opinions, time, and effort! Here's what happened:
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 HABITS DAMAGE BOSSES
|
||||||
|
p Now it's even easier to damage a Boss without skills! It was definitely high time that HabitRPG counted Habits towards battles. We were not living up to our name.
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 MANA DIRECTLY LINKED TO PROGRESS
|
||||||
|
p Mana was pretty broken: you gained it overnight no matter what you'd done the day before, and it was only linked to To-Dos. This meant that using skills was pretty detached from how well you were doing in your real life, which defeated the purpose of the game!
|
||||||
|
p Now you can gain Mana from Dailies and positive Habits as well as To-Dos! You still gain Mana overnight, but how much you gain depends on how many of your Dailies you did.
|
||||||
|
p You can also lose Mana by hitting negative Habits. Yikes! Better not indulge...
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 SKILLS STRENGTHENED AND WEAKENED
|
||||||
|
p Some notoriously overpowered skills have been weakened, like Backstab (which often granted over 1000GP per cast), Burst of Flames (which allowed you to 1-hit KO most bosses regardless of how much or how little you'd done that day), and Valorous Presence (which caused the notorious "I checked off one Daily and gained 997 levels" bug reports).
|
||||||
|
p Some painfully UNDERpowered skills have been strengthened, such as Tools of the Trade and Searing Brightness. We think you'll like these much better now!
|
||||||
|
p For a full description of what changed, <a href='http://habitrpg.wikia.com/wiki/User_blog:LadyAlys/Skills_Rebalancing_and_Other_Changes' target='_blank'>check out this blog post</a>.
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 GIVING FEEDBACK
|
||||||
|
p We understand that these changes may be disruptive to some of you. We're sorry! We hope that with time, you will find them to be a drastic improvement. We will be waiting two weeks (so that everyone can get used to the new system), and then on May 15th will open up a new Trello card for comments and feedback.
|
||||||
|
p For those of you who want to reallocate stats now that you have the new system, please post your User ID in this <a href='https://github.com/HabitRPG/habitrpg/issues/5082' target='_blank'>Github ticket</a>. (Your UUID is found under Settings > API; do NOT post your API Token.) That ticket is also being used to debate a possibility to make it so that reallocating stats does not cost gems. If that's a feature you would like (or would hate), definitely drop by that ticket and let us know!
|
||||||
|
p.small.muted by Alys, Verabird, brandonjreid, ShilohT, betaveros, SabreCat, chimericdream, and everyone who commented with feedback on Trello and Github. You all are great!
|
||||||
h5 4/30/2015 - LAST CHANCE FOR BUSY BEE AND SPRING FLING ITEMS, AND SOON-TO-APPEAR NEW EQUIPMENT!
|
h5 4/30/2015 - LAST CHANCE FOR BUSY BEE AND SPRING FLING ITEMS, AND SOON-TO-APPEAR NEW EQUIPMENT!
|
||||||
hr
|
hr
|
||||||
tr
|
tr
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,52 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
||||||
.task-column(class='{{list.type}}s')
|
.task-column(class='{{list.type}}s')
|
||||||
|
|
||||||
// Todos export/graph options
|
// Todos export/graph options
|
||||||
span.option-box.pull-right(ng-if='::main && list.type=="todo"')
|
span.option-box.pull-right(ng-if='::main')
|
||||||
a.option-action(ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'))
|
a.option-action(ng-if='list.type=="todo"', ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'), style='margin-right:5px;')
|
||||||
span.glyphicon.glyphicon-signal
|
span.glyphicon.glyphicon-signal
|
||||||
//a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
|
//a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
|
||||||
a.option-action(ng-click='notPorted()', tooltip='iCal', ng-show='false')
|
//-a.option-action(ng-if='list.type=="todo"', ng-click='notPorted()', tooltip='iCal', ng-show='false')
|
||||||
span.glyphicon.glyphicon-calendar
|
span.glyphicon.glyphicon-calendar
|
||||||
// <a href="https://www.google.com/calendar/render?cid={{encodeiCalLink(_user.id, _user.apiToken)}}" rel=tooltip title="Google Calendar"><i class=icon-calendar></i></a>
|
// <a href="https://www.google.com/calendar/render?cid={{encodeiCalLink(_user.id, _user.apiToken)}}" rel=tooltip title="Google Calendar"><i class=icon-calendar></i></a>
|
||||||
|
a.option-action(ng-click='list.help=!list.help', tooltip='Click for help')
|
||||||
|
span.glyphicon.glyphicon-question-sign
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
h2.task-column_title {{list.header}}
|
h2.task-column_title
|
||||||
|
| {{list.header}}
|
||||||
|
|
||||||
|
div(ng-if='list.help', ng-switch='::list.type')
|
||||||
|
div(ng-switch-when='habit')
|
||||||
|
ul
|
||||||
|
li Good Habits are things that you do often. They award gold and experience every time you click the <code>+</code>.
|
||||||
|
li Bad Habits are things you want to avoid doing. They remove health every time you click the <code>-</code>.
|
||||||
|
li For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_Habits#Business' target='_blank'>sample Habits</a>
|
||||||
|
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||||
|
div(ng-switch-when='daily')
|
||||||
|
ul
|
||||||
|
li Dailies repeat <strong>every day</strong> that they are active. Click the pencil icon to change the days a Daily is active.
|
||||||
|
li If you don’t complete active Dailies, they will hurt your avatar when your day rolls over.
|
||||||
|
li Dailies turn <strong>redder</strong> when you miss them, and <strong>bluer</strong> when you complete them. The redder the Daily, the more it will reward you... and hurt you.
|
||||||
|
li To change when your day rolls over, go to Settings > Site > Custom Day Start.
|
||||||
|
li For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_Dailies#Business' target='_blank'>sample Dailies</a>!
|
||||||
|
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||||
|
|
||||||
|
div(ng-switch-when='todo')
|
||||||
|
ul
|
||||||
|
li To-Dos start yellow, and get redder (more valuable) the longer it takes to complete them.
|
||||||
|
li To-Dos never hurt you! They only award gold and experience.
|
||||||
|
li Breaking a To-Do down into a small checklist will make it less scary, and will increase your points!
|
||||||
|
li For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_To-Dos#Finances' target='_blank'>sample To-Dos</a>!
|
||||||
|
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||||
|
|
||||||
|
div(ng-switch-when='reward')
|
||||||
|
ul
|
||||||
|
li The Equipment you buy for your avatar is stored in Inventory > Equipment.
|
||||||
|
li Equipment affects your stats (Avatar > Stats).
|
||||||
|
li There will be special equipment here during World Events.
|
||||||
|
li Don't be afraid to set custom rewards! Check out some samples <a href='http://habitrpg.wikia.com/wiki/Sample_Custom_Rewards' target='_blank'>here</a>
|
||||||
|
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||||
|
|
||||||
|
|
||||||
// Todo Chart
|
// Todo Chart
|
||||||
.todos-chart(ng-if='::list.type == "todo"', ng-show='charts.todos')
|
.todos-chart(ng-if='::list.type == "todo"', ng-show='charts.todos')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
|
li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.popoverOpen ? '' : task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
|
||||||
// right-hand side control buttons
|
// right-hand side control buttons
|
||||||
.task-meta-controls
|
.task-meta-controls
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
span.glyphicon.glyphicon-signal
|
span.glyphicon.glyphicon-signal
|
||||||
|
|
|
|
||||||
// notes
|
// notes
|
||||||
span.task-notes(ng-show='task.notes && !task._editing')
|
span.task-notes(ng-show='task.notes && !task._editing', ng-click='task.popoverOpen = !task.popoverOpen', popover-trigger='click', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}')
|
||||||
span.glyphicon.glyphicon-comment
|
span.glyphicon.glyphicon-comment
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,4 +69,4 @@ script(id='modals/login.html', type='text/ng-template')
|
||||||
a(href='/static/privacy' target='_blank')=env.t('privacy')
|
a(href='/static/privacy' target='_blank')=env.t('privacy')
|
||||||
| .
|
| .
|
||||||
.form-group
|
.form-group
|
||||||
input.btn.btn-primary(type='submit', ng-disabled='registrationForm.$invalid', value=env.t('register'))
|
input.btn.btn-primary(type='submit', ng-disabled='registrationForm.$invalid || registrationInProgress', value=env.t('register'))
|
||||||
|
|
|
||||||