mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 08:41:46 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
118c76cc2d
12 changed files with 97 additions and 25 deletions
|
@ -319,6 +319,7 @@ self.setupDebugWorldToRunUntilFrame = function (args) {
|
|||
self.debugWorld.submissionCount = args.submissionCount;
|
||||
self.debugWorld.fixedSeed = args.fixedSeed;
|
||||
self.debugWorld.flagHistory = args.flagHistory;
|
||||
self.debugWorld.realTimeInputEvents = args.realTimeInputEvents;
|
||||
self.debugWorld.difficulty = args.difficulty;
|
||||
if (args.level)
|
||||
self.debugWorld.loadFromLevel(args.level, true);
|
||||
|
@ -381,6 +382,7 @@ self.runWorld = function runWorld(args) {
|
|||
self.world.submissionCount = args.submissionCount;
|
||||
self.world.fixedSeed = args.fixedSeed;
|
||||
self.world.flagHistory = args.flagHistory || [];
|
||||
self.world.realTimeInputEvents = args.realTimeInputEvents || [];
|
||||
self.world.difficulty = args.difficulty || 0;
|
||||
if(args.level)
|
||||
self.world.loadFromLevel(args.level, true);
|
||||
|
@ -539,6 +541,11 @@ self.addFlagEvent = function addFlagEvent(flagEvent) {
|
|||
self.world.addFlagEvent(flagEvent);
|
||||
};
|
||||
|
||||
self.addRealTimeInputEvent = function addRealTimeInputEvent(realTimeInputEvent) {
|
||||
if(!self.world) return;
|
||||
self.world.addRealTimeInputEvent(realTimeInputEvent);
|
||||
};
|
||||
|
||||
self.stopRealTimePlayback = function stopRealTimePlayback() {
|
||||
if(!self.world) return;
|
||||
self.world.realTime = false;
|
||||
|
|
|
@ -60,6 +60,11 @@ module.exports = ModuleLoader = class ModuleLoader extends CocoClass
|
|||
# load dependencies if it's not a vendor library
|
||||
if not _.string.startsWith(e.item.id, 'vendor')
|
||||
have = window.require.list()
|
||||
haveWithIndexRemoved = _(have)
|
||||
.filter (file) -> _.string.endsWith(file, 'index')
|
||||
.map (file) -> file.slice(0,-6)
|
||||
.value()
|
||||
have = have.concat(haveWithIndexRemoved)
|
||||
console.group('Dependencies', e.item.id) if LOG
|
||||
@recentLoadedBytes += e.rawResult.length
|
||||
dependencies = @parseDependencies(e.rawResult)
|
||||
|
|
|
@ -37,6 +37,7 @@ module.exports = class Angel extends CocoClass
|
|||
@allLogs = []
|
||||
@hireWorker()
|
||||
@shared.angels.push @
|
||||
@listenTo @shared.gameUIState.get('realTimeInputEvents'), 'add', @onAddRealTimeInputEvent
|
||||
|
||||
destroy: ->
|
||||
@fireWorker false
|
||||
|
@ -266,6 +267,10 @@ module.exports = class Angel extends CocoClass
|
|||
return unless @running and @work.realTime
|
||||
@worker.postMessage func: 'addFlagEvent', args: e
|
||||
|
||||
onAddRealTimeInputEvent: (realTimeInputEvent) ->
|
||||
return unless @running and @work.realTime
|
||||
@worker.postMessage func: 'addRealTimeInputEvent', args: realTimeInputEvent.toJSON()
|
||||
|
||||
onStopRealTimePlayback: (e) ->
|
||||
return unless @running and @work.realTime
|
||||
@work.realTime = false
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
World = require 'lib/world/world'
|
||||
CocoClass = require 'core/CocoClass'
|
||||
Angel = require 'lib/Angel'
|
||||
GameUIState = require 'models/GameUIState'
|
||||
|
||||
module.exports = class God extends CocoClass
|
||||
@nicks: ['Athena', 'Baldr', 'Crom', 'Dagr', 'Eris', 'Freyja', 'Great Gish', 'Hades', 'Ishtar', 'Janus', 'Khronos', 'Loki', 'Marduk', 'Negafook', 'Odin', 'Poseidon', 'Quetzalcoatl', 'Ra', 'Shiva', 'Thor', 'Umvelinqangi', 'Týr', 'Vishnu', 'Wepwawet', 'Xipe Totec', 'Yahweh', 'Zeus', '上帝', 'Tiamat', '盘古', 'Phoebe', 'Artemis', 'Osiris', '嫦娥', 'Anhur', 'Teshub', 'Enlil', 'Perkele', 'Chaos', 'Hera', 'Iris', 'Theia', 'Uranus', 'Stribog', 'Sabazios', 'Izanagi', 'Ao', 'Tāwhirimātea', 'Tengri', 'Inmar', 'Torngarsuk', 'Centzonhuitznahua', 'Hunab Ku', 'Apollo', 'Helios', 'Thoth', 'Hyperion', 'Alectrona', 'Eos', 'Mitra', 'Saranyu', 'Freyr', 'Koyash', 'Atropos', 'Clotho', 'Lachesis', 'Tyche', 'Skuld', 'Urðr', 'Verðandi', 'Camaxtli', 'Huhetotl', 'Set', 'Anu', 'Allah', 'Anshar', 'Hermes', 'Lugh', 'Brigit', 'Manannan Mac Lir', 'Persephone', 'Mercury', 'Venus', 'Mars', 'Azrael', 'He-Man', 'Anansi', 'Issek', 'Mog', 'Kos', 'Amaterasu Omikami', 'Raijin', 'Susanowo', 'Blind Io', 'The Lady', 'Offler', 'Ptah', 'Anubis', 'Ereshkigal', 'Nergal', 'Thanatos', 'Macaria', 'Angelos', 'Erebus', 'Hecate', 'Hel', 'Orcus', 'Ishtar-Deela Nakh', 'Prometheus', 'Hephaestos', 'Sekhmet', 'Ares', 'Enyo', 'Otrera', 'Pele', 'Hadúr', 'Hachiman', 'Dayisun Tngri', 'Ullr', 'Lua', 'Minerva']
|
||||
|
@ -18,15 +19,17 @@ module.exports = class God extends CocoClass
|
|||
constructor: (options) ->
|
||||
options ?= {}
|
||||
@retrieveValueFromFrame = _.throttle @retrieveValueFromFrame, 1000
|
||||
@gameUIState ?= options.gameUIState or new GameUIState()
|
||||
super()
|
||||
|
||||
# Angels are all given access to this.
|
||||
@angelsShare =
|
||||
@angelsShare = {
|
||||
workerCode: options.workerCode or '/javascripts/workers/worker_world.js' # Either path or function
|
||||
headless: options.headless # Whether to just simulate the goals, or to deserialize all simulation results
|
||||
spectate: options.spectate
|
||||
god: @
|
||||
godNick: @nick
|
||||
@gameUIState
|
||||
workQueue: []
|
||||
firstWorld: true
|
||||
world: undefined
|
||||
|
@ -34,6 +37,7 @@ module.exports = class God extends CocoClass
|
|||
worldClassMap: undefined
|
||||
angels: []
|
||||
busyAngels: [] # Busy angels will automatically register here.
|
||||
}
|
||||
|
||||
# Determine how many concurrent Angels/web workers to use at a time
|
||||
# ~20MB per idle worker + angel overhead - every Angel maps to 1 worker
|
||||
|
|
|
@ -92,6 +92,8 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@gameUIState = @options.gameUIState or new GameUIState({
|
||||
canDragCamera: true
|
||||
})
|
||||
@realTimeInputEvents = @gameUIState.get('realTimeInputEvents')
|
||||
@listenTo(@gameUIState, 'sprite:mouse-down', @onSpriteMouseDown)
|
||||
@initEasel()
|
||||
@initAudio()
|
||||
@onResize = _.debounce @onResize, resizeDelay
|
||||
|
@ -510,6 +512,15 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@gameUIState.trigger('surface:stage-mouse-down', event)
|
||||
@mouseIsDown = true
|
||||
|
||||
onSpriteMouseDown: (e) =>
|
||||
return unless @realTime
|
||||
@realTimeInputEvents.add({
|
||||
type: 'mousedown'
|
||||
pos: @camera.screenToWorld x: e.originalEvent.stageX, y: e.originalEvent.stageY
|
||||
time: @world.dt * @world.frames.length
|
||||
thangID: e.sprite.thang.id
|
||||
})
|
||||
|
||||
onMouseUp: (e) =>
|
||||
return if @disabled
|
||||
onBackground = not @webGLStage.hitTest e.stageX, e.stageY
|
||||
|
@ -596,6 +607,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
onRealTimePlaybackStarted: (e) ->
|
||||
return if @realTime
|
||||
@realTimeInputEvents.reset()
|
||||
@realTime = true
|
||||
@onResize()
|
||||
@playing = false # Will start when countdown is done.
|
||||
|
|
|
@ -216,6 +216,9 @@ module.exports = class World
|
|||
addFlagEvent: (flagEvent) ->
|
||||
@flagHistory.push flagEvent
|
||||
|
||||
addRealTimeInputEvent: (realTimeInputEvent) ->
|
||||
@realTimeInputEvents.push realTimeInputEvent
|
||||
|
||||
loadFromLevel: (level, willSimulate=true) ->
|
||||
@levelID = level.slug
|
||||
@levelComponents = level.levelComponents
|
||||
|
|
|
@ -26,4 +26,5 @@ module.exports = class GameUIState extends CocoModel
|
|||
defaults: -> {
|
||||
selected: []
|
||||
canDragCamera: true
|
||||
realTimeInputEvents: new Backbone.Collection()
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ module.exports = class VerifierTest extends CocoClass
|
|||
session.set 'code', {'hero-placeholder': plan: session.solution.source}
|
||||
state = session.get 'state'
|
||||
state.flagHistory = session.solution.flagHistory
|
||||
state.realTimeInputEvents = session.solution.realTimeInputEvents
|
||||
state.difficulty = session.solution.difficulty or 0
|
||||
session.solution.seed = undefined unless _.isNumber session.solution.seed # TODO: migrate away from submissionCount/sessionID seed objects
|
||||
catch e
|
||||
|
|
|
@ -20,6 +20,7 @@ Article = require 'models/Article'
|
|||
Camera = require 'lib/surface/Camera'
|
||||
AudioPlayer = require 'lib/AudioPlayer'
|
||||
Simulator = require 'lib/simulator/Simulator'
|
||||
GameUIState = require 'models/GameUIState'
|
||||
|
||||
# subviews
|
||||
LevelLoadingView = require './LevelLoadingView'
|
||||
|
@ -108,6 +109,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
|
||||
@opponentSessionID = @getQueryVariable('opponent')
|
||||
@opponentSessionID ?= @options.opponent
|
||||
@gameUIState = new GameUIState()
|
||||
|
||||
$(window).on 'resize', @onWindowResize
|
||||
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
||||
|
@ -137,7 +139,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
|
||||
load: ->
|
||||
@loadStartTime = new Date()
|
||||
@god = new God()
|
||||
@god = new God({@gameUIState})
|
||||
levelLoaderOptions = supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @opponentSessionID, team: @getQueryVariable('team'), observing: @observing, courseID: @courseID
|
||||
if me.isSessionless()
|
||||
levelLoaderOptions.fakeSessionConfig = {}
|
||||
|
@ -345,12 +347,14 @@ module.exports = class PlayLevelView extends RootView
|
|||
initSurface: ->
|
||||
webGLSurface = $('canvas#webgl-surface', @$el)
|
||||
normalSurface = $('canvas#normal-surface', @$el)
|
||||
surfaceOptions =
|
||||
surfaceOptions = {
|
||||
thangTypes: @supermodel.getModels(ThangType)
|
||||
observing: @observing
|
||||
@observing
|
||||
playerNames: @findPlayerNames()
|
||||
levelType: @level.get('type', true)
|
||||
stayVisible: @showAds()
|
||||
@gameUIState
|
||||
}
|
||||
@surface = new Surface(@world, normalSurface, webGLSurface, surfaceOptions)
|
||||
worldBounds = @world.getBounds()
|
||||
bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}]
|
||||
|
|
|
@ -202,7 +202,7 @@ module.exports = class TomeView extends CocoView
|
|||
spell = @spellFor thang, spellName
|
||||
unless spell?.canRead()
|
||||
@clearSpellView()
|
||||
@updateSpellPalette thang, spell
|
||||
@updateSpellPalette thang, spell if spell
|
||||
return
|
||||
unless spell.view is @spellView
|
||||
@clearSpellView()
|
||||
|
|
|
@ -13,6 +13,8 @@ User = require '../models/User'
|
|||
Classroom = require '../models/Classroom'
|
||||
facebook = require '../lib/facebook'
|
||||
gplus = require '../lib/gplus'
|
||||
TrialRequest = require '../models/TrialRequest'
|
||||
log = require 'winston'
|
||||
|
||||
module.exports =
|
||||
fetchByGPlusID: wrap (req, res, next) ->
|
||||
|
@ -133,16 +135,7 @@ module.exports =
|
|||
throw new errors.Conflict('Email already taken')
|
||||
|
||||
req.user.set({ password, email, anonymous: false })
|
||||
try
|
||||
yield req.user.save()
|
||||
catch e
|
||||
if e.code is 11000 # Duplicate key error
|
||||
throw new errors.Conflict('Email already taken')
|
||||
else
|
||||
throw e
|
||||
|
||||
req.user.sendWelcomeEmail()
|
||||
res.status(200).send(req.user.toObject({req: req}))
|
||||
yield module.exports.finishSignup(req, res)
|
||||
|
||||
signupWithFacebook: wrap (req, res) ->
|
||||
unless req.user.isAnonymous()
|
||||
|
@ -159,16 +152,7 @@ module.exports =
|
|||
throw new errors.UnprocessableEntity('Invalid facebookAccessToken')
|
||||
|
||||
req.user.set({ facebookID, email, anonymous: false })
|
||||
try
|
||||
yield req.user.save()
|
||||
catch e
|
||||
if e.code is 11000 # Duplicate key error
|
||||
throw new errors.Conflict('Email already taken')
|
||||
else
|
||||
throw e
|
||||
|
||||
req.user.sendWelcomeEmail()
|
||||
res.status(200).send(req.user.toObject({req: req}))
|
||||
yield module.exports.finishSignup(req, res)
|
||||
|
||||
signupWithGPlus: wrap (req, res) ->
|
||||
unless req.user.isAnonymous()
|
||||
|
@ -186,6 +170,9 @@ module.exports =
|
|||
throw new errors.UnprocessableEntity('Invalid gplusAccessToken')
|
||||
|
||||
req.user.set({ gplusID, email, anonymous: false })
|
||||
yield module.exports.finishSignup(req, res)
|
||||
|
||||
finishSignup: co.wrap (req, res) ->
|
||||
try
|
||||
yield req.user.save()
|
||||
catch e
|
||||
|
@ -194,5 +181,21 @@ module.exports =
|
|||
else
|
||||
throw e
|
||||
|
||||
# post-successful account signup tasks
|
||||
|
||||
req.user.sendWelcomeEmail()
|
||||
|
||||
# If person A creates a trial request without creating an account, then person B uses that computer
|
||||
# to create an account, then person A's trial request is associated with person B's account. To prevent
|
||||
# this, we check that the signup email matches the trial request email, for every signup. If they do
|
||||
# not match, the trial request applicant field is cleared, disassociating the trial request from this
|
||||
# account.
|
||||
trialRequest = yield TrialRequest.findOne({applicant: req.user._id})
|
||||
if trialRequest
|
||||
email = trialRequest.get('properties')?.email or ''
|
||||
emailLower = email.toLowerCase()
|
||||
if emailLower and emailLower isnt req.user.get('emailLower')
|
||||
log.warn('User submitted trial request and created account with different emails. Disassociating trial request.')
|
||||
yield trialRequest.update({$unset: {applicant: ''}})
|
||||
|
||||
res.status(200).send(req.user.toObject({req: req}))
|
||||
|
|
|
@ -3,6 +3,7 @@ utils = require '../utils'
|
|||
urlUser = '/db/user'
|
||||
User = require '../../../server/models/User'
|
||||
Classroom = require '../../../server/models/Classroom'
|
||||
TrialRequest = require '../../../server/models/TrialRequest'
|
||||
Prepaid = require '../../../server/models/Prepaid'
|
||||
request = require '../request'
|
||||
facebook = require '../../../server/lib/facebook'
|
||||
|
@ -706,6 +707,32 @@ describe 'POST /db/user/:handle/signup-with-password', ->
|
|||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(409)
|
||||
done()
|
||||
|
||||
it 'disassociates the user from their trial request if the trial request email and signup email do not match', utils.wrap (done) ->
|
||||
user = yield utils.becomeAnonymous()
|
||||
trialRequest = yield utils.makeTrialRequest({ properties: { email: 'one@email.com' } })
|
||||
expect(trialRequest.get('applicant').equals(user._id)).toBe(true)
|
||||
url = getURL("/db/user/#{user.id}/signup-with-password")
|
||||
email = 'two@email.com'
|
||||
json = { email, password: '12345' }
|
||||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(200)
|
||||
trialRequest = yield TrialRequest.findById(trialRequest.id)
|
||||
expect(trialRequest.get('applicant')).toBeUndefined()
|
||||
done()
|
||||
|
||||
it 'does NOT disassociate the user from their trial request if the trial request email and signup email DO match', utils.wrap (done) ->
|
||||
user = yield utils.becomeAnonymous()
|
||||
trialRequest = yield utils.makeTrialRequest({ properties: { email: 'one@email.com' } })
|
||||
expect(trialRequest.get('applicant').equals(user._id)).toBe(true)
|
||||
url = getURL("/db/user/#{user.id}/signup-with-password")
|
||||
email = 'one@email.com'
|
||||
json = { email, password: '12345' }
|
||||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(200)
|
||||
trialRequest = yield TrialRequest.findById(trialRequest.id)
|
||||
expect(trialRequest.get('applicant').equals(user._id)).toBe(true)
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /db/user/:handle/signup-with-facebook', ->
|
||||
|
|
Loading…
Reference in a new issue