diff --git a/dist/habitrpg-shared.js b/dist/habitrpg-shared.js index a62c2e1490..06f8a3f5be 100644 --- a/dist/habitrpg-shared.js +++ b/dist/habitrpg-shared.js @@ -13699,6 +13699,11 @@ api.dotGet = function(obj, path) { })(this)), obj); }; +api.planGemLimits = { + convRate: 20, + convCap: 25 +}; + /* ------------------------------------------------------ @@ -14734,8 +14739,36 @@ api.wrap = function(user, main) { return typeof cb === "function" ? cb(null, _.pick(user, $w('items stats'))) : void 0; }, purchase: function(req, cb, ga) { - var item, key, type, _ref; + var convCap, convRate, item, key, type, _ref, _ref1, _ref2, _ref3; _ref = req.params, type = _ref.type, key = _ref.key; + if (type === 'gems' && key === 'gem') { + _ref1 = api.planGemLimits, convRate = _ref1.convRate, convCap = _ref1.convCap; + if (!((_ref2 = user.purchased) != null ? (_ref3 = _ref2.plan) != null ? _ref3.planId : void 0 : void 0)) { + return typeof cb === "function" ? cb({ + code: 401, + message: "Must subscribe to purchase gems with GP" + }, req) : void 0; + } + if (!(user.stats.gp >= convRate)) { + return typeof cb === "function" ? cb({ + code: 401, + message: "Not enough Gold" + }) : void 0; + } + if (user.purchased.plan.gemsBought >= convCap) { + return typeof cb === "function" ? cb({ + code: 401, + message: "You've reached the Gold=>Gem conversion cap (" + convCap + ") for this month. We have this to prevent abuse / farming, please wait until the beginning of next month." + }) : void 0; + } + user.balance += .25; + user.purchased.plan.gemsBought++; + user.stats.gp -= convRate; + return typeof cb === "function" ? cb({ + code: 200, + message: "+1 Gems" + }, _.pick(user, $w('stats balance'))) : void 0; + } if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'quests' && type !== 'special') { return typeof cb === "function" ? cb({ code: 404, @@ -15065,6 +15098,28 @@ api.wrap = function(user, main) { } return typeof cb === "function" ? cb(null, 'items.special') : void 0; }, + openMysteryItem: function(req, cb, ga) { + var item, _ref, _ref1; + item = (_ref = user.purchased.plan) != null ? (_ref1 = _ref.mysteryItems) != null ? _ref1.shift() : void 0 : void 0; + if (!item) { + return typeof cb === "function" ? cb({ + code: 400, + message: "Empty" + }) : void 0; + } + item = content.gear.flat[item]; + user.items.gear.owned[item.key] = true; + if (typeof user.markModified === "function") { + user.markModified('purchased.plan.mysteryItems'); + } + if (typeof window !== 'undefined') { + (user._tmp != null ? user._tmp : user._tmp = {}).drop = { + type: 'gear', + dialog: "" + (item.text(req.language)) + " inside!" + }; + } + return typeof cb === "function" ? cb(null, user.items.gear.owned) : void 0; + }, score: function(req, cb) { var addPoints, calculateDelta, calculateReverseDelta, changeTaskValue, delta, direction, id, mpDelta, multiplier, num, options, stats, subtractPoints, task, th, _ref; _ref = req.params, id = _ref.id, direction = _ref.direction; @@ -15366,7 +15421,7 @@ api.wrap = function(user, main) { return api.dotGet(user, path); }, randomDrop: function(modifiers, req) { - var acceptableDrops, chance, drop, dropK, quest, rarity, task, _base, _base1, _base2, _name, _name1, _name2, _ref, _ref1; + var acceptableDrops, chance, drop, dropK, dropMultiplier, quest, rarity, task, _base, _base1, _base2, _name, _name1, _name2, _ref, _ref1, _ref2, _ref3; task = modifiers.task; chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + .02; chance *= task.priority * (1 + (task.streak / 100 || 0)) * (1 + (user._statsComputed.per / 100)) * (1 + (user.contributor.level / 40 || 0)) * (1 + (user.achievements.rebirths / 20 || 0)) * (1 + (user.achievements.streak / 200 || 0)) * (user._tmp.crit || 1) * (1 + .5 * (_.reduce(task.checklist, (function(m, i) { @@ -15383,10 +15438,11 @@ api.wrap = function(user, main) { user.markModified('party.quest.progress'); } } - if ((api.daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= 5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0))) { + dropMultiplier = ((_ref1 = user.purchased) != null ? (_ref2 = _ref1.plan) != null ? _ref2.customerId : void 0 : void 0) ? 2 : 1; + if ((api.daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0)))) { return; } - if (((_ref1 = user.flags) != null ? _ref1.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) { + if (((_ref3 = user.flags) != null ? _ref3.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) { rarity = user.fns.predictableRandom(user.stats.gp); if (rarity > .6) { drop = user.fns.randomVal(_.where(content.food, { @@ -15579,7 +15635,7 @@ api.wrap = function(user, main) { {user} */ cron: function(options) { - var clearBuffs, daysMissed, expTally, lvl, lvlDiv2, now, perfect, plan, progress, todoTally, _base, _base1, _base2, _base3, _progress, _ref; + var clearBuffs, daysMissed, expTally, lvl, lvlDiv2, now, perfect, plan, progress, todoTally, _base, _base1, _base2, _base3, _progress, _ref, _ref1, _ref2, _ref3, _ref4; if (options == null) { options = {}; } @@ -15595,6 +15651,12 @@ api.wrap = function(user, main) { if (user.items.lastDrop.count > 0) { user.items.lastDrop.count = 0; } + if ((_ref = user.purchased) != null ? (_ref1 = _ref.plan) != null ? _ref1.customerId : void 0 : void 0) { + if (moment(user.purchased.plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) { + user.purchased.plan.gemsBought = 0; + user.purchased.plan.dateUpdated = new Date(); + } + } perfect = true; clearBuffs = { str: 0, @@ -15693,12 +15755,14 @@ api.wrap = function(user, main) { date: now, value: expTally }); - user.fns.preenUserHistory(); - if (typeof user.markModified === "function") { - user.markModified('history'); - } - if (typeof user.markModified === "function") { - user.markModified('dailys'); + if (!((_ref2 = user.purchased) != null ? (_ref3 = _ref2.plan) != null ? _ref3.customerId : void 0 : void 0)) { + user.fns.preenUserHistory(); + if (typeof user.markModified === "function") { + user.markModified('history'); + } + if (typeof user.markModified === "function") { + user.markModified('dailys'); + } } user.stats.buffs = perfect ? ((_base3 = user.achievements).perfect != null ? _base3.perfect : _base3.perfect = 0, user.achievements.perfect++, user.stats.lvl < 100 ? lvlDiv2 = Math.ceil(user.stats.lvl / 2) : lvlDiv2 = 50, { str: lvlDiv2, @@ -15712,7 +15776,7 @@ api.wrap = function(user, main) { if (user.stats.mp > user._statsComputed.maxMP) { user.stats.mp = user._statsComputed.maxMP; } - plan = (_ref = user.purchased) != null ? _ref.plan : void 0; + plan = (_ref4 = user.purchased) != null ? _ref4.plan : void 0; if (plan && plan.customerId && plan.dateTerminated && moment(plan.dateTerminated).isBefore(+(new Date))) { _.merge(user.purchased.plan, { planId: null, diff --git a/script/index.coffee b/script/index.coffee index e5bfc19f52..bdf2168e64 100644 --- a/script/index.coffee +++ b/script/index.coffee @@ -8,6 +8,7 @@ api.i18n = i18n # little helper for large arrays of strings. %w"this that another" equivalent from Rails, I really miss that function $w = api.$w = (s)->s.split(' ') + api.dotSet = (obj,path,val)-> arr = path.split('.') _.reduce arr, (curr, next, index) => @@ -18,6 +19,10 @@ api.dotSet = (obj,path,val)-> api.dotGet = (obj,path)-> _.reduce path.split('.'), ((curr, next) => curr?[next]), obj +api.planGemLimits = + convRate: 20 #how much does a gem cost? + convCap: 25 #how many gems can be converted / month? + ### ------------------------------------------------------ Time / Day @@ -664,6 +669,17 @@ api.wrap = (user, main=true) -> # buy is for gear, purchase is for gem-purchaseables (i know, I know...) purchase: (req, cb, ga) -> {type,key} = req.params + + if type is 'gems' and key is 'gem' + {convRate,convCap} = api.planGemLimits + return cb?({code:401,message:"Must subscribe to purchase gems with GP"},req) unless user.purchased?.plan?.planId + return cb?({code:401,message:"Not enough Gold"}) unless user.stats.gp >= convRate + return cb?({code:401,message:"You've reached the Gold=>Gem conversion cap (#{convCap}) for this month. We have this to prevent abuse / farming, please wait until the beginning of next month."}) if user.purchased.plan.gemsBought >= convCap + user.balance += .25 + user.purchased.plan.gemsBought++ + user.stats.gp -= convRate + return cb? {code:200,message:"+1 Gems"}, _.pick(user,$w 'stats balance') + return cb?({code:404,message:":type must be in [hatchingPotions,eggs,food,quests,special]"},req) unless type in ['eggs','hatchingPotions','food','quests','special'] item = content[type][key] return cb?({code:404,message:":key not found for Content.#{type}"},req) unless item @@ -853,6 +869,17 @@ api.wrap = (user, main=true) -> user.markModified? 'items.special.valentineReceived' cb? null, 'items.special' + openMysteryItem: (req,cb,ga) -> + item = user.purchased.plan?.mysteryItems?.shift() + return cb?(code:400,message:"Empty") unless item + item = content.gear.flat[item] + user.items.gear.owned[item.key] = true + user.markModified? 'purchased.plan.mysteryItems' + # Could show {code:200} message, but it's yellow with no icon. This is round-about, but prettier. FIXME + (user._tmp?={}).drop = {type: 'gear', dialog: "#{item.text(req.language)} inside!"} if typeof window != 'undefined' + #cb? {code:200, message:"#{item.text} inside!"}, user.items.gear.owned + cb? null, user.items.gear.owned + # ------ # Score # ------ @@ -1151,7 +1178,8 @@ api.wrap = (user, main=true) -> user.markModified? 'party.quest.progress' #console.log {progress:user.party.quest.progress} - return if (api.daysSince(user.items.lastDrop.date, user.preferences) is 0) and (user.items.lastDrop.count >= 5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level or 0)) + dropMultiplier = if user.purchased?.plan?.customerId then 2 else 1 + return if (api.daysSince(user.items.lastDrop.date, user.preferences) is 0) and (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level or 0))) if user.flags?.dropsEnabled and user.fns.predictableRandom(user.stats.exp) < chance # current breakdown - 1% (adjustable) chance on drop @@ -1326,6 +1354,13 @@ api.wrap = (user, main=true) -> if user.items.lastDrop.count > 0 user.items.lastDrop.count = 0 + # Reset caps for subscribers + if user.purchased?.plan?.customerId + if moment(user.purchased.plan.dateUpdated).format('MMYYYY') != moment().format('MMYYYY') + #+moment().format('D') >= +moment(user.purchased.plan.dateCreated).format('D') and + user.purchased.plan.gemsBought = 0 + user.purchased.plan.dateUpdated = new Date() + # "Perfect Day" achievement for perfect-days perfect = true clearBuffs = {str:0,int:0,per:0,con:0,stealth:0,streaks:false} @@ -1388,9 +1423,14 @@ api.wrap = (user, main=true) -> lvl++ expTally += api.tnl(lvl) (user.history.exp ?= []).push { date: now, value: expTally } - user.fns.preenUserHistory() - user.markModified? 'history' - user.markModified? 'dailys' # covers dailys.*.history + + # premium subscribers can keep their full history. + # TODO figure out something performance-wise + unless user.purchased?.plan?.customerId + user.fns.preenUserHistory() + user.markModified? 'history' + user.markModified? 'dailys' # covers dailys.*.history + user.stats.buffs = if perfect user.achievements.perfect ?= 0