Ads for free campaign players

Display leaderboard ads on campaign and play views.
Do no show ads in classroom, picoCTF, or to teachers.
Add no ads blurb to subscription features matrix.
Scale game UI for ads on short screens.

Closes 
This commit is contained in:
Matt Lott 2016-03-15 15:51:59 -07:00
parent 1bab6cee88
commit 255ebbc048
12 changed files with 292 additions and 204 deletions

View file

@ -530,11 +530,20 @@ module.exports = Surface = class Surface extends CocoClass
newWidth = 0.55 * pageWidth newWidth = 0.55 * pageWidth
newHeight = newWidth / aspectRatio newHeight = newWidth / aspectRatio
return unless newWidth > 0 and newHeight > 0 return unless newWidth > 0 and newHeight > 0
return if newWidth is oldWidth and newHeight is oldHeight and not @options.spectateGame
return if newWidth < 200 or newHeight < 200
#scaleFactor = if application.isIPadApp then 2 else 1 # Retina #scaleFactor = if application.isIPadApp then 2 else 1 # Retina
scaleFactor = 1 scaleFactor = 1
@normalCanvas.add(@webGLCanvas).attr width: newWidth * scaleFactor, height: newHeight * scaleFactor if @options.stayVisible
availableHeight = window.innerHeight
availableHeight -= $('.ad-container').outerHeight()
availableHeight -= $('#game-area').outerHeight() - $('#canvas-wrapper').outerHeight()
scaleFactor = availableHeight / newHeight if availableHeight < newHeight
newWidth *= scaleFactor
newHeight *= scaleFactor
return if newWidth is oldWidth and newHeight is oldHeight and not @options.spectateGame
return if newWidth < 200 or newHeight < 200
@normalCanvas.add(@webGLCanvas).attr width: newWidth, height: newHeight
# Cannot do this to the webGLStage because it does not use scaleX/Y. # Cannot do this to the webGLStage because it does not use scaleX/Y.
# Instead the LayerAdapter scales webGL-enabled layers. # Instead the LayerAdapter scales webGL-enabled layers.

View file

@ -502,6 +502,7 @@
feature5: "Video tutorials" feature5: "Video tutorials"
feature6: "Premium email support" feature6: "Premium email support"
feature7: "Private <strong>Clans</strong>" feature7: "Private <strong>Clans</strong>"
feature8: "<strong>No ads!</strong>"
free: "Free" free: "Free"
month: "month" month: "month"
must_be_logged: "You must be logged in first. Please create an account or log in from the menu above." must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."

View file

@ -77,7 +77,7 @@ module.exports = class User extends CocoModel
# y = a * ln(1/b * (x + c)) + 1 # y = a * ln(1/b * (x + c)) + 1
@levelFromExp: (xp) -> @levelFromExp: (xp) ->
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + c))) + 1 else 1 if xp > 0 then Math.floor(a * Math.log((1 / b) * (xp + c))) + 1 else 1
# x = b * e^((y-1)/a) - c # x = b * e^((y-1)/a) - c
@expForLevel: (level) -> @expForLevel: (level) ->
@ -137,6 +137,16 @@ module.exports = class User extends CocoModel
application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin() application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin()
@announcesActionAudioGroup @announcesActionAudioGroup
getCampaignAdsGroup: ->
return @campaignAdsGroup if @campaignAdsGroup
group = me.get('testGroupNumber') % 2
@campaignAdsGroup = switch group
when 0 then 'no-ads'
when 1 then 'leaderboard-ads'
@campaignAdsGroup = 'no-ads' if me.isAdmin()
application.tracker.identify campaignAdsGroup: @campaignAdsGroup unless me.isAdmin()
@campaignAdsGroup
getHomepageGroup: -> getHomepageGroup: ->
# Only testing on en-US so localization issues are not a factor # Only testing on en-US so localization issues are not a factor
return 'new-home-student' unless _.string.startsWith(me.get('preferredLanguage', true) or 'en-US', 'en') return 'new-home-student' unless _.string.startsWith(me.get('preferredLanguage', true) or 'en-US', 'en')

View file

@ -111,7 +111,7 @@
text-align: center text-align: center
tr tr
td td
padding: 3px padding: 2px
border-width: 0px border-width: 0px
border-top-width: 1px border-top-width: 1px
border-color: rgba(85, 85, 85, 0.1) border-color: rgba(85, 85, 85, 0.1)

View file

@ -628,6 +628,14 @@ $gameControlMargin: 30px
img img
height: 30px height: 30px
.ad-container
width: 100%
height: 90px
text-align: center
.gameplay-container
position: absolute
body.ipad #campaign-view body.ipad #campaign-view
// iPad only supports up to Kithgard Gates for now. // iPad only supports up to Kithgard Gates for now.
.campaign-switch .campaign-switch

View file

@ -66,6 +66,7 @@ $level-resize-transition-time: 0.5s
.level-content .level-content
position: relative position: relative
background-color: black
#canvas-wrapper #canvas-wrapper
top: 50px top: 50px
@ -83,14 +84,14 @@ $level-resize-transition-time: 0.5s
canvas#webgl-surface canvas#webgl-surface
background-color: #333 background-color: #333
z-index: 1 z-index: 1
canvas#normal-surface canvas#normal-surface
z-index: 1 z-index: 1
position: absolute position: absolute
top: 0 top: 0
left: 0 left: 0
pointer-events: none pointer-events: none
canvas#webgl-surface, canvas#normal-surface canvas#webgl-surface, canvas#normal-surface
display: block display: block
z-index: 2 z-index: 2
@ -259,6 +260,10 @@ $level-resize-transition-time: 0.5s
right: 15px right: 15px
font-size: 30px font-size: 30px
.ad-container
width: 100%
height: 90px
text-align: center
html.fullscreen-editor html.fullscreen-editor
#level-view #level-view

View file

@ -68,18 +68,26 @@
span.glyphicon.glyphicon-ok span.glyphicon.glyphicon-ok
tr tr
td.feature-description td.feature-description
span(data-i18n="subscribe.feature6") span(data-i18n="[html]subscribe.feature7")
if !me.isOnPremiumServer() if !me.isOnPremiumServer()
td.free-cell td.free-cell
td.center-ok td.center-ok
span.glyphicon.glyphicon-ok span.glyphicon.glyphicon-ok
tr tr
td.feature-description td.feature-description
span(data-i18n="[html]subscribe.feature7") span(data-i18n="subscribe.feature6")
if !me.isOnPremiumServer() if !me.isOnPremiumServer()
td.free-cell td.free-cell
td.center-ok td.center-ok
span.glyphicon.glyphicon-ok span.glyphicon.glyphicon-ok
if me.getCampaignAdsGroup() === 'leaderboard-ads'
tr
td.feature-description
span(data-i18n="[html]subscribe.feature8")
if !me.isOnPremiumServer()
td.free-cell
td.center-ok
span.glyphicon.glyphicon-ok
#parents-info(data-i18n="subscribe.parents") #parents-info(data-i18n="subscribe.parents")
#payment-methods-info(data-i18n="subscribe.payment_methods") #payment-methods-info(data-i18n="subscribe.payment_methods")

View file

@ -1,165 +1,182 @@
a(href="/").picoctf-hide if view.showAds()
img.small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat") // TODO: loading this multiple times yields script error:
// Uncaught TagError: adsbygoogle.push() error: All ins elements in the DOM with class=adsbygoogle already have ads in them.
.picoctf-show .ad-container
a(href="http://staging.picoctf.com").picoctf-logo if campaign
img.small-nav-logo(src="http://picoctf.com/img/2014_logo_blue2.svg", title="picoCTF home", alt="picoCTF home") script(async, src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js")
a(href="http://codecombat.com").picoctf-powered-by ins.adsbygoogle(style="display:inline-block;width:728px;height:90px", data-ad-client="ca-pub-6640930638193614", data-ad-slot="4924994487")
em.spr powered by script.
img(src="/images/pages/base/logo.png", title="Powered by CodeCombat - Learn how to code by playing a game ", alt="Powered by CodeCombat") (adsbygoogle = window.adsbygoogle || []).push({});
if campaign
.map
.gradient.horizontal-gradient.top-gradient
.gradient.vertical-gradient.right-gradient
.gradient.horizontal-gradient.bottom-gradient
.gradient.vertical-gradient.left-gradient
.map-background(alt="", draggable="false")
each level in levels
if !level.hidden
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (level.next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + (levelStatusMap[level.slug] || ""), data-level-slug=level.slug, data-level-original=level.original, title=i18n(level, 'name') + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
if level.unlocksHero && (!level.purchasedHero || editorMode)
img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png")
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
if level.slug == 'lost-viking'
img.star(src="/file/db/thang.type/5441c3144e9aeb727cc97111/portrait.png")
else if level.requiresSubscription
img.star(src="/images/pages/play/star.png")
if levelStatusMap[level.slug] === 'complete'
img.banner(src="/images/pages/play/level-banner-complete.png")
if levelStatusMap[level.slug] === 'started'
img.banner(src="/images/pages/play/level-banner-started.png")
if levelDifficultyMap[level.slug]
.level-difficulty-banner-text= levelDifficultyMap[level.slug]
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + (levelStatusMap[level.slug] || ""))
.level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
- var playCount = levelPlayCountMap[level.slug]
.progress.progress-striped.active.hide
.progress-bar(style="width: 100%")
- var showsLeaderboard = levelStatusMap[level.slug] === 'complete' && ((level.scoreTypes && level.scoreTypes.length) || ['hero-ladder', 'course-ladder'].indexOf(level.type) !== -1);
div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : "") + (showsLeaderboard ? " shows-leaderboard" : ""))
.level-status
h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
- var description = i18n(level, 'description') || level.description || ""
.level-description!= marked(description, {sanitize: !picoCTF})
if level.disabled
p
span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week.
a.spr(href="/contribute/adventurer")
strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer
span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels.
if level.displayConcepts && level.displayConcepts.length
p
for concept in level.displayConcepts
kbd(data-i18n="concepts." + concept)
if !level.disabled && !level.locked
if playCount && playCount.sessions
.play-counts.hidden
span.spl.spr= playCount.sessions
span(data-i18n="play.players") players
span.spr , #{Math.round(playCount.playtime / 3600)}
span(data-i18n="play.hours_played") hours played
if showsLeaderboard
button.btn.btn-warning.btn.btn-lg.btn-illustrated.view-solutions(data-level-slug=level.slug)
span(data-i18n="leaderboard.scores")
button.btn.btn-success.btn.btn-lg.btn-illustrated.start-level(data-i18n="common.play") Play
if me.get('courseInstances') && me.get('courseInstances').length
.course-version.hidden(data-level-original=level.original)
em(data-i18n="general.or")
| ...
br
button.btn.btn-primary.btn.btn-lg.btn-illustrated
span(data-i18n="play.play_classroom_version") Play Classroom Version
else if level.unlocksHero && !level.purchasedHero
img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png", style="left: #{level.position.x}%; bottom: #{level.position.y}%;")
for adjacentCampaign in adjacentCampaigns
a(href=(editorMode ? "/editor/campaign/" : "/play/") + adjacentCampaign.slug)
span.glyphicon.glyphicon-share-alt.campaign-switch(style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id)
else
.portal
.portals
for campaignSlug in ['dungeon', 'forest', 'desert', 'mountain', 'glacier', 'volcano']
- var campaign = campaigns[campaignSlug];
- var godmode = me.get('permissions', true).indexOf('godmode') != -1;
div(class="campaign #{campaignSlug}" + (campaign ? "" : " silhouette") + (campaign && campaign.locked && !godmode ? " locked" : ""), data-campaign-slug=campaignSlug)
.campaign-label
h2.campaign-name
if campaign
span= i18n(campaign.attributes, 'fullName')
else
span ???
if campaign && campaign.levelsTotal
h3.levels-completed
span= campaign.levelsCompleted
| /
span= campaign.levelsTotal
if campaign && campaign.locked && !godmode
h3.campaign-locked(data-i18n="play.locked") Locked
else if campaign
btn(data-i18n="common.play").btn.btn-illustrated.btn-lg.btn-success.play-button
if campaign && campaign.get('description')
p.campaign-description
span= i18n(campaign.attributes, 'description')
.game-controls.header-font.picoctf-hide
button.btn.poll.hidden(data-i18n="[title]play.poll")
a.btn.clans(href="/clans", data-i18n="[title]clans.clans")
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes")
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
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
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()
// button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
if me.get('anonymous', true)
button.btn.settings(data-toggle='coco-modal', data-target='core/CreateAccountModal', data-i18n="[title]play.settings")
.user-status.header-font.picoctf-hide
.user-status-line
span.gem.gem-30
span#gems-count.spr= me.gems()
span.level-indicator(data-i18n="general.player_level")
span.player-level.spr= me.level()
span.player-hero-icon
if me.get('anonymous')
span.player-name.spr(data-i18n="play.anonymous") Anonymous Player
button.btn.btn-illustrated.login-button.btn-warning(data-i18n="login.log_in")
button.btn.btn-illustrated.signup-button.btn-danger(data-i18n="signup.sign_up")
else else
a(data-toggle="coco-modal", data-target="play/modal/PlayAccountModal").player-name.spr= me.get('name') script(async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js")
button#logout-button.btn.btn-illustrated.btn-warning(data-i18n="login.log_out") Log Out ins.adsbygoogle(style="display:inline-block;width:728px;height:90px", data-ad-client="ca-pub-6640930638193614", data-ad-slot="4469166082")
if me.isPremium() script.
button.btn.btn-illustrated.btn-primary(data-i18n="nav.contact", data-toggle="coco-modal", data-target="core/ContactModal") Contact (adsbygoogle = window.adsbygoogle || []).push({});
button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#volume-button(data-i18n="[title]play.adjust_volume", title="Adjust volume") // TODO: .gameplay-container causes world map buttons to briefly appear in top left of screen
.glyphicon.glyphicon-volume-off .gameplay-container
.glyphicon.glyphicon-volume-down a(href="/").picoctf-hide
.glyphicon.glyphicon-volume-up img.small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
if campaign && !editorMode .picoctf-show
button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#back-button(data-i18n="[title]resources.campaigns", title="Campaigns") a(href="http://staging.picoctf.com").picoctf-logo
.glyphicon.glyphicon-globe img.small-nav-logo(src="http://picoctf.com/img/2014_logo_blue2.svg", title="picoCTF home", alt="picoCTF home")
a(href="http://codecombat.com").picoctf-powered-by
em.spr powered by
img(src="/images/pages/base/logo.png", title="Powered by CodeCombat - Learn how to code by playing a game ", alt="Powered by CodeCombat")
if editorMode if campaign
button.btn.btn-lg.btn-inverse.campaign-control-button#clear-storage-button(data-i18n="[title]editor.clear_storage", title="Clear your local changes") .map
.glyphicon.glyphicon-refresh .gradient.horizontal-gradient.top-gradient
.gradient.vertical-gradient.right-gradient
.gradient.horizontal-gradient.bottom-gradient
.gradient.vertical-gradient.left-gradient
.map-background(alt="", draggable="false")
if campaign && campaign.loaded each level in levels
h1#campaign-status.picoctf-hide if !level.hidden
.campaign-status-background div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (level.next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + (levelStatusMap[level.slug] || ""), data-level-slug=level.slug, data-level-original=level.original, title=i18n(level, 'name') + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
.campaign-name if level.unlocksHero && (!level.purchasedHero || editorMode)
- var fullName = i18n(campaign.attributes, 'fullName') img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png")
if (me.get('preferredLanguage', true) || 'en-US').split('-')[0] == 'en' || fullName != campaign.get('fullName') a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
// We have a translation. if level.slug == 'lost-viking'
span= fullName img.star(src="/file/db/thang.type/5441c3144e9aeb727cc97111/portrait.png")
.levels-completed else if level.requiresSubscription
span= levelsCompleted img.star(src="/images/pages/play/star.png")
| / if levelStatusMap[level.slug] === 'complete'
span= levelsTotal img.banner(src="/images/pages/play/level-banner-complete.png")
if levelStatusMap[level.slug] === 'started'
img.banner(src="/images/pages/play/level-banner-started.png")
if levelDifficultyMap[level.slug]
.level-difficulty-banner-text= levelDifficultyMap[level.slug]
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + (levelStatusMap[level.slug] || ""))
.level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
- var playCount = levelPlayCountMap[level.slug]
.progress.progress-striped.active.hide
.progress-bar(style="width: 100%")
- var showsLeaderboard = levelStatusMap[level.slug] === 'complete' && ((level.scoreTypes && level.scoreTypes.length) || ['hero-ladder', 'course-ladder'].indexOf(level.type) !== -1);
div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : "") + (showsLeaderboard ? " shows-leaderboard" : ""))
.level-status
h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
- var description = i18n(level, 'description') || level.description || ""
.level-description!= marked(description, {sanitize: !picoCTF})
if level.disabled
p
span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week.
a.spr(href="/contribute/adventurer")
strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer
span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels.
if level.displayConcepts && level.displayConcepts.length
p
for concept in level.displayConcepts
kbd(data-i18n="concepts." + concept)
if !level.disabled && !level.locked
if playCount && playCount.sessions
.play-counts.hidden
span.spl.spr= playCount.sessions
span(data-i18n="play.players") players
span.spr , #{Math.round(playCount.playtime / 3600)}
span(data-i18n="play.hours_played") hours played
if showsLeaderboard
button.btn.btn-warning.btn.btn-lg.btn-illustrated.view-solutions(data-level-slug=level.slug)
span(data-i18n="leaderboard.scores")
button.btn.btn-success.btn.btn-lg.btn-illustrated.start-level(data-i18n="common.play") Play
if me.get('courseInstances') && me.get('courseInstances').length
.course-version.hidden(data-level-original=level.original)
em(data-i18n="general.or")
| ...
br
button.btn.btn-primary.btn.btn-lg.btn-illustrated
span(data-i18n="play.play_classroom_version") Play Classroom Version
else if level.unlocksHero && !level.purchasedHero
img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png", style="left: #{level.position.x}%; bottom: #{level.position.y}%;")
for adjacentCampaign in adjacentCampaigns
a(href=(editorMode ? "/editor/campaign/" : "/play/") + adjacentCampaign.slug)
span.glyphicon.glyphicon-share-alt.campaign-switch(style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id)
else
.portal
.portals
for campaignSlug in ['dungeon', 'forest', 'desert', 'mountain', 'glacier', 'volcano']
- var campaign = campaigns[campaignSlug];
- var godmode = me.get('permissions', true).indexOf('godmode') != -1;
div(class="campaign #{campaignSlug}" + (campaign ? "" : " silhouette") + (campaign && campaign.locked && !godmode ? " locked" : ""), data-campaign-slug=campaignSlug)
.campaign-label
h2.campaign-name
if campaign
span= i18n(campaign.attributes, 'fullName')
else
span ???
if campaign && campaign.levelsTotal
h3.levels-completed
span= campaign.levelsCompleted
| /
span= campaign.levelsTotal
if campaign && campaign.locked && !godmode
h3.campaign-locked(data-i18n="play.locked") Locked
else if campaign
btn(data-i18n="common.play").btn.btn-illustrated.btn-lg.btn-success.play-button
if campaign && campaign.get('description')
p.campaign-description
span= i18n(campaign.attributes, 'description')
.game-controls.header-font.picoctf-hide
button.btn.poll.hidden(data-i18n="[title]play.poll")
a.btn.clans(href="/clans", data-i18n="[title]clans.clans")
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes")
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
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
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()
// button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
if me.get('anonymous', true)
button.btn.settings(data-toggle='coco-modal', data-target='core/CreateAccountModal', data-i18n="[title]play.settings")
.user-status.header-font.picoctf-hide
.user-status-line
span.gem.gem-30
span#gems-count.spr= me.gems()
span.level-indicator(data-i18n="general.player_level")
span.player-level.spr= me.level()
span.player-hero-icon
if me.get('anonymous')
span.player-name.spr(data-i18n="play.anonymous") Anonymous Player
button.btn.btn-illustrated.login-button.btn-warning(data-i18n="login.log_in")
button.btn.btn-illustrated.signup-button.btn-danger(data-i18n="signup.sign_up")
else
a(data-toggle="coco-modal", data-target="play/modal/PlayAccountModal").player-name.spr= me.get('name')
button#logout-button.btn.btn-illustrated.btn-warning(data-i18n="login.log_out") Log Out
if me.isPremium()
button.btn.btn-illustrated.btn-primary(data-i18n="nav.contact", data-toggle="coco-modal", data-target="core/ContactModal") Contact
button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#volume-button(data-i18n="[title]play.adjust_volume", title="Adjust volume")
.glyphicon.glyphicon-volume-off
.glyphicon.glyphicon-volume-down
.glyphicon.glyphicon-volume-up
if campaign && !editorMode
button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#back-button(data-i18n="[title]resources.campaigns", title="Campaigns")
.glyphicon.glyphicon-globe
if editorMode
button.btn.btn-lg.btn-inverse.campaign-control-button#clear-storage-button(data-i18n="[title]editor.clear_storage", title="Clear your local changes")
.glyphicon.glyphicon-refresh
if campaign && campaign.loaded
h1#campaign-status.picoctf-hide
.campaign-status-background
.campaign-name
- var fullName = i18n(campaign.attributes, 'fullName')
if (me.get('preferredLanguage', true) || 'en-US').split('-')[0] == 'en' || fullName != campaign.get('fullName')
// We have a translation.
span= fullName
.levels-completed
span= levelsCompleted
| /
span= levelsTotal

View file

@ -1,49 +1,59 @@
#level-loading-view if view.showAds()
// TODO: loading this multiple times yields script error:
// Uncaught TagError: adsbygoogle.push() error: All ins elements in the DOM with class=adsbygoogle already have ads in them.
.ad-container
script(async, src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js")
ins.adsbygoogle(style="display:inline-block;width:728px;height:90px", data-ad-client="ca-pub-6640930638193614", data-ad-slot="5527096883")
script.
(adsbygoogle = window.adsbygoogle || []).push({});
.level-content .game-container
#control-bar-view #level-loading-view
#fullscreen-editor-background-screen(title="Click to minimize the code editor") .level-content
#control-bar-view
#code-area #fullscreen-editor-background-screen(title="Click to minimize the code editor")
#code-area-gradient.gradient
#tome-view
#game-area #code-area
#code-area-gradient.gradient
#tome-view
#canvas-wrapper #game-area
canvas(width=924, height=589)#webgl-surface
canvas(width=924, height=589)#normal-surface
#ascii-surface
#canvas-left-gradient.gradient
#canvas-top-gradient.gradient
#goals-view
#level-flags-view #canvas-wrapper
canvas(width=924, height=589)#webgl-surface
canvas(width=924, height=589)#normal-surface
#ascii-surface
#canvas-left-gradient.gradient
#canvas-top-gradient.gradient
#goals-view
#gold-view #level-flags-view
#problem-alert-view #gold-view
#level-chat-view #problem-alert-view
#multiplayer-status-view #level-chat-view
#duel-stats-view #multiplayer-status-view
#playback-view #duel-stats-view
#thang-hud #playback-view
#level-dialogue-view #thang-hud
button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip #level-dialogue-view
#level-footer-shadow button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip
#level-footer-background
if !me.get('anonymous') #level-footer-shadow
#play-footer(class=me.isPremium() ? "premium" : "") #level-footer-background
p(class='footer-link-text').picoctf-hide
a.contact-link(title='Send CodeCombat a message', tabindex=-1, data-i18n="nav.contact") Contact if !me.get('anonymous')
#play-footer(class=me.isPremium() ? "premium" : "")
p(class='footer-link-text').picoctf-hide
a.contact-link(title='Send CodeCombat a message', tabindex=-1, data-i18n="nav.contact") Contact

View file

@ -22,7 +22,7 @@
.product .product
h4.subscription-gem-amount x{{gems}} / mo h4.subscription-gem-amount x{{gems}} / mo
h3(data-i18n="account.subscription") h3(data-i18n="account.subscription")
if me.isPremium() if me.hasSubscription()
button.disabled.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success button.disabled.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success
| ✓ | ✓
span(data-i18n="account.subscribed") span(data-i18n="account.subscribed")

View file

@ -266,6 +266,11 @@ module.exports = class CampaignView extends RootView
authModal.mode = 'signup' authModal.mode = 'signup'
@openModalView authModal @openModalView authModal
showAds: ->
if application.isProduction() && !me.isPremium() && !me.isTeacher() && !window.serverConfig.picoCTF
return me.getCampaignAdsGroup() is 'leaderboard-ads'
false
annotateLevel: (level) -> annotateLevel: (level) ->
level.position ?= { x: 10, y: 10 } level.position ?= { x: 10, y: 10 }
level.locked = not me.ownsLevel level.original level.locked = not me.ownsLevel level.original
@ -554,6 +559,7 @@ module.exports = class CampaignView extends RootView
aspectRatio = mapWidth / mapHeight aspectRatio = mapWidth / mapHeight
pageWidth = @$el.width() pageWidth = @$el.width()
pageHeight = @$el.height() pageHeight = @$el.height()
pageHeight -= adContainerHeight if adContainerHeight = $('.ad-container').outerHeight()
widthRatio = pageWidth / mapWidth widthRatio = pageWidth / mapWidth
heightRatio = pageHeight / mapHeight heightRatio = pageHeight / mapHeight
# Make sure we can see the whole map, fading to background in one dimension. # Make sure we can see the whole map, fading to background in one dimension.

View file

@ -148,6 +148,13 @@ module.exports = class PlayLevelView extends RootView
application.tracker?.trackEvent 'Finished Level Load', category: 'Play Level', label: @levelID, level: @levelID, loadDuration: @loadDuration application.tracker?.trackEvent 'Finished Level Load', category: 'Play Level', label: @levelID, level: @levelID, loadDuration: @loadDuration
application.tracker?.trackTiming @loadDuration, 'Level Load Time', @levelID, @levelID application.tracker?.trackTiming @loadDuration, 'Level Load Time', @levelID, @levelID
isCourseMode: -> @courseID and @courseInstanceID
showAds: ->
if application.isProduction() && !me.isPremium() && !me.isTeacher() && !window.serverConfig.picoCTF && !@isCourseMode()
return me.getCampaignAdsGroup() is 'leaderboard-ads'
false
# CocoView overridden methods ############################################### # CocoView overridden methods ###############################################
getRenderData: -> getRenderData: ->
@ -326,7 +333,13 @@ module.exports = class PlayLevelView extends RootView
initSurface: -> initSurface: ->
webGLSurface = $('canvas#webgl-surface', @$el) webGLSurface = $('canvas#webgl-surface', @$el)
normalSurface = $('canvas#normal-surface', @$el) normalSurface = $('canvas#normal-surface', @$el)
@surface = new Surface(@world, normalSurface, webGLSurface, thangTypes: @supermodel.getModels(ThangType), observing: @observing, playerNames: @findPlayerNames(), levelType: @level.get('type', true)) surfaceOptions =
thangTypes: @supermodel.getModels(ThangType)
observing: @observing
playerNames: @findPlayerNames()
levelType: @level.get('type', true)
stayVisible: @showAds()
@surface = new Surface(@world, normalSurface, webGLSurface, surfaceOptions)
worldBounds = @world.getBounds() worldBounds = @world.getBounds()
bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}] bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}]
@surface.camera.setBounds(bounds) @surface.camera.setBounds(bounds)
@ -499,7 +512,8 @@ module.exports = class PlayLevelView extends RootView
break break
Backbone.Mediator.publish 'tome:cast-spell', {} Backbone.Mediator.publish 'tome:cast-spell', {}
onWindowResize: (e) => @endHighlight() onWindowResize: (e) =>
@endHighlight()
onDisableControls: (e) -> onDisableControls: (e) ->
return if e.controls and not ('level' in e.controls) return if e.controls and not ('level' in e.controls)
@ -535,7 +549,7 @@ module.exports = class PlayLevelView extends RootView
@endHighlight() @endHighlight()
options = {level: @level, supermodel: @supermodel, session: @session, hasReceivedMemoryWarning: @hasReceivedMemoryWarning, courseID: @courseID, courseInstanceID: @courseInstanceID, world: @world} options = {level: @level, supermodel: @supermodel, session: @session, hasReceivedMemoryWarning: @hasReceivedMemoryWarning, courseID: @courseID, courseInstanceID: @courseInstanceID, world: @world}
ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder'] then HeroVictoryModal else VictoryModal ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder'] then HeroVictoryModal else VictoryModal
ModalClass = CourseVictoryModal if @courseID and @courseInstanceID ModalClass = CourseVictoryModal if @isCourseMode()
ModalClass = PicoCTFVictoryModal if window.serverConfig.picoCTF ModalClass = PicoCTFVictoryModal if window.serverConfig.picoCTF
victoryModal = new ModalClass(options) victoryModal = new ModalClass(options)
@openModalView(victoryModal) @openModalView(victoryModal)