From 255ebbc048effcb97d2d92cc1de81961acb5b06b Mon Sep 17 00:00:00 2001 From: Matt Lott <mattlott@live.com> Date: Tue, 15 Mar 2016 15:51:59 -0700 Subject: [PATCH] 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 #3491 --- app/lib/surface/Surface.coffee | 15 +- app/locale/en.coffee | 1 + app/models/User.coffee | 12 +- app/styles/modal/subscribe-modal.sass | 2 +- app/styles/play/campaign-view.sass | 8 + app/styles/play/level.sass | 9 +- app/templates/core/subscribe-modal.jade | 12 +- app/templates/play/campaign-view.jade | 337 ++++++++++--------- app/templates/play/level.jade | 72 ++-- app/templates/play/modal/buy-gems-modal.jade | 2 +- app/views/play/CampaignView.coffee | 6 + app/views/play/level/PlayLevelView.coffee | 20 +- 12 files changed, 292 insertions(+), 204 deletions(-) diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 0c0d8dd64..51b2e2789 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -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. diff --git a/app/locale/en.coffee b/app/locale/en.coffee index e1b26dd8b..6f12fe859 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -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." diff --git a/app/models/User.coffee b/app/models/User.coffee index 356fa46fe..8ea24ea5c 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -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') diff --git a/app/styles/modal/subscribe-modal.sass b/app/styles/modal/subscribe-modal.sass index 1d588e8f2..4a9efb066 100644 --- a/app/styles/modal/subscribe-modal.sass +++ b/app/styles/modal/subscribe-modal.sass @@ -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) diff --git a/app/styles/play/campaign-view.sass b/app/styles/play/campaign-view.sass index 8fbd22a7f..fd2bddcd0 100644 --- a/app/styles/play/campaign-view.sass +++ b/app/styles/play/campaign-view.sass @@ -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 diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass index ad2a515c5..fb3f236bc 100644 --- a/app/styles/play/level.sass +++ b/app/styles/play/level.sass @@ -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 diff --git a/app/templates/core/subscribe-modal.jade b/app/templates/core/subscribe-modal.jade index fec7039f7..b0eca0241 100644 --- a/app/templates/core/subscribe-modal.jade +++ b/app/templates/core/subscribe-modal.jade @@ -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") diff --git a/app/templates/play/campaign-view.jade b/app/templates/play/campaign-view.jade index b2b0aa87b..96b3bfe32 100644 --- a/app/templates/play/campaign-view.jade +++ b/app/templates/play/campaign-view.jade @@ -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 diff --git a/app/templates/play/level.jade b/app/templates/play/level.jade index 02bc0c9b7..5bf5a8efe 100644 --- a/app/templates/play/level.jade +++ b/app/templates/play/level.jade @@ -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 diff --git a/app/templates/play/modal/buy-gems-modal.jade b/app/templates/play/modal/buy-gems-modal.jade index c67c31ca0..261fa9e41 100644 --- a/app/templates/play/modal/buy-gems-modal.jade +++ b/app/templates/play/modal/buy-gems-modal.jade @@ -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") diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index f2fbfee90..ffb0da0b6 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -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. diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index b9a4590e1..71c134ca8 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -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)