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
newHeight = newWidth / aspectRatio
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 = 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.
# Instead the LayerAdapter scales webGL-enabled layers.

View file

@ -502,6 +502,7 @@
feature5: "Video tutorials"
feature6: "Premium email support"
feature7: "Private <strong>Clans</strong>"
feature8: "<strong>No ads!</strong>"
free: "Free"
month: "month"
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
@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
@expForLevel: (level) ->
@ -137,6 +137,16 @@ module.exports = class User extends CocoModel
application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin()
@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: ->
# 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')

View file

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

View file

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

View file

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

View file

@ -68,18 +68,26 @@
span.glyphicon.glyphicon-ok
tr
td.feature-description
span(data-i18n="subscribe.feature6")
span(data-i18n="[html]subscribe.feature7")
if !me.isOnPremiumServer()
td.free-cell
td.center-ok
span.glyphicon.glyphicon-ok
tr
td.feature-description
span(data-i18n="[html]subscribe.feature7")
span(data-i18n="subscribe.feature6")
if !me.isOnPremiumServer()
td.free-cell
td.center-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")
#payment-methods-info(data-i18n="subscribe.payment_methods")

View file

@ -1,165 +1,182 @@
a(href="/").picoctf-hide
img.small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
.picoctf-show
a(href="http://staging.picoctf.com").picoctf-logo
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 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")
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
if campaign
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="4924994487")
script.
(adsbygoogle = window.adsbygoogle || []).push({});
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
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="4469166082")
script.
(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")
.glyphicon.glyphicon-volume-off
.glyphicon.glyphicon-volume-down
.glyphicon.glyphicon-volume-up
// TODO: .gameplay-container causes world map buttons to briefly appear in top left of screen
.gameplay-container
a(href="/").picoctf-hide
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
button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#back-button(data-i18n="[title]resources.campaigns", title="Campaigns")
.glyphicon.glyphicon-globe
.picoctf-show
a(href="http://staging.picoctf.com").picoctf-logo
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
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
.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")
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
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
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
#control-bar-view
.game-container
#level-loading-view
#fullscreen-editor-background-screen(title="Click to minimize the code editor")
.level-content
#control-bar-view
#code-area
#code-area-gradient.gradient
#tome-view
#fullscreen-editor-background-screen(title="Click to minimize the code editor")
#game-area
#code-area
#code-area-gradient.gradient
#tome-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
#game-area
#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
#level-footer-background
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
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
#level-footer-shadow
#level-footer-background
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
h4.subscription-gem-amount x{{gems}} / mo
h3(data-i18n="account.subscription")
if me.isPremium()
if me.hasSubscription()
button.disabled.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success
| ✓
span(data-i18n="account.subscribed")

View file

@ -266,6 +266,11 @@ module.exports = class CampaignView extends RootView
authModal.mode = 'signup'
@openModalView authModal
showAds: ->
if application.isProduction() && !me.isPremium() && !me.isTeacher() && !window.serverConfig.picoCTF
return me.getCampaignAdsGroup() is 'leaderboard-ads'
false
annotateLevel: (level) ->
level.position ?= { x: 10, y: 10 }
level.locked = not me.ownsLevel level.original
@ -554,6 +559,7 @@ module.exports = class CampaignView extends RootView
aspectRatio = mapWidth / mapHeight
pageWidth = @$el.width()
pageHeight = @$el.height()
pageHeight -= adContainerHeight if adContainerHeight = $('.ad-container').outerHeight()
widthRatio = pageWidth / mapWidth
heightRatio = pageHeight / mapHeight
# 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?.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 ###############################################
getRenderData: ->
@ -326,7 +333,13 @@ module.exports = class PlayLevelView extends RootView
initSurface: ->
webGLSurface = $('canvas#webgl-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()
bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}]
@surface.camera.setBounds(bounds)
@ -499,7 +512,8 @@ module.exports = class PlayLevelView extends RootView
break
Backbone.Mediator.publish 'tome:cast-spell', {}
onWindowResize: (e) => @endHighlight()
onWindowResize: (e) =>
@endHighlight()
onDisableControls: (e) ->
return if e.controls and not ('level' in e.controls)
@ -535,7 +549,7 @@ module.exports = class PlayLevelView extends RootView
@endHighlight()
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 = CourseVictoryModal if @courseID and @courseInstanceID
ModalClass = CourseVictoryModal if @isCourseMode()
ModalClass = PicoCTFVictoryModal if window.serverConfig.picoCTF
victoryModal = new ModalClass(options)
@openModalView(victoryModal)