mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-29 18:45:48 -05:00
177 lines
6.9 KiB
CoffeeScript
177 lines
6.9 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
|
|
@spectateOpponentCodeLanguage = options.spectateOpponentCodeLanguage
|
|
@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 @otherSession?.get('submittedCodeLanguage') ? @spectateOpponentCodeLanguage
|
|
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)
|
|
if sessionSource isnt '// Should fill in some default source\n' # TODO: figure out why session is getting this default source in there and stop it
|
|
@source = sessionSource
|
|
@thangs = {}
|
|
if @canRead() # We can avoid creating these views if we'll never use them.
|
|
@view = new SpellView {spell: @, level: options.level, session: @session, worker: @worker}
|
|
@view.render() # Get it ready and code loaded in advance
|
|
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level
|
|
@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) ->
|
|
#console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
|
|
@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 or @spectateView
|
|
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 and @canWrite e.team
|
|
if e.codeLanguage and e.code
|
|
[thangSlug, methodSlug] = @spellKey.split '/'
|
|
if opponentCode = e.code[thangSlug]?[methodSlug]
|
|
@source = opponentCode
|
|
@updateLanguageAether e.codeLanguage
|
|
else
|
|
console.error 'Spell onNewOpponentCode did not receive code', e
|