2014-01-03 13:32:13 -05:00
View = require ' views/kinds/CocoView '
thangs_template = require ' templates/editor/level/thangs_tab '
Level = require ' models/Level '
ThangType = require ' models/ThangType '
LevelComponent = require ' models/LevelComponent '
CocoCollection = require ' models/CocoCollection '
{ isObjectID } = require ' models/CocoModel '
Surface = require ' lib/surface/Surface '
Thang = require ' lib/world/thang '
LevelThangEditView = require ' ./thang/edit '
# Moving the screen while dragging thangs constants
MOVE_MARGIN = 0.15
MOVE_SPEED = 13
# Essential component original ids
componentOriginals =
" existence.Exists " : " 524b4150ff92f1f4f8000024 "
" physics.Physical " : " 524b75ad7fc0f6d519000001 "
class ThangTypeSearchCollection extends CocoCollection
2014-01-20 20:20:04 -05:00
url: ' /db/thang_type/search?project=true '
2014-01-03 13:32:13 -05:00
model: ThangType
module.exports = class ThangsTabView extends View
id: " editor-level-thangs-tab-view "
className: ' tab-pane active '
template: thangs_template
startsLoading: true
subscriptions:
' surface:sprite-selected ' : ' onExtantThangSelected '
' surface:mouse-moved ' : ' onSurfaceMouseMoved '
' surface:mouse-over ' : ' onSurfaceMouseOver '
' surface:mouse-out ' : ' onSurfaceMouseOut '
' level-loaded ' : ' onLevelLoaded '
' edit-level-thang ' : ' editThang '
' level-thang-edited ' : ' onLevelThangEdited '
' level-thang-done-editing ' : ' onLevelThangDoneEditing '
' level:view-switched ' : ' onViewSwitched '
' sprite:mouse-down ' : ' onSpriteMouseDown '
' sprite:dragged ' : ' onSpriteDragged '
' sprite:mouse-up ' : ' onSpriteMouseUp '
' sprite:double-clicked ' : ' onSpriteDoubleClicked '
2014-01-06 17:53:21 -05:00
' surface:stage-mouse-down ' : ' onStageMouseDown '
2014-01-20 06:15:20 -05:00
2014-01-06 20:11:57 -05:00
shortcuts:
' esc ' : -> @ selectAddThang ( )
2014-01-03 13:32:13 -05:00
constructor: (options) ->
super options
@world = options . world
@thangTypes = @ supermodel . getCollection new ThangTypeSearchCollection ( ) # should load depended-on Components, too
@ thangTypes . once ' sync ' , @ onThangTypesLoaded
@ thangTypes . fetch ( )
onThangTypesLoaded: =>
@ supermodel . addCollection @ thangTypes
@ supermodel . populateModel model for model in @ thangTypes . models
@startsLoading = false
@ render ( ) # do it again but without the loading screen
@ onLevelLoaded level: @ level if @ level
getRenderData: (context={}) =>
context = super ( context )
2014-01-21 02:02:23 -05:00
thangTypes = ( thangType . attributes for thangType in @ supermodel . getModels ( ThangType ) )
thangTypes = _ . uniq thangTypes , false , ' original '
groupMap = { }
for thangType in thangTypes
groupMap [ thangType . kind ] ? = [ ]
groupMap [ thangType . kind ] . push thangType
groups = [ ]
for groupName in Object . keys ( groupMap ) . sort ( )
someThangTypes = groupMap [ groupName ]
someThangTypes = _ . sortBy someThangTypes , ' name '
group =
name: groupName
thangs: someThangTypes
groups . push group
context.thangTypes = thangTypes
context.groups = groups
2014-01-03 13:32:13 -05:00
context
afterRender: ->
return if @ startsLoading
super ( )
$ ( ' .tab-content ' ) . click @ selectAddThang
2014-01-20 06:15:20 -05:00
$ ( ' # thangs-list ' ) . bind ' mousewheel ' , @ preventBodyScrollingInThangList
2014-01-03 13:32:13 -05:00
key ' left ' , _ . bind @ moveAddThangSelection , @ , - 1
key ' right ' , _ . bind @ moveAddThangSelection , @ , 1
key ' delete, del, backspace ' , @ deleteSelectedExtantThang
key ' f ' , => Backbone . Mediator . publish ( ' level-set-debug ' , debug: not @ surface . debug )
key ' g ' , => Backbone . Mediator . publish ( ' level-set-grid ' , grid: not @ surface . gridShowing ( ) )
2014-01-20 06:15:20 -05:00
preventBodyScrollingInThangList: (e) ->
@ scrollTop += ( if e . deltaY < 0 then 1 else - 1 ) * 30
e . preventDefault ( )
2014-01-03 13:32:13 -05:00
onLevelLoaded: (e) ->
@level = e . level
return if @ startsLoading
data = $ . extend ( true , { } , @ level . attributes )
treemaOptions =
schema: Level . schema . get ( ' properties ' ) . thangs
data: data . thangs
supermodel: @ supermodel
callbacks:
change: @ onThangsChanged
select: @ onTreemaThangSelected
dblclick: @ onTreemaThangDoubleClicked
readOnly: true
nodeClasses:
thang: ThangNode
array: ThangsNode
world: @ world
@thangsTreema = @ $el . find ( ' # thangs-treema ' ) . treema treemaOptions
@ thangsTreema . build ( )
@ thangsTreema . open ( )
@ onThangsChanged ( ) # Initialize the World with Thangs
@ initSurface ( )
initSurface: ->
surfaceCanvas = $ ( ' canvas # surface ' , @ $el )
@surface = new Surface @ world , surfaceCanvas , {
wizards: false
paths: false
grid: true
navigateToSelection: false
thangTypes: @ supermodel . getModels ( ThangType )
showInvisible: true
}
@surface.playing = false
@ surface . setWorld @ world
@ surface . camera . zoomTo ( { x : 262 , y : - 164 } , 1.66 , 0 )
destroy: ->
super ( )
@ selectAddThangType null
@ surface . destroy ( )
onViewSwitched: (e) ->
@ surface ? . spriteBoss ? . selectSprite null , null
onSpriteMouseDown: (e) ->
2014-01-06 17:53:21 -05:00
# Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked.
clearTimeout @ backgroundAddClickTimeout
onStageMouseDown: (e) ->
if @ addThangSprite
# If we click on the background, we need to add @addThangSprite, but not if onSpriteMouseDown will fire.
@backgroundAddClickTimeout = _ . defer => @ onExtantThangSelected { }
2014-01-03 13:32:13 -05:00
onSpriteDragged: (e) ->
return unless @ selectedExtantThang and e . thang ? . id is @ selectedExtantThang ? . id
{ stageX , stageY } = e . originalEvent
wop = @ surface . camera . canvasToWorld x: stageX , y: stageY
wop.z = @ selectedExtantThang . depth / 2
@ adjustThangPos @ selectedExtantSprite , @ selectedExtantThang , wop
[ w , h ] = [ @ surface . camera . canvasWidth , @ surface . camera . canvasHeight ]
@ calculateMovement ( stageX / w , stageY / h , w / h )
onSpriteMouseUp: (e) ->
clearInterval ( @ movementInterval ) if @ movementInterval ?
@movementInterval = null
return unless @ selectedExtantThang and e . thang ? . id is @ selectedExtantThang ? . id
pos = @ selectedExtantThang . pos
physicalOriginal = componentOriginals [ " physics.Physical " ]
path = " id= #{ @ selectedExtantThang . id } /components/original= #{ physicalOriginal } " # TODO: hack
physical = @ thangsTreema . get path
return if not physical or ( physical . config . pos . x is pos . x and physical . config . pos . y is pos . y )
@ thangsTreema . set path + ' /config/pos ' , x: pos . x , y: pos . y , z: pos . z
onSpriteDoubleClicked: (e) ->
return unless e . thang
@ editThang thangID: e . thang . id
# TODO: figure out a good way to have all Surface clicks and Treema clicks just proxy in one direction, so we can maintain only one way of handling selection and deletion
onExtantThangSelected: (e) ->
@selectedExtantThang = e . thang
@selectedExtantSprite = e . sprite
if e . thang and ( key . alt or key . meta )
# We alt-clicked, so create a clone addThang
@ selectAddThangType e . thang . spriteName , @ selectedExtantThang
else if e . thang and not ( @ addThangSprite and @ addThangType is " Blood Torch Test " ) # TODO: figure out which Thangs can be placed on other Thangs
# We clicked on a Thang (or its Treema), so select the Thang
@ selectAddThang null
@selectedExtantThangClickTime = new Date ( )
treemaThang = _ . find @ thangsTreema . childrenTreemas , (treema) => treema . data . id is @ selectedExtantThang . id
if treemaThang
treemaThang . select ( ) unless treemaThang . isSelected ( )
else if @ addThangSprite
# We clicked on the background when we had an add Thang selected, so add it
@ addThang @ addThangType , @ addThangSprite . thang . pos
else
# We clicked on the background, so deselect anything selected
@ thangsTreema . deselectAll ( )
selectAddThang: (e) =>
if e then target = $ ( e . target ) else target = @ $el . find ( ' .add-thangs-palette ' ) # pretend to click on background if no event
return true if target . attr ( ' id ' ) is ' surface '
2014-01-06 20:11:57 -05:00
target = target . closest ( ' .add-thang-palette-icon ' )
2014-01-03 13:32:13 -05:00
wasSelected = target . hasClass ' selected '
@ $el . find ( ' .add-thangs-palette .add-thang-palette-icon.selected ' ) . removeClass ( ' selected ' )
@ selectAddThangType ( if wasSelected then null else target . attr ' data-thang-type ' )
target . addClass ( ' selected ' ) if @ addThangType
false
moveAddThangSelection: (direction) ->
return unless @ addThangType
icons = $ ( ' .add-thangs-palette .add-thang-palette-icon ' )
selectedIcon = icons . filter ( ' .selected ' )
selectedIndex = icons . index selectedIcon
nextSelectedIndex = ( selectedIndex + direction + icons . length ) % icons . length
@ selectAddThang { target: icons [ nextSelectedIndex ] }
selectAddThangType: (type, @cloneSourceThang) ->
if _ . isString type
type = _ . find @ supermodel . getModels ( ThangType ) , (m) -> m . get ( " name " ) is type
pos = @ addThangSprite ? . thang . pos # Maintain old sprite's pos if we have it
@ surface . spriteBoss . removeSprite @ addThangSprite if @ addThangSprite
@addThangType = type
if @ addThangType
@ surface . camera . lock ( ) # hmm, this interfere with zooming
thang = @ createAddThang ( )
@addThangSprite = @ surface . spriteBoss . addThangToSprites thang , @ surface . spriteBoss . spriteLayers [ " Floating " ]
@addThangSprite.notOfThisWorld = true
@addThangSprite.displayObject.alpha = 0.75
@ addThangSprite . playSound ? ' selected '
pos ? = x: Math . round ( @ world . width / 2 ) , y: Math . round ( @ world . height / 2 )
@ adjustThangPos @ addThangSprite , thang , pos
else
@ surface . camera . unlock ( )
@addThangSprite = null
createEssentialComponents: ->
[
{ original: componentOriginals [ " existence.Exists " ] , majorVersion: 0 , config: { } }
{ original: componentOriginals [ " physics.Physical " ] , majorVersion: 0 , config: { pos: { x: 10 , y: 10 , z: 1 } , width: 2 , height: 2 , depth: 2 , shape: " box " } }
]
createAddThang: ->
allComponents = ( lc . attributes for lc in @ supermodel . getModels LevelComponent )
rawComponents = @ addThangType . get ( ' components ' ) ? [ ]
rawComponents = @ createEssentialComponents ( ) unless rawComponents . length
mockThang = { components: rawComponents }
@ level . sortThangComponents [ mockThang ] , allComponents
components = [ ]
for raw in mockThang . components
comp = _ . find allComponents , { original: raw . original }
continue if comp . name in [ ' Selectable ' , ' Attackable ' ] # Don't draw health bars or intercept clicks
componentClass = @ world . loadClassFromCode comp . js , comp . name , " component "
components . push [ componentClass , raw . config ]
thang = new Thang @ world , @ addThangType . get ( ' name ' ) , " Add Thang Phantom "
thang . addComponents components . . .
thang
adjustThangPos: (sprite, thang, pos) ->
snap = sprite ? . data ? . snap or sprite ? . thangType ? . get ( ' snap ' ) or { x: 0.01 , y: 0.01 } # Centimeter resolution by default
pos.x = Math . round ( ( pos . x - thang . width / 2 ) / snap . x ) * snap . x + thang . width / 2
pos.y = Math . round ( ( pos . y - thang . height / 2 ) / snap . y ) * snap . y + thang . height / 2
pos.z = thang . depth / 2
thang.pos = pos
@ surface . spriteBoss . update true # Make sure Obstacle layer resets cache
onSurfaceMouseMoved: (e) ->
return unless @ addThangSprite
wop = @ surface . camera . canvasToWorld x: e . x , y: e . y
wop.z = 0.5
@ adjustThangPos @ addThangSprite , @ addThangSprite . thang , wop
null
onSurfaceMouseOver: (e) ->
return unless @ addThangSprite
@addThangSprite.displayObject.visible = true
onSurfaceMouseOut: (e) ->
return unless @ addThangSprite
@addThangSprite.displayObject.visible = false
calculateMovement: (pctX, pctY, widthHeightRatio) ->
MOVE_TOP_MARGIN = 1.0 - MOVE_MARGIN
if MOVE_TOP_MARGIN > pctX > MOVE_MARGIN and MOVE_TOP_MARGIN > pctY > MOVE_MARGIN
clearInterval ( @ movementInterval ) if @ movementInterval ?
@movementInterval = null
return @moveLatitude = @moveLongitude = @speed = 0
# calculating speed to be 0.0 to 1.0 within the movement buffer on the outer edge
diff = ( MOVE_MARGIN * 2 ) # comments are assuming MOVE_MARGIN is 0.1
@speed = Math . max ( Math . abs ( pctX - 0.5 ) , Math . abs ( pctY - 0.5 ) ) * 2 # pct is now 0.8 - 1.0
@ speed -= 1.0 - diff # 0.0 - 0.2
@ speed *= ( 1.0 / diff ) # 0.0 - 1.0
@ speed *= MOVE_SPEED
@moveLatitude = pctX * 2 - 1
@moveLongitude = pctY * 2 - 1
@ moveLongitude /= widthHeightRatio if widthHeightRatio > 1.0
@ moveLatitude *= widthHeightRatio if widthHeightRatio < 1.0
@movementInterval = setInterval ( @ moveSide , 16 ) unless @ movementInterval ?
moveSide: =>
return unless @ speed
c = @ surface . camera
p = { x : c . target . x + @ moveLatitude * @ speed / c . zoom , y : c . target . y + @ moveLongitude * @ speed / c . zoom }
c . zoomTo ( p , c . zoom , 0 )
deleteSelectedExtantThang: (e) =>
return if $ ( e . target ) . hasClass ' treema-node '
@ thangsTreema . onDeletePressed e
@ onTreemaThangSelected null , @ thangsTreema . getSelectedTreemas ( )
Thang . resetThangIDs ( ) # TODO: find some way to do this when we delete from treema, too
onThangsChanged: (e) =>
@ level . set ' thangs ' , @ thangsTreema . data
serializedLevel = @ level . serialize @ supermodel
@ world . loadFromLevel serializedLevel , false
thang.isSelectable = not thang . isLand for thang in @ world . thangs # let us select walls and such
@ surface ? . setWorld @ world
@ selectAddThangType @ addThangType if @ addThangType # make another addThang sprite, since the World just refreshed
Backbone . Mediator . publish ' level-thangs-changed ' , thangsData: @ thangsTreema . data
null
onTreemaThangSelected: (e, selectedTreemas) =>
selectedThangID = _ . last ( selectedTreemas ) ? . data . id
if selectedThangID isnt @ selectedExtantThang ? . id
@ surface . spriteBoss . selectThang selectedThangID
onTreemaThangDoubleClicked: (e, treema) =>
id = treema ? . data ? . id
@ editThang thangID: id if id
addThang: (thangType, pos) ->
thangID = Thang . nextID ( thangType . get ( ' name ' ) ) until thangID and not @ thangsTreema . get " id= #{ thangID } "
if @ cloneSourceThang
components = _ . cloneDeep @ thangsTreema . get " id= #{ @ cloneSourceThang . id } /components "
@ selectAddThang null
else
components = _ . cloneDeep thangType . get ( ' components ' ) ? [ ]
components = @ createEssentialComponents ( ) unless components . length
physical = _ . find components , (c) -> c . config ? . pos ?
physical.config.pos = x: pos . x , y: pos . y , z: physical . config . pos . z if physical
thang = thangType: thangType . get ( ' original ' ) , id: thangID , components: components
@ thangsTreema . insert ' ' , thang
2014-01-21 00:19:54 -05:00
@ supermodel . populateModel thangType # Make sure we grab any new data for the thang we just added
2014-01-03 13:32:13 -05:00
editThang: (e) ->
if e . target # click event
thangData = $ ( e . target ) . data ' thang-data '
else # Mediator event
window . thangsTreema = @ thangsTreema
thangData = @ thangsTreema . get " id= #{ e . thangID } "
@editThangView = new LevelThangEditView thangData: thangData , supermodel: @ supermodel , level: @ level , world: @ world
@ insertSubView @ editThangView
@ $el . find ( ' .thangs-column ' ) . addClass ( ' hide ' )
Backbone . Mediator . publish ' level:view-switched ' , e
onLevelThangEdited: (e) ->
newThang = e . thangData
@ thangsTreema . set " id= #{ e . id } " , newThang
onLevelThangDoneEditing: ->
@ removeSubView @ editThangView
@ $el . find ( ' .thangs-column ' ) . removeClass ( ' hide ' )
class ThangsNode extends TreemaNode . nodeMap . array
valueClass: ' treema-array-replacement '
getChildren: ->
children = super ( arguments . . . )
2014-01-21 00:19:54 -05:00
# TODO: add some filtering to only work with certain types of units at a time
2014-01-03 13:32:13 -05:00
return children
class ThangNode extends TreemaObjectNode
valueClass: ' treema-thang '
collection: false
2014-01-21 00:19:54 -05:00
@thangNameMap: { }
2014-01-03 13:32:13 -05:00
buildValueForDisplay: (valEl) ->
pos = _ . find ( @ data . components , (c) -> c . config ? . pos ? ) ? . config . pos # TODO: hack
s = " #{ @ data . thangType } "
if isObjectID s
2014-01-21 00:19:54 -05:00
unless name = ThangNode . thangNameMap [ s ]
thangType = _ . find @ settings . supermodel . getModels ( ThangType ) , (m) -> m . get ( ' original ' ) is s
name = ThangNode . thangNameMap [ s ] = thangType . get ' name '
s = name
2014-01-03 13:32:13 -05:00
s += " - " + @ data . id if @ data . id isnt s
if pos
s += " ( #{ Math . round ( pos . x ) } , #{ Math . round ( pos . y ) } ) "
else
s += " (non-physical) "
@ buildValueForDisplaySimply valEl , s
onEnterPressed: ->
Backbone . Mediator . publish ' edit-level-thang ' , levelThang: @ data . id