mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-28 01:55:38 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
1221265d08
14 changed files with 60 additions and 82 deletions
|
@ -117,51 +117,15 @@ module.exports = class User extends CocoModel
|
||||||
application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin()
|
application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin()
|
||||||
@announcesActionAudioGroup
|
@announcesActionAudioGroup
|
||||||
|
|
||||||
getGemPromptGroup: ->
|
getFourthLevelGroup: ->
|
||||||
# A/B Testing whether extra prompt when low gems leads to more gem purchases
|
return @fourthLevelGroup if @fourthLevelGroup
|
||||||
# TODO: Rename gem purchase event in BuyGemsModal to 'Started gem purchase' after this test is over
|
|
||||||
return @gemPromptGroup if @gemPromptGroup
|
|
||||||
group = me.get('testGroupNumber') % 8
|
group = me.get('testGroupNumber') % 8
|
||||||
@gemPromptGroup = switch group
|
@fourthLevelGroup = switch group
|
||||||
when 0, 1, 2, 3 then 'prompt'
|
when 0, 1, 2, 3 then 'signs-and-portents'
|
||||||
when 4, 5, 6, 7 then 'no-prompt'
|
when 4, 5, 6, 7 then 'forgetful-gemsmith'
|
||||||
@gemPromptGroup = 'prompt' if me.isAdmin()
|
@fourthLevelGroup = 'signs-and-portents' if me.isAdmin()
|
||||||
application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin()
|
application.tracker.identify fourthLevelGroup: @fourthLevelGroup unless me.isAdmin()
|
||||||
@gemPromptGroup
|
@fourthLevelGroup
|
||||||
|
|
||||||
getForeshadowsLevels: ->
|
|
||||||
return false if $.browser.msie
|
|
||||||
return @foreshadowsLevels if @foreshadowsLevels?
|
|
||||||
group = me.get('testGroupNumber') % 16
|
|
||||||
@foreshadowsLevels = switch group
|
|
||||||
when 0, 1, 2, 3, 4, 5, 6, 7 then true
|
|
||||||
when 8, 9, 10, 11, 12, 13, 14, 15 then false
|
|
||||||
@foreshadowsLevels = true if me.isAdmin()
|
|
||||||
application.tracker.identify foreshadowsLevels: @foreshadowsLevels unless me.isAdmin()
|
|
||||||
@foreshadowsLevels
|
|
||||||
|
|
||||||
getLeaderboardsGroup: ->
|
|
||||||
return @leaderboardsGroup if @leaderboardsGroup?
|
|
||||||
group = me.get('testGroupNumber') % 64
|
|
||||||
if group < 16
|
|
||||||
@leaderboardsGroup = 'always'
|
|
||||||
else if group < 32
|
|
||||||
@leaderboardsGroup = 'early'
|
|
||||||
else if group < 48
|
|
||||||
@leaderboardsGroup = 'late'
|
|
||||||
else
|
|
||||||
@leaderboardsGroup = 'never'
|
|
||||||
@leaderboardsGroup = 'always' if me.isAdmin()
|
|
||||||
application.tracker.identify leaderboardsGroup: @leaderboardsGroup unless me.isAdmin()
|
|
||||||
@leaderboardsGroup
|
|
||||||
|
|
||||||
getShowsPortal: ->
|
|
||||||
return @showsPortal if @showsPortal?
|
|
||||||
group = me.get('testGroupNumber')
|
|
||||||
@showsPortal = if group < 128 then true else false
|
|
||||||
@showsPortal = true if me.isAdmin()
|
|
||||||
application.tracker.identify showsPortal: @showsPortal unless me.isAdmin()
|
|
||||||
@showsPortal
|
|
||||||
|
|
||||||
getVideoTutorialStylesIndex: (numVideos=0)->
|
getVideoTutorialStylesIndex: (numVideos=0)->
|
||||||
# A/B Testing video tutorial styles
|
# A/B Testing video tutorial styles
|
||||||
|
|
|
@ -402,9 +402,9 @@ $gameControlMargin: 30px
|
||||||
|
|
||||||
.level-indicator
|
.level-indicator
|
||||||
margin-left: 15px
|
margin-left: 15px
|
||||||
color: #E5C100
|
color: white
|
||||||
display: inline-block
|
display: inline-block
|
||||||
margin: 0p 2px
|
margin: 0 2px
|
||||||
|
|
||||||
.player-hero-icon
|
.player-hero-icon
|
||||||
margin-left: 10px
|
margin-left: 10px
|
||||||
|
|
|
@ -83,7 +83,8 @@ else
|
||||||
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
|
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
|
||||||
if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp
|
if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp
|
||||||
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
|
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
|
||||||
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
|
if !me.get('anonymous', true)
|
||||||
|
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
|
||||||
if me.isAdmin()
|
if me.isAdmin()
|
||||||
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
|
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
|
||||||
else if me.get('anonymous', true)
|
else if me.get('anonymous', true)
|
||||||
|
|
|
@ -59,8 +59,6 @@ module.exports = class CampaignView extends RootView
|
||||||
@editorMode = options?.editorMode
|
@editorMode = options?.editorMode
|
||||||
if @editorMode
|
if @editorMode
|
||||||
@terrain ?= 'dungeon'
|
@terrain ?= 'dungeon'
|
||||||
else unless me.getShowsPortal()
|
|
||||||
@terrain ?= 'dungeon'
|
|
||||||
@levelStatusMap = {}
|
@levelStatusMap = {}
|
||||||
@levelPlayCountMap = {}
|
@levelPlayCountMap = {}
|
||||||
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model
|
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model
|
||||||
|
@ -160,6 +158,9 @@ module.exports = class CampaignView extends RootView
|
||||||
context = super(context)
|
context = super(context)
|
||||||
context.campaign = @campaign
|
context.campaign = @campaign
|
||||||
context.levels = _.values($.extend true, {}, @campaign?.get('levels') ? {})
|
context.levels = _.values($.extend true, {}, @campaign?.get('levels') ? {})
|
||||||
|
if me.level() < 12 and @terrain is 'dungeon' and not @editorMode
|
||||||
|
reject = if me.getFourthLevelGroup() is 'signs-and-portents' then 'forgetful-gemsmith' else 'signs-and-portents'
|
||||||
|
context.levels = _.reject context.levels, slug: reject
|
||||||
@annotateLevel level for level in context.levels
|
@annotateLevel level for level in context.levels
|
||||||
count = @countLevels context.levels
|
count = @countLevels context.levels
|
||||||
context.levelsCompleted = count.completed
|
context.levelsCompleted = count.completed
|
||||||
|
@ -257,6 +258,7 @@ module.exports = class CampaignView extends RootView
|
||||||
level.locked = false if me.isInGodMode()
|
level.locked = false if me.isInGodMode()
|
||||||
level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
|
level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
|
||||||
level.disabled = false if me.isInGodMode()
|
level.disabled = false if me.isInGodMode()
|
||||||
|
level.locked = false
|
||||||
level.color = 'rgb(255, 80, 60)'
|
level.color = 'rgb(255, 80, 60)'
|
||||||
if level.requiresSubscription
|
if level.requiresSubscription
|
||||||
level.color = 'rgb(80, 130, 200)'
|
level.color = 'rgb(80, 130, 200)'
|
||||||
|
@ -323,7 +325,7 @@ module.exports = class CampaignView extends RootView
|
||||||
@playAmbientSound()
|
@playAmbientSound()
|
||||||
|
|
||||||
testParticles: ->
|
testParticles: ->
|
||||||
return unless @campaign?.loaded and me.getForeshadowsLevels()
|
return unless @campaign?.loaded and $.browser.chrome # Sometimes this breaks in non-Chrome browsers, according to A/B tests.
|
||||||
@particleMan ?= new ParticleMan()
|
@particleMan ?= new ParticleMan()
|
||||||
@particleMan.removeEmitters()
|
@particleMan.removeEmitters()
|
||||||
@particleMan.attach @$el.find('.map')
|
@particleMan.attach @$el.find('.map')
|
||||||
|
|
|
@ -150,11 +150,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
# Show the "I'm done" button between 30 - 120 minutes if they definitely came from Hour of Code
|
# Show the "I'm done" button between 30 - 120 minutes if they definitely came from Hour of Code
|
||||||
c.showHourOfCodeDoneButton = me.get('hourOfCode') and showDone
|
c.showHourOfCodeDoneButton = me.get('hourOfCode') and showDone
|
||||||
|
|
||||||
if @level.get('scoreTypes')?.length
|
c.showLeaderboard = @level.get('scoreTypes')?.length > 0
|
||||||
lg = me.getLeaderboardsGroup()
|
|
||||||
c.showLeaderboard = lg is 'always'
|
|
||||||
c.showLeaderboard = true if me.level() >= 3 and lg.group is 'early'
|
|
||||||
c.showLeaderboard = true if me.level() >= 5 and lg.group is 'late'
|
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
|
@ -563,9 +563,6 @@ module.exports = class InventoryModal extends ModalView
|
||||||
return @openModalView authModal
|
return @openModalView authModal
|
||||||
|
|
||||||
askToBuyGems: (unlockButton) ->
|
askToBuyGems: (unlockButton) ->
|
||||||
if me.getGemPromptGroup() is 'no-prompt'
|
|
||||||
return @askToSignUp() if me.get('anonymous')
|
|
||||||
return @openModalView new BuyGemsModal()
|
|
||||||
@$el.find('.unlock-button').popover 'destroy'
|
@$el.find('.unlock-button').popover 'destroy'
|
||||||
popoverTemplate = buyGemsPromptTemplate {}
|
popoverTemplate = buyGemsPromptTemplate {}
|
||||||
unlockButton.popover(
|
unlockButton.popover(
|
||||||
|
|
|
@ -65,8 +65,7 @@ module.exports = class BuyGemsModal extends ModalView
|
||||||
Backbone.Mediator.publish 'buy-gems-modal:purchase-initiated', { productID: productID }
|
Backbone.Mediator.publish 'buy-gems-modal:purchase-initiated', { productID: productID }
|
||||||
|
|
||||||
else
|
else
|
||||||
# TODO: rename this event to 'Started gem purchase' after gemPrompt A/B test is over
|
application.tracker?.trackEvent 'Started gem purchase', { productID: productID }
|
||||||
application.tracker?.trackEvent 'Started purchase', { productID: productID }
|
|
||||||
stripeHandler.open({
|
stripeHandler.open({
|
||||||
description: $.t(product.i18n)
|
description: $.t(product.i18n)
|
||||||
amount: product.amount
|
amount: product.amount
|
||||||
|
|
|
@ -265,9 +265,6 @@ module.exports = class PlayHeroesModal extends ModalView
|
||||||
return @openModalView authModal
|
return @openModalView authModal
|
||||||
|
|
||||||
askToBuyGems: (unlockButton) ->
|
askToBuyGems: (unlockButton) ->
|
||||||
if me.getGemPromptGroup() is 'no-prompt'
|
|
||||||
return @askToSignUp() if me.get('anonymous')
|
|
||||||
return @openModalView new BuyGemsModal()
|
|
||||||
@$el.find('.unlock-button').popover 'destroy'
|
@$el.find('.unlock-button').popover 'destroy'
|
||||||
popoverTemplate = buyGemsPromptTemplate {}
|
popoverTemplate = buyGemsPromptTemplate {}
|
||||||
unlockButton.popover(
|
unlockButton.popover(
|
||||||
|
|
|
@ -216,9 +216,6 @@ module.exports = class PlayItemsModal extends ModalView
|
||||||
return @openModalView authModal
|
return @openModalView authModal
|
||||||
|
|
||||||
askToBuyGems: (unlockButton) ->
|
askToBuyGems: (unlockButton) ->
|
||||||
if me.getGemPromptGroup() is 'no-prompt'
|
|
||||||
return @askToSignUp() if me.get('anonymous')
|
|
||||||
return @openModalView new BuyGemsModal()
|
|
||||||
@$el.find('.unlock-button').popover 'destroy'
|
@$el.find('.unlock-button').popover 'destroy'
|
||||||
popoverTemplate = buyGemsPromptTemplate {}
|
popoverTemplate = buyGemsPromptTemplate {}
|
||||||
unlockButton.popover(
|
unlockButton.popover(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// foreshadowsLevels A/B Results
|
// foreshadowsLevels A/B Results
|
||||||
// Test started 2015-01-29
|
// Test started 2015-01-29, ended 2015-02-26
|
||||||
// Fixed some particle problems on 2015-02-02
|
// Fixed some particle problems on 2015-02-02
|
||||||
|
// Fixed more particle problems in old browsers on 2015-02-19
|
||||||
|
// Final results: tests showed no difference in completion rates, but slight drops in level start rates for non-Chrome browsers, so we stopped doing it outside of Chrome.
|
||||||
|
|
||||||
// Usage:
|
// Usage:
|
||||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||||
|
@ -9,18 +11,18 @@ load('abTestHelpers.js');
|
||||||
|
|
||||||
var scriptStartTime = new Date();
|
var scriptStartTime = new Date();
|
||||||
try {
|
try {
|
||||||
var startDay = '2015-02-03'
|
var startDay = '2015-02-20';
|
||||||
log("Today is " + new Date().toISOString().substr(0, 10));
|
log("Today is " + new Date().toISOString().substr(0, 10));
|
||||||
log("Start day is " + startDay);
|
log("Start day is " + startDay);
|
||||||
|
|
||||||
var eventFunnel = ['Started Level', 'Saw Victory'];
|
var eventFunnel = ['Started Level', 'Saw Victory'];
|
||||||
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith'];
|
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith', 'kounter-kithwise', 'kithgard-gates', 'rich-forager', 'village-guard'];
|
||||||
|
|
||||||
// getForeshadowsLevels
|
// getForeshadowsLevels
|
||||||
var testGroupFn = function (testGroupNumber) {
|
var testGroupFn = function (testGroupNumber) {
|
||||||
var group = testGroupNumber % 16
|
var group = testGroupNumber % 16
|
||||||
return group >= 0 && group <= 7;
|
return group >= 0 && group <= 7;
|
||||||
}
|
};
|
||||||
|
|
||||||
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs);
|
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs);
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
// gemPromptGroup A/B Results
|
// gemPromptGroup A/B Results
|
||||||
// Test started 2014-11-24
|
// Test started 2014-11-24, ended 2015-02-26
|
||||||
|
// Final results:
|
||||||
|
// no-prompt 3789 gem shop shown, 62 purchased
|
||||||
|
// prompt 2658 gem shop shown, 78 purchased
|
||||||
|
// Decided prompt was better, so now always prompt. (Yay being nice.)
|
||||||
|
|
||||||
// Usage:
|
// Usage:
|
||||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||||
|
|
||||||
// TODO: Why is no-prompt group 50% larger?
|
|
||||||
|
|
||||||
load('abTestHelpers.js');
|
load('abTestHelpers.js');
|
||||||
|
|
||||||
var scriptStartTime = new Date();
|
var scriptStartTime = new Date();
|
||||||
try {
|
try {
|
||||||
var startDay = '2014-11-24'
|
var startDay = '2014-11-24';
|
||||||
// startDay = '2015-01-15'
|
// startDay = '2015-01-15'
|
||||||
log("Today is " + new Date().toISOString().substr(0, 10));
|
log("Today is " + new Date().toISOString().substr(0, 10));
|
||||||
log("Start day is " + startDay);
|
log("Start day is " + startDay);
|
||||||
|
@ -21,7 +23,7 @@ try {
|
||||||
var testGroupFn = function (testGroupNumber) {
|
var testGroupFn = function (testGroupNumber) {
|
||||||
var group = testGroupNumber % 8
|
var group = testGroupNumber % 8
|
||||||
return group >= 0 && group <= 3 ? 'prompt' : 'no-prompt';
|
return group >= 0 && group <= 3 ? 'prompt' : 'no-prompt';
|
||||||
}
|
};
|
||||||
|
|
||||||
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn);
|
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// leaderboardsGroup A/B Results
|
// leaderboardsGroup A/B Results
|
||||||
// Test started 2015-01-30
|
// Test started 2015-01-30, ended 2015-02-26
|
||||||
|
// Final results: no differences in level starts or completions. Perhaps they affect playtime or purchases, but harder to say. At least they don't hurt, so wejust turned them on for everyone always.
|
||||||
|
|
||||||
// Usage:
|
// Usage:
|
||||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||||
|
@ -8,12 +9,12 @@ load('abTestHelpers.js');
|
||||||
|
|
||||||
var scriptStartTime = new Date();
|
var scriptStartTime = new Date();
|
||||||
try {
|
try {
|
||||||
var startDay = '2015-01-30'
|
var startDay = '2015-02-07';
|
||||||
log("Today is " + new Date().toISOString().substr(0, 10));
|
log("Today is " + new Date().toISOString().substr(0, 10));
|
||||||
log("Start day is " + startDay);
|
log("Start day is " + startDay);
|
||||||
|
|
||||||
var eventFunnel = ['Started Level', 'Saw Victory'];
|
var eventFunnel = ['Started Level', 'Saw Victory'];
|
||||||
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith'];
|
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith', 'kounter-kithwise', 'kithgard-gates', 'rich-forager', 'village-guard'];
|
||||||
|
|
||||||
// getLeaderboardsGroup
|
// getLeaderboardsGroup
|
||||||
var testGroupFn = function (testGroupNumber) {
|
var testGroupFn = function (testGroupNumber) {
|
||||||
|
@ -22,7 +23,7 @@ try {
|
||||||
if (group < 32) return 'early';
|
if (group < 32) return 'early';
|
||||||
if (group < 48) return 'late';
|
if (group < 48) return 'late';
|
||||||
return 'never';
|
return 'never';
|
||||||
}
|
};
|
||||||
|
|
||||||
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs);
|
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
// showsPortal A/B Results
|
// showsPortal A/B Results
|
||||||
// Test started 2015-02-05
|
// Test started 2015-02-05, ended 2015-02-26
|
||||||
|
// Final results: seems to help people get to the later levels, and at least definitely doesn't hurt:
|
||||||
|
// dungeons-of-kithgard false 64605 49956 77.33
|
||||||
|
// dungeons-of-kithgard true 65380 50590 77.38
|
||||||
|
// gems-in-the-deep false 45339 40788 89.96
|
||||||
|
// gems-in-the-deep true 45922 41455 90.27
|
||||||
|
// kithgard-gates false 7415 6904 93.11
|
||||||
|
// kithgard-gates true 7783 7249 93.14
|
||||||
|
// kounter-kithwise true 95 92 96.84
|
||||||
|
// kounter-kithwise false 86 77 89.53
|
||||||
|
// rich-forager false 1067 822 77.04
|
||||||
|
// rich-forager true 1111 834 75.07
|
||||||
|
// shadow-guard false 38089 35239 92.52
|
||||||
|
// shadow-guard true 38774 35975 92.78
|
||||||
|
// the-mighty-sand-yak true 505 400 79.21
|
||||||
|
// the-mighty-sand-yak false 425 329 77.41
|
||||||
|
//
|
||||||
|
// Group totals:
|
||||||
|
// false 157026 134115 85.41
|
||||||
|
// true 159570 136595 85.60
|
||||||
|
|
||||||
|
|
||||||
// Usage:
|
// Usage:
|
||||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||||
|
@ -13,7 +33,7 @@ try {
|
||||||
log("Start day is " + startDay);
|
log("Start day is " + startDay);
|
||||||
|
|
||||||
var eventFunnel = ['Started Level', 'Saw Victory'];
|
var eventFunnel = ['Started Level', 'Saw Victory'];
|
||||||
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith'];
|
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'kounter-kithwise', 'kithgard-gates', 'rich-forager', 'the-mighty-sand-yak'];
|
||||||
|
|
||||||
// getShowsPortal
|
// getShowsPortal
|
||||||
var testGroupFn = function (testGroupNumber) {
|
var testGroupFn = function (testGroupNumber) {
|
||||||
|
|
|
@ -345,11 +345,11 @@ LevelHandler = class LevelHandler extends Handler
|
||||||
@sendSuccess res, data
|
@sendSuccess res, data
|
||||||
|
|
||||||
hasAccessToDocument: (req, document, method=null) ->
|
hasAccessToDocument: (req, document, method=null) ->
|
||||||
|
return true if req.user?.isArtisan()
|
||||||
method ?= req.method
|
method ?= req.method
|
||||||
return true if method is null or method is 'get'
|
return true if method is null or method is 'get'
|
||||||
super(req, document, method)
|
super(req, document, method)
|
||||||
|
|
||||||
|
|
||||||
getLevelPlaytimesBySlugs: (req, res) ->
|
getLevelPlaytimesBySlugs: (req, res) ->
|
||||||
# Returns an array of per-day level average playtimes
|
# Returns an array of per-day level average playtimes
|
||||||
# Parameters:
|
# Parameters:
|
||||||
|
|
Loading…
Reference in a new issue