2014-07-23 10:02:45 -04:00
SpellView = require ' ./SpellView '
SpellListTabEntryView = require ' ./SpellListTabEntryView '
2014-01-03 13:32:13 -05:00
{ me } = require ' lib/auth '
2014-05-08 14:43:00 -04:00
Aether . addGlobal ' Vector ' , require ' lib/world/vector '
Aether . addGlobal ' _ ' , _
2014-01-03 13:32:13 -05:00
module.exports = class Spell
loaded: false
view: null
entryView: null
2014-01-28 18:24:08 -05:00
constructor: (options) ->
@spellKey = options . spellKey
@pathComponents = options . pathComponents
@session = options . session
2014-06-20 20:19:18 -04:00
@otherSession = options . otherSession
2014-05-15 18:18:15 -04:00
@spectateView = options . spectateView
2014-01-28 18:24:08 -05:00
@supermodel = options . supermodel
@skipProtectAPI = options . skipProtectAPI
2014-02-17 20:38:49 -05:00
@worker = options . worker
2014-08-14 14:55:43 -04:00
@levelID = options . levelID
2014-01-28 18:24:08 -05:00
2014-06-18 01:17:44 -04:00
p = options . programmableMethod
@languages = p . languages ? { }
@ languages . javascript ? = p . source
2014-01-03 13:32:13 -05:00
@name = p . name
@permissions = read: p . permissions ? . read ? [ ] , readwrite: p . permissions ? . readwrite ? [ ] # teams
2014-06-20 20:19:18 -04:00
if @ canWrite ( )
@ setLanguage options . language
2014-06-20 21:45:57 -04:00
else if @ isEnemySpell ( )
2014-06-20 20:19:18 -04:00
@ setLanguage options . otherSession . get ' submittedCodeLanguage '
else
@ setLanguage ' javascript '
2014-06-18 01:17:44 -04:00
@useTranspiledCode = @ shouldUseTranspiledCode ( )
2014-06-30 22:16:26 -04:00
console . log ' Spell ' , @ spellKey , ' is using transpiled code (should only happen if it \' s an enemy/spectate writable method). ' if @ useTranspiledCode
2014-06-18 01:17:44 -04:00
@source = @ originalSource
2014-04-26 17:21:26 -04:00
@parameters = p . parameters
if @ permissions . readwrite . length and sessionSource = @ session . getSourceFor ( @ spellKey )
@source = sessionSource
2014-01-03 13:32:13 -05:00
@thangs = { }
2014-08-25 00:52:33 -04:00
if @ canRead ( ) # We can avoid creating these views if we'll never use them.
2014-08-25 00:39:34 -04:00
@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 ( )
2014-06-30 22:16:26 -04:00
@team = @ permissions . readwrite [ 0 ] ? ' common '
2014-02-10 16:18:39 -05:00
Backbone . Mediator . publish ' tome:spell-created ' , spell: @
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
Backbone . Mediator . subscribe ' realtime-multiplayer:new-opponent-code ' , @ onNewOpponentCode
2014-01-03 13:32:13 -05:00
2014-02-11 15:10:21 -05:00
destroy: ->
2014-08-25 00:52:33 -04:00
@ view ? . destroy ( )
@ tabView ? . destroy ( )
2014-02-11 18:38:36 -05:00
@thangs = null
2014-02-17 20:38:49 -05:00
@worker = null
2014-02-11 16:10:59 -05:00
2014-06-18 01:17:44 -04:00
setLanguage: (@language) ->
@originalSource = @ languages [ language ] ? @ languages . javascript
2014-01-03 13:32:13 -05:00
addThang: (thang) ->
2014-02-06 17:00:27 -05:00
if @ thangs [ thang . id ]
@ thangs [ thang . id ] . thang = thang
else
@ thangs [ thang . id ] = { thang: thang , aether: @ createAether ( thang ) , castAether: null }
2014-01-03 13:32:13 -05:00
2014-02-05 18:16:59 -05:00
removeThangID: (thangID) ->
delete @ thangs [ thangID ]
2014-01-03 13:32:13 -05:00
canRead: (team) ->
( team ? me . team ) in @ permissions . read or ( team ? me . team ) in @ permissions . readwrite
canWrite: (team) ->
( team ? me . team ) in @ permissions . readwrite
getSource: ->
2014-08-25 00:52:33 -04:00
@ view ? . getSource ( ) ? @ source
2014-01-03 13:32:13 -05:00
transpile: (source) ->
if source
@source = source
else
source = @ getSource ( )
2014-02-17 20:38:49 -05:00
[ pure , problems ] = [ null , null ]
2014-05-15 18:18:15 -04:00
if @ useTranspiledCode
transpiledCode = @ session . get ( ' code ' )
2014-02-17 20:38:49 -05:00
for thangID , spellThang of @ thangs
unless pure
2014-05-23 15:04:42 -04:00
if @ useTranspiledCode and transpiledSpell = transpiledCode [ @ spellKey . split ( ' / ' ) [ 0 ] ] ? [ @ name ]
2014-05-15 18:18:15 -04:00
spellThang.aether.pure = transpiledSpell
else
pure = spellThang . aether . transpile source
problems = spellThang . aether . problems
2014-06-30 22:16:26 -04:00
#console.log 'aether transpiled', source.length, 'to', spellThang.aether.pure.length, 'for', thangID, @spellKey
2014-02-17 20:38:49 -05:00
else
2014-07-19 23:26:13 -04:00
spellThang.aether.raw = source
2014-02-17 20:38:49 -05:00
spellThang.aether.pure = pure
spellThang.aether.problems = problems
2014-06-30 22:16:26 -04:00
#console.log 'aether reused transpilation for', thangID, @spellKey
2014-02-17 20:38:49 -05:00
null
2014-01-03 13:32:13 -05:00
hasChanged: (newSource=null, currentSource=null) ->
( newSource ? @ originalSource ) isnt ( currentSource ? @ source )
2014-04-22 11:54:35 -04:00
hasChangedSignificantly: (newSource=null, currentSource=null, cb) ->
2014-01-03 13:32:13 -05:00
for thangID , spellThang of @ thangs
aether = spellThang . aether
break
unless aether
2014-06-30 22:16:26 -04:00
console . error @ toString ( ) , ' couldn \' t find a spellThang with aether of ' , @ thangs
2014-04-22 14:04:56 -04:00
cb false
2014-04-22 11:54:35 -04:00
workerMessage =
2014-06-30 22:16:26 -04:00
function: ' hasChangedSignificantly '
2014-04-22 11:54:35 -04:00
a: ( newSource ? @ originalSource )
spellKey: @ spellKey
b: ( currentSource ? @ source )
careAboutLineNumbers: true
careAboutLint: true
2014-06-30 22:16:26 -04:00
@ worker . addEventListener ' message ' , (e) =>
2014-04-22 11:54:35 -04:00
workerData = JSON . parse e . data
2014-06-30 22:16:26 -04:00
if workerData . function is ' hasChangedSignificantly ' and workerData . spellKey is @ spellKey
@ worker . removeEventListener ' message ' , arguments . callee , false
2014-04-22 11:54:35 -04:00
cb ( workerData . hasChanged )
@ worker . postMessage JSON . stringify ( workerMessage )
2014-04-26 17:21:26 -04:00
2014-01-03 13:32:13 -05:00
createAether: (thang) ->
2014-03-28 09:42:08 -04:00
aceConfig = me . get ( ' aceConfig ' ) ? { }
2014-05-15 00:54:36 -04:00
writable = @ permissions . readwrite . length > 0
2014-01-03 13:32:13 -05:00
aetherOptions =
problems:
2014-06-30 22:16:26 -04:00
jshint_W040: { level: ' ignore ' }
jshint_W030: { level: ' ignore ' } # aether_NoEffect instead
jshint_W038: { level: ' ignore ' } # eliminates hoisting problems
jshint_W091: { level: ' ignore ' } # eliminates more hoisting problems
jshint_E043: { level: ' ignore ' } # https://github.com/codecombat/codecombat/issues/813 -- since we can't actually tell JSHint to really ignore things
jshint_Unknown: { level: ' ignore ' } # E043 also triggers Unknown, so ignore that, too
2014-05-08 14:43:00 -04:00
aether_MissingThis: { level: ' error ' }
2014-06-20 20:19:18 -04:00
language: @ language
2014-01-03 13:32:13 -05:00
functionName: @ name
functionParameters: @ parameters
yieldConditionally: thang . plan ?
2014-05-08 14:43:00 -04:00
globals: [ ' Vector ' , ' _ ' ]
2014-01-29 11:38:37 -05:00
# TODO: Gridmancer doesn't currently work with protectAPI, so hack it off
2014-06-30 22:16:26 -04:00
protectAPI: not ( @ skipProtectAPI or window . currentView ? . level . get ( ' name ' ) . match ( ' Gridmancer ' ) ) and writable # If anyone can write to this method, we must protect it.
2014-05-09 12:29:50 -04:00
includeFlow: false
2014-05-25 15:15:32 -04:00
executionLimit: 1 * 1000 * 1000
2014-06-30 22:16:26 -04:00
#console.log 'creating aether with options', aetherOptions
2014-01-03 13:32:13 -05:00
aether = new Aether aetherOptions
2014-04-18 17:59:08 -04:00
workerMessage =
2014-06-30 22:16:26 -04:00
function: ' createAether '
2014-04-18 17:59:08 -04:00
spellKey: @ spellKey
options: aetherOptions
@ worker . postMessage JSON . stringify workerMessage
2014-01-03 13:32:13 -05:00
aether
2014-05-15 00:54:36 -04:00
updateLanguageAether: (@language) ->
2014-03-16 21:14:04 -04:00
for thangId , spellThang of @ thangs
2014-05-15 00:54:36 -04:00
spellThang . aether ? . setLanguage @ language
2014-03-16 21:14:04 -04:00
spellThang.castAether = null
2014-06-26 01:56:39 -04:00
Backbone . Mediator . publish ' tome:spell-changed-language ' , spell: @ , language: @ language
2014-04-26 17:21:26 -04:00
workerMessage =
2014-06-30 22:16:26 -04:00
function: ' updateLanguageAether '
2014-05-15 00:54:36 -04:00
newLanguage: @ language
2014-04-18 17:59:08 -04:00
@ worker . postMessage JSON . stringify workerMessage
2014-03-16 21:14:04 -04:00
@ transpile ( )
2014-01-03 13:32:13 -05:00
toString: ->
" <Spell: #{ @ spellKey } > "
2014-06-18 01:17:44 -04:00
2014-06-20 20:19:18 -04:00
isEnemySpell: ->
2014-06-20 21:45:57 -04:00
return false unless @ permissions . readwrite . length
2014-06-21 15:24:14 -04:00
return false unless @ otherSession
2014-06-20 20:19:18 -04:00
teamSpells = @ session . get ( ' teamSpells ' )
team = @ session . get ( ' team ' ) ? ' humans '
teamSpells and not _ . contains ( teamSpells [ team ] , @ spellKey )
2014-06-18 01:17:44 -04:00
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.
2014-06-20 20:19:18 -04:00
return true if @ isEnemySpell ( ) # Use transpiled for enemy spells.
2014-06-18 01:17:44 -04:00
# 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 ' ) )
false
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
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 recieve code ' , e