mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-05-02 00:43:34 -04:00
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
This commit is contained in:
parent
1bab6cee88
commit
255ebbc048
12 changed files with 292 additions and 204 deletions
app
lib/surface
locale
models
styles
templates
views/play
|
@ -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.
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue