codecombat/app/views/play/level/tome/Spell.coffee
2014-09-03 18:35:14 -07:00

174 lines
6.5 KiB
CoffeeScript

SpellView = require './SpellView'
SpellListTabEntryView = require './SpellListTabEntryView'
{me} = require 'lib/auth'
{createAetherOptions} = require 'lib/aether_utils'
module.exports = class Spell
loaded: false
view: null
entryView: null
constructor: (options) ->
@spellKey = options.spellKey
@pathComponents = options.pathComponents
@session = options.session
@otherSession = options.otherSession
@spectateView = options.spectateView
@supermodel = options.supermodel
@skipProtectAPI = options.skipProtectAPI
@worker = options.worker
@levelID = options.levelID
p = options.programmableMethod
@languages = p.languages ? {}
@languages.javascript ?= p.source
@name = p.name
@permissions = read: p.permissions?.read ? [], readwrite: p.permissions?.readwrite ? [] # teams
if @canWrite()
@setLanguage options.language
else if @isEnemySpell()
@setLanguage options.otherSession.get 'submittedCodeLanguage'
else
@setLanguage 'javascript'
@useTranspiledCode = @shouldUseTranspiledCode()
console.log 'Spell', @spellKey, 'is using transpiled code (should only happen if it\'s an enemy/spectate writable method).' if @useTranspiledCode
@source = @originalSource
@parameters = p.parameters
if @permissions.readwrite.length and sessionSource = @session.getSourceFor(@spellKey)
@source = sessionSource
@thangs = {}
if @canRead() # We can avoid creating these views if we'll never use them.
@view = new SpellView {spell: @, session: @session, worker: @worker}
@view.render() # Get it ready and code loaded in advance
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, language: @language
@tabView.render()
@team = @permissions.readwrite[0] ? 'common'
Backbone.Mediator.publish 'tome:spell-created', spell: @
Backbone.Mediator.subscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode
destroy: ->
@view?.destroy()
@tabView?.destroy()
@thangs = null
@worker = null
setLanguage: (@language) ->
@originalSource = @languages[language] ? @languages.javascript
addThang: (thang) ->
if @thangs[thang.id]
@thangs[thang.id].thang = thang
else
@thangs[thang.id] = {thang: thang, aether: @createAether(thang), castAether: null}
removeThangID: (thangID) ->
delete @thangs[thangID]
canRead: (team) ->
(team ? me.team) in @permissions.read or (team ? me.team) in @permissions.readwrite
canWrite: (team) ->
(team ? me.team) in @permissions.readwrite
getSource: ->
@view?.getSource() ? @source
transpile: (source) ->
if source
@source = source
else
source = @getSource()
[pure, problems] = [null, null]
if @useTranspiledCode
transpiledCode = @session.get('code')
for thangID, spellThang of @thangs
unless pure
if @useTranspiledCode and transpiledSpell = transpiledCode[@spellKey.split('/')[0]]?[@name]
spellThang.aether.pure = transpiledSpell
else
pure = spellThang.aether.transpile source
problems = spellThang.aether.problems
#console.log 'aether transpiled', source.length, 'to', spellThang.aether.pure.length, 'for', thangID, @spellKey
else
spellThang.aether.raw = source
spellThang.aether.pure = pure
spellThang.aether.problems = problems
#console.log 'aether reused transpilation for', thangID, @spellKey
null
hasChanged: (newSource=null, currentSource=null) ->
(newSource ? @originalSource) isnt (currentSource ? @source)
hasChangedSignificantly: (newSource=null, currentSource=null, cb) ->
for thangID, spellThang of @thangs
aether = spellThang.aether
break
unless aether
console.error @toString(), 'couldn\'t find a spellThang with aether of', @thangs
cb false
workerMessage =
function: 'hasChangedSignificantly'
a: (newSource ? @originalSource)
spellKey: @spellKey
b: (currentSource ? @source)
careAboutLineNumbers: true
careAboutLint: true
@worker.addEventListener 'message', (e) =>
workerData = JSON.parse e.data
if workerData.function is 'hasChangedSignificantly' and workerData.spellKey is @spellKey
@worker.removeEventListener 'message', arguments.callee, false
cb(workerData.hasChanged)
@worker.postMessage JSON.stringify(workerMessage)
createAether: (thang) ->
aceConfig = me.get('aceConfig') ? {}
writable = @permissions.readwrite.length > 0
skipProtectAPI = @skipProtectAPI or not writable
aetherOptions = createAetherOptions functionName: @name, codeLanguage: @language, functionParameters: @parameters, skipProtectAPI: skipProtectAPI
aether = new Aether aetherOptions
workerMessage =
function: 'createAether'
spellKey: @spellKey
options: aetherOptions
@worker.postMessage JSON.stringify workerMessage
aether
updateLanguageAether: (@language) ->
for thangId, spellThang of @thangs
spellThang.aether?.setLanguage @language
spellThang.castAether = null
Backbone.Mediator.publish 'tome:spell-changed-language', spell: @, language: @language
workerMessage =
function: 'updateLanguageAether'
newLanguage: @language
@worker.postMessage JSON.stringify workerMessage
@transpile()
toString: ->
"<Spell: #{@spellKey}>"
isEnemySpell: ->
return false unless @permissions.readwrite.length
return false unless @otherSession
teamSpells = @session.get('teamSpells')
team = @session.get('team') ? 'humans'
teamSpells and not _.contains(teamSpells[team], @spellKey)
shouldUseTranspiledCode: ->
# Determine whether this code has already been transpiled, or whether it's raw source needing transpilation.
return true if @spectateView # Use transpiled code for both teams if we're just spectating.
return true if @isEnemySpell() # Use transpiled for enemy spells.
# Players without permissions can't view the raw code.
return true if @session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions', true))
false
onNewOpponentCode: (e) =>
return unless @spellKey
if e.codeLanguage and e.code
spellkeyComponents = @spellKey.split '/'
if e.code[spellkeyComponents[0]]?[spellkeyComponents[1]]
@source = e.code[spellkeyComponents[0]][spellkeyComponents[1]]
@updateLanguageAether e.codeLanguage
else
console.error 'Spell onNewOpponentCode did not receive code', e