From 3bdede35875681401da1a93458bb54a30e697c68 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 11 Feb 2015 15:13:38 -0800 Subject: [PATCH 1/4] Fixed #141. --- app/views/editor/level/scripts/ScriptsTabView.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/editor/level/scripts/ScriptsTabView.coffee b/app/views/editor/level/scripts/ScriptsTabView.coffee index 0fd5fb192..51fcfa208 100644 --- a/app/views/editor/level/scripts/ScriptsTabView.coffee +++ b/app/views/editor/level/scripts/ScriptsTabView.coffee @@ -118,6 +118,7 @@ module.exports = class ScriptsTabView extends CocoView treema.enableTracking() onScriptChanged: => + return unless @selectedScriptPath @scriptsTreema.set(@selectedScriptPath, @scriptTreema.data) onThangsEdited: (e) -> From 5ee6eaf8b439c133fcb97bbb3ff9dba041bdca76 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 11 Feb 2015 15:51:49 -0800 Subject: [PATCH 2/4] Fixed audio upload nodes to work in Firefox. Fixed #445. --- app/core/treema-ext.coffee | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/core/treema-ext.coffee b/app/core/treema-ext.coffee index cb41d61fd..f5d1d3c17 100644 --- a/app/core/treema-ext.coffee +++ b/app/core/treema-ext.coffee @@ -96,8 +96,16 @@ class SoundFileTreema extends TreemaNode.nodeMap.string buildValueForDisplay: (valEl, data) -> mimetype = "audio/#{@keyForParent}" + mimetypes = [mimetype] + if mimetype is 'audio/mp3' + # https://github.com/codecombat/codecombat/issues/445 + # http://stackoverflow.com/questions/10688588/which-mime-type-should-i-use-for-mp3 + mimetypes.push 'audio/mpeg' + else if mimetype is 'audio/ogg' + mimetypes.push 'application/ogg' + mimetypes.push 'video/ogg' # huh, that's what it took to be able to upload ogg sounds in Firefox pickButton = $('') - .click(=> filepicker.pick {mimetypes:[mimetype]}, @onFileChosen) + .click(=> filepicker.pick {mimetypes: mimetypes}, @onFileChosen) playButton = $('') .click(@playFile) stopButton = $('') @@ -116,7 +124,7 @@ class SoundFileTreema extends TreemaNode.nodeMap.string menu = $('') files = @getFiles() for file in files - continue unless file.get('contentType') is mimetype + continue unless file.get('contentType') in mimetypes path = file.get('metadata').path filename = file.get 'filename' fullPath = [path, filename].join('/') From 2efb6aafbc3e5b3644626fb6fd569c1e3ecb8d04 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 11 Feb 2015 16:12:26 -0800 Subject: [PATCH 3/4] Fixed #1632. --- app/core/auth.coffee | 4 ++-- app/views/core/AuthModal.coffee | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/core/auth.coffee b/app/core/auth.coffee index 0fc6044cd..06390af82 100644 --- a/app/core/auth.coffee +++ b/app/core/auth.coffee @@ -37,14 +37,14 @@ module.exports.createUserWithoutReload = (userObject, failure=backboneFailure) - Backbone.Mediator.publish('created-user-without-reload') }) -module.exports.loginUser = (userObject, failure=genericFailure) -> +module.exports.loginUser = (userObject, failure=genericFailure, nextURL=null) -> console.log 'logging in as', userObject.email jqxhr = $.post('/auth/login', { username: userObject.email, password: userObject.password }, - (model) -> window.location.reload() + (model) -> if nextURL then window.location.href = nextURL else window.location.reload() ) jqxhr.fail(failure) diff --git a/app/views/core/AuthModal.coffee b/app/views/core/AuthModal.coffee index a990bb220..9b52bd013 100644 --- a/app/views/core/AuthModal.coffee +++ b/app/views/core/AuthModal.coffee @@ -73,7 +73,7 @@ module.exports = class AuthModal extends ModalView res = tv4.validateMultiple userObject, User.schema return forms.applyErrorsToForm(@$el, res.errors) unless res.valid @enableModalInProgress(@$el) # TODO: part of forms - loginUser(userObject) + loginUser userObject, null, window.nextURL createAccount: -> forms.clearFormAlerts(@$el) From 63fa2f86d4eb1336120b1ad8e1c7379da7f39409 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 11 Feb 2015 20:24:12 -0800 Subject: [PATCH 4/4] Tracking who is simulating matches so we can see patterns in ill-reported matches. Rejecting simulations from simulators with old versions of the Simulator code. --- app/lib/simulator/Simulator.coffee | 27 +++++++++++++-- app/schemas/models/level_session.coffee | 1 + app/templates/play/ladder/my_matches_tab.jade | 2 +- app/views/ladder/MyMatchesTabView.coffee | 1 + server/queues/scoring.coffee | 33 ++++++++++++++----- server_setup.coffee | 2 +- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/app/lib/simulator/Simulator.coffee b/app/lib/simulator/Simulator.coffee index 9805f38bc..2ec95bfef 100644 --- a/app/lib/simulator/Simulator.coffee +++ b/app/lib/simulator/Simulator.coffee @@ -5,9 +5,23 @@ GoalManager = require 'lib/world/GoalManager' God = require 'lib/God' {createAetherOptions} = require 'lib/aether_utils' +SIMULATOR_VERSION = 1 + +simulatorInfo = {} +if $.browser + simulatorInfo['desktop'] = $.browser.desktop if $.browser.desktop + simulatorInfo['name'] = $.browser.name if $.browser.name + simulatorInfo['platform'] = $.browser.platform if $.browser.platform + simulatorInfo['version'] = $.browser.versionNumber if $.browser.versionNumber + module.exports = class Simulator extends CocoClass constructor: (@options) -> @options ?= {} + simulatorType = if @options.headlessClient then 'headless' else 'browser' + @simulator = + type: simulatorType + version: SIMULATOR_VERSION + info: simulatorInfo _.extend @, Backbone.Events @trigger 'statusUpdate', 'Starting simulation!' @retryDelayInSeconds = 2 @@ -28,10 +42,17 @@ module.exports = class Simulator extends CocoClass type: 'POST' parse: true data: - 'humansGameID': humanGameID - 'ogresGameID': ogresGameID + humansGameID: humanGameID + ogresGameID: ogresGameID + simulator: @simulator error: (errorData) -> console.warn "There was an error fetching two games! #{JSON.stringify errorData}" + if errorData?.responseText?.indexOf("Old simulator") isnt -1 + noty { + text: errorData.responseText + layout: 'center' + type: 'error' + } success: (taskData) => return if @destroyed unless taskData @@ -278,7 +299,6 @@ module.exports = class Simulator extends CocoClass return if @destroyed console.log "Task registration result: #{JSON.stringify result}" @trigger 'statusUpdate', 'Results were successfully sent back to server!' - console.log 'Simulated by you:', @simulatedByYou @simulatedByYou++ unless @options.headlessClient simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1 @@ -307,6 +327,7 @@ module.exports = class Simulator extends CocoClass originalSessionRank: -1 calculationTime: 500 sessions: [] + simulator: @simulator for session in @task.getSessions() sessionResult = diff --git a/app/schemas/models/level_session.coffee b/app/schemas/models/level_session.coffee index 6942b8f6b..41f585f43 100644 --- a/app/schemas/models/level_session.coffee +++ b/app/schemas/models/level_session.coffee @@ -285,6 +285,7 @@ _.extend LevelSessionSchema.properties, codeLanguage: type: ['string', 'null'] # 'null' in case an opponent session got corrupted, don't care much here description: 'What submittedCodeLanguage the opponent used during the match' + simulator: {type: 'object', description: 'Holds info on who simulated the match, and with what tools.'} c.extendBasicProperties LevelSessionSchema, 'level.session' c.extendPermissionsProperties LevelSessionSchema, 'level.session' diff --git a/app/templates/play/ladder/my_matches_tab.jade b/app/templates/play/ladder/my_matches_tab.jade index bf4d9e856..5c1a12543 100644 --- a/app/templates/play/ladder/my_matches_tab.jade +++ b/app/templates/play/ladder/my_matches_tab.jade @@ -31,7 +31,7 @@ div#columns.row th(data-i18n="general.when") When th for match in team.matches - tr(class=(match.stale ? "stale " : "") + (match.fresh ? "fresh " : "") + match.state) + tr(class=(match.stale ? "stale " : "") + (match.fresh ? "fresh " : "") + match.state, title=match.simulator) td.state-cell if match.state === 'win' span(data-i18n="general.win").win Win diff --git a/app/views/ladder/MyMatchesTabView.coffee b/app/views/ladder/MyMatchesTabView.coffee index eebc92ab8..ecc2370e0 100644 --- a/app/views/ladder/MyMatchesTabView.coffee +++ b/app/views/ladder/MyMatchesTabView.coffee @@ -72,6 +72,7 @@ module.exports = class MyMatchesTabView extends CocoView stale: match.date < submitDate fresh: fresh codeLanguage: match.codeLanguage + simulator: JSON.stringify(match.simulator) } for team in @teams diff --git a/server/queues/scoring.coffee b/server/queues/scoring.coffee index f97403e1d..74f822243 100644 --- a/server/queues/scoring.coffee +++ b/server/queues/scoring.coffee @@ -15,6 +15,8 @@ bayes = new (require 'bayesian-battle')() scoringTaskQueue = undefined scoringTaskTimeoutInSeconds = 600 +SIMULATOR_VERSION = 1 + module.exports.setup = (app) -> connectToScoringQueue() connectToScoringQueue = -> @@ -124,6 +126,7 @@ module.exports.getTwoGames = (req, res) -> #if userIsAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.') humansGameID = req.body.humansGameID ogresGameID = req.body.ogresGameID + return if simulatorIsTooOld req, res #ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span'] # Let's not give any extra simulations to old ladders. ladderGameIDs = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove'] levelID = _.sample ladderGameIDs @@ -224,6 +227,8 @@ module.exports.getTwoGames = (req, res) -> module.exports.recordTwoGames = (req, res) -> sessions = req.body.sessions #console.log 'Recording non-chained result of', sessions?[0]?.name, sessions[0]?.metrics?.rank, 'and', sessions?[1]?.name, sessions?[1]?.metrics?.rank + return if simulatorIsTooOld req, res + req.body?.simulator?.user = '' + req.user?._id yetiGuru = clientResponseObject: req.body, isRandomMatch: true async.waterfall [ @@ -442,7 +447,9 @@ getSessionInformation = (sessionIDString, callback) -> callback null, session module.exports.processTaskResult = (req, res) -> + return if simulatorIsTooOld req, res originalSessionID = req.body?.originalSessionID + req.body?.simulator?.user = '' + req.user?._id yetiGuru = {} try async.waterfall [ @@ -578,14 +585,14 @@ addMatchToSessions = (newScoreObject, callback) -> matchObject.opponents = {} for session in @clientResponseObject.sessions sessionID = session.sessionID - matchObject.opponents[sessionID] = {} - matchObject.opponents[sessionID].sessionID = sessionID - matchObject.opponents[sessionID].userID = session.creator - matchObject.opponents[sessionID].name = session.name - matchObject.opponents[sessionID].totalScore = session.totalScore - matchObject.opponents[sessionID].metrics = {} - matchObject.opponents[sessionID].metrics.rank = Number(newScoreObject[sessionID]?.gameRanking ? 0) - matchObject.opponents[sessionID].codeLanguage = newScoreObject[sessionID].submittedCodeLanguage + matchObject.opponents[sessionID] = match = {} + match.sessionID = sessionID + match.userID = session.creator + match.name = session.name + match.totalScore = session.totalScore + match.metrics = {} + match.metrics.rank = Number(newScoreObject[sessionID]?.gameRanking ? 0) + match.codeLanguage = newScoreObject[sessionID].submittedCodeLanguage #log.info "Match object computed, result: #{matchObject}" #log.info 'Writing match object to database...' @@ -603,6 +610,7 @@ updateMatchesInSession = (matchObject, sessionID, callback) -> opponentsArray = _.toArray opponentsClone currentMatchObject.opponents = opponentsArray currentMatchObject.codeLanguage = matchObject.opponents[opponentsArray[0].sessionID].codeLanguage + currentMatchObject.simulator = @clientResponseObject.simulator LevelSession.findOne {'_id': sessionID}, (err, session) -> session = session.toObject() currentMatchObject.playtime = session.playtime ? 0 @@ -785,3 +793,12 @@ retrieveOldSessionData = (sessionID, callback) -> markSessionAsDoneRanking = (sessionID, cb) -> #console.log 'Marking session as done ranking...' LevelSession.update {'_id': sessionID}, {'isRanking': false}, cb + +simulatorIsTooOld = (req, res) -> + clientSimulator = req.body.simulator + return false if clientSimulator?.version >= SIMULATOR_VERSION + message = "Old simulator version #{clientSimulator?.version}, need to clear cache and get version #{SIMULATOR_VERSION}." + log.debug "400: #{message}" + res.send 400, message + res.end() + true diff --git a/server_setup.coffee b/server_setup.coffee index df81d50a0..ab99f4a04 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -50,7 +50,7 @@ setupErrorMiddleware = (app) -> res.status(err.status ? 500).send(error: "Something went wrong!") message = "Express error: #{req.method} #{req.path}: #{err.message}" log.error "#{message}, stack: #{err.stack}" - hipchat.sendTowerHipChatMessage(message) + hipchat.sendHipChatMessage(message, ['tower'], {papertrail: true}) else next(err) setupExpressMiddleware = (app) ->