From ab5bd6d5af9b7a9be695c7ebbfddd755da074949 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 12 Dec 2013 15:57:01 -0700 Subject: [PATCH] [https://github.com/HabitRPG/habitrpg/issues/1977] port pet-feeding to user.ops, so that it's now available in the API. Also add handling of 200 errors to simply notify and continue on up to server (friendly messages) --- dist/habitrpg-shared.js | 96 +++++++++++++++++++++++++++++++++++++++-- script/index.coffee | 34 ++++++++++++++- 2 files changed, 124 insertions(+), 6 deletions(-) diff --git a/dist/habitrpg-shared.js b/dist/habitrpg-shared.js index 6ceaa5508f..5ddee465e7 100644 --- a/dist/habitrpg-shared.js +++ b/dist/habitrpg-shared.js @@ -10478,9 +10478,15 @@ var process=require("__browserify_process");(function() { }); } if (scheduleMisses > 0) { - api.score(user, task, 'down', { - times: scheduleMisses, - cron: true + user.ops.score({ + params: { + id: task.id, + direction: 'down' + }, + query: { + times: scheduleMisses, + cron: true + } }); } } @@ -10862,7 +10868,40 @@ var process=require("__browserify_process");(function() { /* - This wraps the user prototype, giving it functions + User is now wrapped (both on client and server), adding a few new properties: + * getters (_statsComputed, tasks, etc) + * user.fns, which is a bunch of helper functions + These were originally up above, but they make more sense belonging to the user object so we don't have to pass + the user object all over the place. In fact, we should pull in more functions such as cron(), updateStats(), etc. + * user.ops, which is super important: + + If a function is inside user.ops, it has magical properties. If you call it on the client it updates the user object in + the browser and when it's done it POSTs to the server, calling `src/controllers/user.js#OP_NAME` (the exact same name + of the op function). The first argument `req` is `{query, body, params}`, it's what the express controller function + expects. Because of this, we don't construct op functions in standard way (function(arg1,arg2,arg3){}), but instead + the first arg is {query, body, params} and the second is a callback(err, results). `query` is the most commonly-used, + but body is useful for updates/creates (eg, tasks), and params are used in those cases too (eg, {op:'updateTask',{params:{id:task.id}, body:task}}}) + This needs refinement: see http://stackoverflow.com/questions/4024271/rest-api-best-practices-where-to-put-parameters + + If `src/controllers/user.js#OP_NAME` doesn't exist, it's automatically added. It runs the code in user.ops.OP_NAME + to update the user model server-side, then performs `user.save()`. You can see this in action for `user.ops.buy`. That + function doesn't exist on the server - so the client calls it, it updates user in the browser, auto-POSTs to server, server + handles it by calling `user.ops.buy` again (to update user on the server), and then saves. We can do this for + everything that doesn't need any code different from what's in user.ops.OP_NAME for special-handling server-side. If we + *do* need special handling, just add `src/controllers/user.js#OP_NAME` to override the user.ops.OP_NAME, and just be + sure to call user.ops.OP_NAME at some point within the overridden function. + + TODO + * Allow different error handling - like "200 - not enough gold" (which shouldn't be treated as 500) + * Is this the best way to wrap the user object? I thought of using user.prototype, but user is an object not a Function. + user on the server is a Mongoose model, so we can use prototype - but to do it on the client, we'd probably have to + move to $resource for user + * Move to $resource! + * Migrate the remaining parts: Tags, feeding pets, etc + + BUGS + * User registration doesn't refresh, and you have to click "Play" again (and refresh *yet again* after getting in) + * Daily repeats don't have the days showing up (translations issue?) */ @@ -10897,6 +10936,55 @@ var process=require("__browserify_process");(function() { } return task; }, + feed: function(req, cb) { + var egg, evolve, food, pet, potion, userPets, _ref, _ref1, _ref2, _ref3; + _ref2 = [(_ref = req.query) != null ? _ref.pet : void 0, content.food[(_ref1 = req.query) != null ? _ref1.food : void 0]], pet = _ref2[0], food = _ref2[1]; + _ref3 = pet.split('-'), egg = _ref3[0], potion = _ref3[1]; + if (!(pet && food)) { + return cb("?food=__&pet=__ are both required in query, and must be valid"); + } + if (content.specialPets[pet]) { + return cb("Can't feed this pet."); + } + if (user.items.mounts[pet] && (userPets[pet] >= 50 || food.name === 'Saddle')) { + return cb("You already have that mount"); + } + userPets = user.items.pets; + evolve = function() { + userPets[pet] = 0; + user.items.mounts[pet] = true; + if (pet === user.items.currentPet) { + user.items.currentPet = ""; + } + return cb({ + code: 200, + message: "You have tamed " + egg + ", let's go for a ride!" + }, req); + }; + if (food.name === 'Saddle') { + evolve(); + } else { + if (food.target === potion) { + userPets[pet] += 5; + cb({ + code: 200, + message: "" + egg + " really likes the " + food.name + "!" + }, req); + } else { + userPets[pet] += 2; + cb({ + code: 200, + message: "" + egg + " eats the " + food.name + " but doesn't seem to enjoy it." + }, req); + } + if (userPets[pet] >= 50 && !user.items.mounts[pet]) { + evolve(); + } + } + user.items.pets[pet] = userPets[pet]; + user.items.food[food.name]--; + return typeof cb === "function" ? cb(null, req) : void 0; + }, buy: function(req, cb) { var item, key, _ref; key = req.query.key; diff --git a/script/index.coffee b/script/index.coffee index da9640eb89..02dae96cf0 100644 --- a/script/index.coffee +++ b/script/index.coffee @@ -344,7 +344,8 @@ api.cron = (user, options={}) -> _.times daysMissed, (n) -> thatDay = moment(now).subtract('days', n + 1) scheduleMisses++ if api.shouldDo(thatDay, repeat, user.preferences) - api.score(user, task, 'down', {times:scheduleMisses, cron:true}) if scheduleMisses > 0 + if scheduleMisses > 0 + user.ops.score({params:{id:task.id, direction:'down'}, query:{times:scheduleMisses, cron:true}}) switch type when 'daily' @@ -647,6 +648,35 @@ api.wrap = (user) -> cb? null, req task + feed: (req, cb) -> + [pet, food] = [req.query?.pet, content.food[req.query?.food]] + [egg, potion] = pet.split('-') + + return cb("?food=__&pet=__ are both required in query, and must be valid") unless pet and food # TODO validation on pet string + return cb("Can't feed this pet.") if content.specialPets[pet] + return cb("You already have that mount") if user.items.mounts[pet] and (userPets[pet] >= 50 or food.name is 'Saddle') + + userPets = user.items.pets + evolve = -> + userPets[pet] = 0 + user.items.mounts[pet] = true + user.items.currentPet = "" if pet is user.items.currentPet + cb {code:200, message:"You have tamed #{egg}, let's go for a ride!"}, req + + if food.name is 'Saddle' + evolve() + else + if food.target is potion + userPets[pet] += 5 + cb {code:200, message: "#{egg} really likes the #{food.name}!"}, req + else + userPets[pet] += 2 + cb {code:200, message: "#{egg} eats the #{food.name} but doesn't seem to enjoy it."}, req + evolve() if userPets[pet] >= 50 and not user.items.mounts[pet] + user.items.pets[pet] = userPets[pet] + user.items.food[food.name]-- + cb? null, req + buy: (req, cb) -> {key} = req.query item = if key is 'potion' then content.potion else content.gear.flat[key] @@ -694,7 +724,7 @@ api.wrap = (user) -> user.stats.lvl-- if user.stats.lvl > 1 # Lose a stat point - lostStat = randomVal _.reduce ['str','con','per','int'], ((m,k)->(m[k]=k if user.stats[v];m)), {} + lostStat = randomVal _.reduce(['str','con','per','int'], ((m,k)->(m[k]=k if user.stats[v];m)), {}) user.stats[lostStat]-- if lostStat # Lose a gear piece