mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 16:17:57 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
801eb0a9f7
22 changed files with 196 additions and 46 deletions
BIN
app/assets/images/level/loading_left_wing.png
Normal file
BIN
app/assets/images/level/loading_left_wing.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 410 KiB |
BIN
app/assets/images/level/loading_right_wing.png
Normal file
BIN
app/assets/images/level/loading_right_wing.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 411 KiB |
|
@ -205,6 +205,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
.to({alpha: 0.6, scaleY: @options.camera.y2x, scaleX: 1}, 100, createjs.Ease.circOut)
|
||||
.to({alpha: 0, scaleY: 0, scaleX: 0}, 700, createjs.Ease.circIn)
|
||||
.call =>
|
||||
return if @destroyed
|
||||
@options.groundLayer.removeChild circle
|
||||
delete @handledAoEs[event]
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports = class CoordinateDisplay extends createjs.Container
|
|||
|
||||
build: ->
|
||||
@mouseEnabled = @mouseChildren = false
|
||||
@addChild @label = new createjs.Text("", "20px Arial", "#003300")
|
||||
@addChild @label = new createjs.Text("", "40px Arial", "#003300")
|
||||
@label.name = 'position text'
|
||||
@label.shadow = new createjs.Shadow("#FFFFFF", 1, 1, 0)
|
||||
|
||||
|
|
|
@ -25,10 +25,10 @@ module.exports = class DebugDisplay extends createjs.Container
|
|||
|
||||
build: ->
|
||||
@mouseEnabled = @mouseChildren = false
|
||||
@addChild @frameText = new createjs.Text "...", "20px Arial", "#FFF"
|
||||
@addChild @frameText = new createjs.Text "...", "40px Arial", "#FFF"
|
||||
@frameText.name = 'frame text'
|
||||
@frameText.x = @canvasWidth - 50
|
||||
@frameText.y = @canvasHeight - 25
|
||||
@frameText.x = @canvasWidth - 100
|
||||
@frameText.y = @canvasHeight - 50
|
||||
@frameText.alpha = 0.5
|
||||
|
||||
updateFrame: (currentFrame) ->
|
||||
|
@ -42,4 +42,4 @@ module.exports = class DebugDisplay extends createjs.Container
|
|||
@framesRenderedThisSecond = 0
|
||||
|
||||
@frameText.text = Math.round(currentFrame) + (if @fps? then " - " + @fps + ' fps' else '')
|
||||
@frameText.x = @canvasWidth - @frameText.getMeasuredWidth() - 10
|
||||
@frameText.x = @canvasWidth - @frameText.getMeasuredWidth() - 20
|
||||
|
|
|
@ -59,7 +59,7 @@ module.exports = class Label extends CocoClass
|
|||
o.fontWeight = {D: "bold", S: "bold", N: "bold"}[st]
|
||||
o.shadow = {D: false, S: true, N: true}[st]
|
||||
o.shadowColor = {D: "#FFF", S: "#000", N: "#FFF"}[st]
|
||||
o.fontSize = {D: 25, S: 12, N: 12}[st]
|
||||
o.fontSize = {D: 50, S: 24, N: 24}[st]
|
||||
fontFamily = {D: "Arial", S: "Arial", N: "Arial"}[st]
|
||||
o.fontDescriptor = "#{o.fontWeight} #{o.fontSize}px #{fontFamily}"
|
||||
o.fontColor = {D: "#000", S: "#FFF", N: "#00a"}[st]
|
||||
|
|
|
@ -81,7 +81,7 @@ module.exports = class Mark extends CocoClass
|
|||
shape.graphics.endStroke()
|
||||
shape.graphics.endFill()
|
||||
|
||||
text = new createjs.Text "" + index, "20px Arial", color.replace('0.5', '1')
|
||||
text = new createjs.Text "" + index, "40px Arial", color.replace('0.5', '1')
|
||||
text.regX = text.getMeasuredWidth() / 2
|
||||
text.regY = text.getMeasuredHeight() / 2
|
||||
text.shadow = new createjs.Shadow("#000000", 1, 1, 0)
|
||||
|
|
|
@ -110,7 +110,13 @@ module.exports = class SpriteBoss extends CocoClass
|
|||
createOpponentWizard: (opponent) ->
|
||||
# TODO: colorize name and cloud by team, colorize wizard by user's color config, level-specific wizard spawn points
|
||||
sprite = @createWizardSprite thangID: opponent.id, name: opponent.name
|
||||
if not opponent.levelSlug or opponent.levelSlug is "brawlwood"
|
||||
sprite.targetPos = if opponent.team is 'ogres' then {x: 52, y: 52} else {x: 28, y: 28}
|
||||
else if opponent.levelSlug is "dungeon-arena"
|
||||
sprite.targetPos = if opponent.team is 'ogres' then {x:72, y: 39} else {x: 9, y:39}
|
||||
else
|
||||
sprite.targetPos = if opponent.team is 'ogres' then {x:52, y: 28} else {x: 20, y:28}
|
||||
|
||||
|
||||
createWizardSprite: (options) ->
|
||||
sprite = new WizardSprite @thangTypeFor("Wizard"), @createSpriteOptions(options)
|
||||
|
@ -294,7 +300,7 @@ module.exports = class SpriteBoss extends CocoClass
|
|||
@willSelectThang = [thangID, null]
|
||||
@updateTarget()
|
||||
return unless @selectionMark
|
||||
@selectedSprite = null unless @selectedSprite?.thang
|
||||
@selectedSprite = null if @selectedSprite and (@selectedSprite.destroyed or not @selectedSprite.thang)
|
||||
@selectionMark.toggle @selectedSprite?
|
||||
@selectionMark.setSprite @selectedSprite
|
||||
@selectionMark.update()
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
html
|
||||
background-color: #2f261d
|
||||
|
||||
html, body
|
||||
// For level loading view wings
|
||||
overflow-x: hidden
|
||||
|
||||
// https://github.com/twbs/bootstrap/issues/9237 -- need a version that's not !important
|
||||
.secret
|
||||
display: none
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
display: block
|
||||
z-index: 1
|
||||
|
||||
|
||||
//max-width: 1680px // guideline, but for now let's let it stretch out
|
||||
min-width: 1024px
|
||||
position: relative
|
||||
|
||||
|
|
65
app/styles/play/level/loading.sass
Normal file
65
app/styles/play/level/loading.sass
Normal file
|
@ -0,0 +1,65 @@
|
|||
@import "app/styles/bootstrap/mixins"
|
||||
@import "app/styles/mixins"
|
||||
|
||||
@mixin sky-background($url: '', $backgroundPosition: left)
|
||||
$top: #95D9EF
|
||||
$mid: #FFFFFF
|
||||
$bot: #8EC643
|
||||
$stop: 99.6%
|
||||
background: $mid
|
||||
background-image: url($url) // fallback
|
||||
background-image: url($url), -webkit-linear-gradient(top, $top, $mid $stop, $bot)
|
||||
background-image: url($url), -ms-linear-gradient(top, $top, $mid $stop, $bot)
|
||||
background-image: url($url), linear-gradient(to bottom, $top, $mid $stop, $bot)
|
||||
background-repeat: no-repeat
|
||||
background-position: top $backgroundPosition
|
||||
|
||||
#level-loading-view
|
||||
color: blue
|
||||
width: 100%
|
||||
height: 100%
|
||||
position: absolute
|
||||
z-index: 20
|
||||
$UNVEIL_TIME: 1.2s
|
||||
pointer-events: none
|
||||
|
||||
.loading-details
|
||||
position: absolute
|
||||
top: 20px
|
||||
left: 50%
|
||||
$WIDTH: 1000px
|
||||
width: $WIDTH
|
||||
margin-left: (-$WIDTH / 2)
|
||||
z-index: 100
|
||||
background-color: rgba(220, 255, 230, 0.5)
|
||||
border-radius: 30px
|
||||
padding: 10px
|
||||
text-align: center
|
||||
// http://matthewlein.com/ceaser/ Bounce down a bit, then snap up.
|
||||
-webkit-transition: top $UNVEIL_TIME cubic-bezier(0.285, 0, 0.670, 0)
|
||||
-webkit-transition: top $UNVEIL_TIME cubic-bezier(0.285, -0.595, 0.670, -0.600)
|
||||
-moz-transition: top $UNVEIL_TIME cubic-bezier(0.285, -0.595, 0.670, -0.600)
|
||||
-o-transition: top $UNVEIL_TIME cubic-bezier(0.285, -0.595, 0.670, -0.600)
|
||||
transition: top $UNVEIL_TIME cubic-bezier(0.285, -0.595, 0.670, -0.600)
|
||||
|
||||
.load-progress
|
||||
width: 100%
|
||||
|
||||
.progress-bar
|
||||
width: 1%
|
||||
transition-duration: 1.2s
|
||||
|
||||
.left-wing, .right-wing
|
||||
width: 100%
|
||||
height: 100%
|
||||
position: absolute
|
||||
|
||||
.left-wing
|
||||
@include sky-background('/images/level/loading_left_wing.png', right)
|
||||
left: -50%
|
||||
transition: all $UNVEIL_TIME ease
|
||||
|
||||
.right-wing
|
||||
@include sky-background('/images/level/loading_right_wing.png', left)
|
||||
right: -50%
|
||||
transition: all $UNVEIL_TIME ease
|
|
@ -1,7 +1,6 @@
|
|||
@import "app/styles/bootstrap/mixins"
|
||||
@import "app/styles/mixins"
|
||||
|
||||
|
||||
#spectate-level-view
|
||||
#playback-view
|
||||
width: 100%
|
||||
|
@ -24,12 +23,16 @@
|
|||
line-height: 15px
|
||||
left: 0
|
||||
|
||||
|
||||
max-width: 1920px
|
||||
margin: 0 auto
|
||||
@include user-select(none)
|
||||
|
||||
#level-loading-view
|
||||
max-height: 1284px
|
||||
|
||||
.level-content
|
||||
position: relative
|
||||
margin: 0px auto
|
||||
|
||||
#canvas-wrapper
|
||||
height: 100%
|
||||
|
@ -38,6 +41,8 @@
|
|||
|
||||
canvas#surface
|
||||
background-color: #ddd
|
||||
max-height: 93%
|
||||
max-height: -webkit-calc(100% - 60px)
|
||||
max-height: calc(100% - 60px)
|
||||
height: auto
|
||||
max-width: 100%
|
||||
|
@ -46,7 +51,6 @@
|
|||
margin: 0 auto
|
||||
|
||||
|
||||
//max-width: 1680px // guideline, but for now let's let it stretch out
|
||||
min-width: 1024px
|
||||
position: relative
|
||||
#thang-hud
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
div.alert.alert-info
|
||||
strong Enter
|
||||
| to confirm
|
||||
canvas(width=924, height=590)
|
||||
canvas(width=1920, height=1224)
|
||||
.modal-footer
|
||||
a.btn.btn-primary#done-button Done
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
.world-container.thangs-column
|
||||
h3(data-i18n="editor.level_tab_thangs_conditions") Starting Conditions
|
||||
#canvas-wrapper
|
||||
canvas(width=924, height=589)#surface
|
||||
canvas(width=1920, height=1224)#surface
|
||||
#canvas-left-gradient.gradient
|
||||
#canvas-top-gradient.gradient
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#level-loading-view
|
||||
|
||||
.level-content
|
||||
|
||||
#control-bar-view
|
||||
|
@ -7,7 +9,7 @@
|
|||
#tome-view
|
||||
|
||||
#canvas-wrapper
|
||||
canvas(width=924, height=589)#surface
|
||||
canvas(width=1920, height=1224)#surface
|
||||
#canvas-left-gradient.gradient
|
||||
#canvas-top-gradient.gradient
|
||||
|
||||
|
|
13
app/templates/play/level/level_loading.jade
Normal file
13
app/templates/play/level/level_loading.jade
Normal file
|
@ -0,0 +1,13 @@
|
|||
.left-wing
|
||||
|
||||
.right-wing
|
||||
|
||||
.loading-details
|
||||
|
||||
h2(data-i18n='play_level.loading_level') Loading Level
|
||||
|
||||
.load-progress
|
||||
.progress.progress-striped.active
|
||||
.progress-bar.progress-bar-success
|
||||
|
||||
h4 Tip: you can shift+click a position on the map to insert it into the spell editor.
|
|
@ -1,7 +1,9 @@
|
|||
#level-loading-view
|
||||
|
||||
.level-content
|
||||
#control-bar-view
|
||||
#canvas-wrapper
|
||||
canvas(width=1848, height=1178)#surface
|
||||
canvas(width=1920, height=1224)#surface
|
||||
#canvas-left-gradient.gradient
|
||||
#canvas-top-gradient.gradient
|
||||
#gold-view.secret.expanded
|
||||
|
|
40
app/views/play/level/level_loading_view.coffee
Normal file
40
app/views/play/level/level_loading_view.coffee
Normal file
|
@ -0,0 +1,40 @@
|
|||
View = require 'views/kinds/CocoView'
|
||||
template = require 'templates/play/level/level_loading'
|
||||
|
||||
module.exports = class LevelLoadingView extends View
|
||||
id: "level-loading-view"
|
||||
template: template
|
||||
|
||||
subscriptions:
|
||||
'level-loader:progress-changed': 'onLevelLoaderProgressChanged'
|
||||
|
||||
onLevelLoaderProgressChanged: (e) ->
|
||||
@progress = e.progress
|
||||
@updateProgressBar()
|
||||
|
||||
updateProgressBar: ->
|
||||
#@text.text = "BUILDING" if @progress is 1
|
||||
@$el.find('.progress-bar').css('width', (100 * @progress) + '%')
|
||||
|
||||
showReady: ->
|
||||
return
|
||||
|
||||
unveil: ->
|
||||
_.delay @reallyUnveil, 1000
|
||||
|
||||
reallyUnveil: =>
|
||||
return if @destroyed
|
||||
loadingDetails = @$el.find('.loading-details')
|
||||
duration = parseFloat loadingDetails.css 'transition-duration'
|
||||
loadingDetails.css 'top', -loadingDetails.outerHeight(true)
|
||||
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
|
||||
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
|
||||
_.delay @onUnveilEnded, duration * 1000
|
||||
|
||||
onUnveilEnded: =>
|
||||
return if @destroyed
|
||||
Backbone.Mediator.publish 'onLoadingViewUnveiled', view: @
|
||||
|
||||
getRenderData: (c={}) ->
|
||||
super c
|
||||
c
|
|
@ -22,6 +22,7 @@ Camera = require 'lib/surface/Camera'
|
|||
AudioPlayer = require 'lib/AudioPlayer'
|
||||
|
||||
# subviews
|
||||
LoadingView = require './level/level_loading_view'
|
||||
TomeView = require './level/tome/tome_view'
|
||||
ChatView = require './level/level_chat_view'
|
||||
HUDView = require './level/hud_view'
|
||||
|
@ -32,8 +33,6 @@ GoldView = require './level/gold_view'
|
|||
VictoryModal = require './level/modal/victory_modal'
|
||||
InfiniteLoopModal = require './level/modal/infinite_loop_modal'
|
||||
|
||||
LoadingScreen = require 'lib/LoadingScreen'
|
||||
|
||||
PROFILE_ME = false
|
||||
|
||||
module.exports = class PlayLevelView extends View
|
||||
|
@ -62,6 +61,8 @@ module.exports = class PlayLevelView extends View
|
|||
'level:session-will-save': 'onSessionWillSave'
|
||||
'level:set-team': 'setTeam'
|
||||
'god:new-world-created': 'loadSoundsForWorld'
|
||||
'level:started': 'onLevelStarted'
|
||||
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
|
||||
|
||||
events:
|
||||
'click #level-done-button': 'onDonePressed'
|
||||
|
@ -122,8 +123,7 @@ module.exports = class PlayLevelView extends View
|
|||
|
||||
afterRender: ->
|
||||
window.onPlayLevelViewLoaded? @ # still a hack
|
||||
@loadingScreen = new LoadingScreen(@$el.find('canvas')[0])
|
||||
@loadingScreen.show()
|
||||
@insertSubView @loadingView = new LoadingView {}
|
||||
@$el.find('#level-done-button').hide()
|
||||
super()
|
||||
|
||||
|
@ -149,16 +149,15 @@ module.exports = class PlayLevelView extends View
|
|||
|
||||
onLevelLoaderLoaded: ->
|
||||
return unless @levelLoader.progress() is 1 # double check, since closing the guide may trigger this early
|
||||
# Save latest level played in local storage
|
||||
if window.currentModal and not window.currentModal.destroyed
|
||||
@loadingScreen.showReady()
|
||||
@loadingView.showReady()
|
||||
return Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelLoaderLoaded, @
|
||||
|
||||
# Save latest level played in local storage
|
||||
localStorage["lastLevel"] = @levelID if localStorage?
|
||||
@grabLevelLoaderData()
|
||||
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
|
||||
@loadOpponentTeam(team)
|
||||
@loadingScreen.destroy()
|
||||
@god.level = @level.serialize @supermodel
|
||||
@god.worldClassMap = @world.classMap
|
||||
@setTeam team
|
||||
|
@ -202,6 +201,12 @@ module.exports = class PlayLevelView extends View
|
|||
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
|
||||
@session.set 'multiplayer', false
|
||||
|
||||
onLevelStarted: (e) ->
|
||||
@loadingView?.unveil()
|
||||
|
||||
onLoadingViewUnveiled: (e) ->
|
||||
@removeSubView @loadingView
|
||||
@loadingView = null
|
||||
|
||||
onSupermodelLoadedOne: =>
|
||||
@modelsLoaded ?= 0
|
||||
|
|
|
@ -20,6 +20,7 @@ Camera = require 'lib/surface/Camera'
|
|||
AudioPlayer = require 'lib/AudioPlayer'
|
||||
|
||||
# subviews
|
||||
LoadingView = require './level/level_loading_view'
|
||||
TomeView = require './level/tome/tome_view'
|
||||
ChatView = require './level/level_chat_view'
|
||||
HUDView = require './level/hud_view'
|
||||
|
@ -30,8 +31,6 @@ GoldView = require './level/gold_view'
|
|||
VictoryModal = require './level/modal/victory_modal'
|
||||
InfiniteLoopModal = require './level/modal/infinite_loop_modal'
|
||||
|
||||
LoadingScreen = require 'lib/LoadingScreen'
|
||||
|
||||
PROFILE_ME = false
|
||||
|
||||
module.exports = class SpectateLevelView extends View
|
||||
|
@ -57,6 +56,8 @@ module.exports = class SpectateLevelView extends View
|
|||
'level:set-team': 'setTeam'
|
||||
'god:new-world-created': 'loadSoundsForWorld'
|
||||
'next-game-pressed': 'onNextGamePressed'
|
||||
'level:started': 'onLevelStarted'
|
||||
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
|
||||
|
||||
events:
|
||||
'click #level-done-button': 'onDonePressed'
|
||||
|
@ -73,6 +74,10 @@ module.exports = class SpectateLevelView extends View
|
|||
|
||||
@sessionOne = @getQueryVariable 'session-one'
|
||||
@sessionTwo = @getQueryVariable 'session-two'
|
||||
if options.spectateSessions
|
||||
@sessionOne = options.spectateSessions.sessionOne
|
||||
@sessionTwo = options.spectateSessions.sessionTwo
|
||||
|
||||
if not @sessionOne or not @sessionTwo
|
||||
@fetchRandomSessionPair (err, data) =>
|
||||
if err? then return console.log "There was an error fetching the random session pair: #{data}"
|
||||
|
@ -112,8 +117,7 @@ module.exports = class SpectateLevelView extends View
|
|||
|
||||
afterRender: ->
|
||||
window.onPlayLevelViewLoaded? @ # still a hack
|
||||
@loadingScreen = new LoadingScreen(@$el.find('canvas')[0])
|
||||
@loadingScreen.show()
|
||||
@insertSubView @loadingView = new LoadingView {}
|
||||
@$el.find('#level-done-button').hide()
|
||||
super()
|
||||
|
||||
|
@ -141,14 +145,13 @@ module.exports = class SpectateLevelView extends View
|
|||
return unless @levelLoader.progress() is 1 # double check, since closing the guide may trigger this early
|
||||
# Save latest level played in local storage
|
||||
if window.currentModal and not window.currentModal.destroyed
|
||||
@loadingScreen.showReady()
|
||||
@loadingView.showReady()
|
||||
return Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelLoaderLoaded, @
|
||||
|
||||
@grabLevelLoaderData()
|
||||
#at this point, all requisite data is loaded, and sessions are not denormalized
|
||||
team = @world.teamForPlayer(0)
|
||||
@loadOpponentTeam(team)
|
||||
@loadingScreen.destroy()
|
||||
@god.level = @level.serialize @supermodel
|
||||
@god.worldClassMap = @world.classMap
|
||||
@setTeam team
|
||||
|
@ -167,11 +170,13 @@ module.exports = class SpectateLevelView extends View
|
|||
id: @session.get('creator')
|
||||
name: @session.get('creatorName')
|
||||
team: @session.get('team')
|
||||
levelSlug: @level.get('slug')
|
||||
|
||||
@surface.createOpponentWizard
|
||||
id: @otherSession.get('creator')
|
||||
name: @otherSession.get('creatorName')
|
||||
team: @otherSession.get('team')
|
||||
levelSlug: @level.get('slug')
|
||||
|
||||
grabLevelLoaderData: ->
|
||||
@session = @levelLoader.session
|
||||
|
@ -199,6 +204,12 @@ module.exports = class SpectateLevelView extends View
|
|||
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
|
||||
@session.set 'multiplayer', false
|
||||
|
||||
onLevelStarted: (e) ->
|
||||
@loadingView?.unveil()
|
||||
|
||||
onLoadingViewUnveiled: (e) ->
|
||||
@removeSubView @loadingView
|
||||
@loadingView = null
|
||||
|
||||
onSupermodelLoadedOne: =>
|
||||
@modelsLoaded ?= 0
|
||||
|
@ -429,12 +440,11 @@ module.exports = class SpectateLevelView extends View
|
|||
if err? then return console.log "There was an error fetching the random session pair: #{data}"
|
||||
@sessionOne = data[0]._id
|
||||
@sessionTwo = data[1]._id
|
||||
console.log "Playing session #{@sessionOne} against #{@sessionTwo}"
|
||||
url = "/play/spectate/dungeon-arena?session-one=#{@sessionOne}&session-two=#{@sessionTwo}"
|
||||
url = "/play/spectate/#{@levelID}?session-one=#{@sessionOne}&session-two=#{@sessionTwo}"
|
||||
Backbone.Mediator.publish 'router:navigate', {
|
||||
route: url,
|
||||
viewClass: SpectateLevelView,
|
||||
viewArgs: [{spectateSessions:{sessionOne: @sessionOne, sessionTwo: @sessionTwo}}, "dungeon-arena"]}
|
||||
viewArgs: [{spectateSessions:{sessionOne: @sessionOne, sessionTwo: @sessionTwo}}, @levelID ]}
|
||||
|
||||
fetchRandomSessionPair: (cb) ->
|
||||
console.log "Fetching random session pair!"
|
||||
|
|
|
@ -100,8 +100,8 @@ sendLadderUpdateEmail = (session, daysAgo) ->
|
|||
context =
|
||||
email_id: sendwithus.templates.ladder_update_email
|
||||
recipient:
|
||||
#address: user.email
|
||||
address: 'nick@codecombat.com' # Debugging
|
||||
address: user.email
|
||||
#address: 'nick@codecombat.com' # Debugging
|
||||
name: name
|
||||
email_data:
|
||||
name: name
|
||||
|
|
Loading…
Reference in a new issue