2014-07-17 20:20:11 -04:00
CocoView = require ' views/kinds/CocoView '
2014-07-23 10:02:45 -04:00
AddThangsView = require ' ./AddThangsView '
2014-09-02 17:52:56 -04:00
thangs_template = require ' templates/editor/level/thangs-tab-view '
2014-01-03 13:32:13 -05:00
Level = require ' models/Level '
ThangType = require ' models/ThangType '
LevelComponent = require ' models/LevelComponent '
2014-04-22 15:42:26 -04:00
CocoCollection = require ' collections/CocoCollection '
2014-01-03 13:32:13 -05:00
{ isObjectID } = require ' models/CocoModel '
Surface = require ' lib/surface/Surface '
Thang = require ' lib/world/thang '
2014-07-23 10:02:45 -04:00
LevelThangEditView = require ' ./LevelThangEditView '
2014-03-15 15:31:39 -04:00
ComponentsCollection = require ' collections/ComponentsCollection '
2014-01-03 13:32:13 -05:00
# Moving the screen while dragging thangs constants
MOVE_MARGIN = 0.15
MOVE_SPEED = 13
2014-09-01 23:07:50 -04:00
# Let us place these on top of other Thangs
2014-09-24 15:02:45 -04:00
overlappableThangTypeNames = [ ' Torch ' , ' Chains ' , ' Bird ' , ' Cloud 1 ' , ' Cloud 2 ' , ' Cloud 3 ' , ' Waterfall ' , ' Obstacle ' , ' Electrowall ' , ' Spike Walls ' ]
2014-09-01 23:07:50 -04:00
2014-01-03 13:32:13 -05:00
class ThangTypeSearchCollection extends CocoCollection
2014-06-09 10:18:26 -04:00
url: ' /db/thang.type?project=original,name,version,slug,kind,components '
2014-01-03 13:32:13 -05:00
model: ThangType
2014-07-17 20:20:11 -04:00
module.exports = class ThangsTabView extends CocoView
2014-09-02 17:52:56 -04:00
id: ' thangs-tab-view '
2014-01-03 13:32:13 -05:00
className: ' tab-pane active '
template: thangs_template
subscriptions:
' surface:sprite-selected ' : ' onExtantThangSelected '
' surface:mouse-moved ' : ' onSurfaceMouseMoved '
' surface:mouse-over ' : ' onSurfaceMouseOver '
' surface:mouse-out ' : ' onSurfaceMouseOut '
2014-08-27 15:24:03 -04:00
' editor:edit-level-thang ' : ' editThang '
2014-09-17 00:43:03 -04:00
' editor:level-thang-edited ' : ' onLevelThangEdited '
2014-08-27 15:24:03 -04:00
' editor:level-thang-done-editing ' : ' onLevelThangDoneEditing '
' editor:view-switched ' : ' onViewSwitched '
2014-01-03 13:32:13 -05:00
' sprite:dragged ' : ' onSpriteDragged '
' sprite:mouse-up ' : ' onSpriteMouseUp '
2014-09-02 17:06:37 -04:00
' sprite:mouse-down ' : ' onSpriteMouseDown '
2014-01-03 13:32:13 -05:00
' sprite:double-clicked ' : ' onSpriteDoubleClicked '
2014-09-15 17:54:21 -04:00
' surface:stage-mouse-down ' : ' onStageMouseDown '
2014-05-20 19:20:24 -04:00
' surface:stage-mouse-up ' : ' onStageMouseUp '
2014-08-27 15:24:03 -04:00
' editor:random-terrain-generated ' : ' onRandomTerrainGenerated '
2014-01-31 13:21:32 -05:00
2014-01-21 13:42:09 -05:00
events:
' click # extant-thangs-filter button ' : ' onFilterExtantThangs '
2014-03-30 13:38:54 -04:00
' click # delete ' : ' onDeleteClicked '
' click # duplicate ' : ' onDuplicateClicked '
2014-04-21 15:15:22 -04:00
' click # thangs-container-toggle ' : ' toggleThangsContainer '
2014-09-02 17:52:56 -04:00
' click # thangs-palette-toggle ' : ' toggleThangsPalette '
2014-04-25 22:11:32 -04:00
# 'click .add-thang-palette-icon': 'toggleThangsPalette'
2014-01-31 13:21:32 -05:00
2014-01-06 20:11:57 -05:00
shortcuts:
2014-02-24 12:58:12 -05:00
' esc ' : ' selectAddThang '
' delete, del, backspace ' : ' deleteSelectedExtantThang '
' left ' : -> @ moveAddThangSelection - 1
' right ' : -> @ moveAddThangSelection 1
2014-08-14 13:28:50 -04:00
' ctrl+z, ⌘+z ' : ' undo '
' ctrl+shift+z, ⌘+shift+z ' : ' redo '
2014-01-21 13:42:09 -05:00
2014-01-03 13:32:13 -05:00
constructor: (options) ->
super options
@world = options . world
2014-04-16 02:28:59 -04:00
2014-04-28 14:52:04 -04:00
# should load depended-on Components, too
@thangTypes = @ supermodel . loadCollection ( new ThangTypeSearchCollection ( ) , ' thangs ' ) . model
2014-03-15 15:31:39 -04:00
# just loading all Components for now: https://github.com/codecombat/codecombat/issues/405
2014-04-28 14:52:04 -04:00
@componentCollection = @ supermodel . loadCollection ( new ComponentsCollection ( ) , ' components ' ) . load ( )
2014-05-01 18:44:50 -04:00
@level = options . level
2014-04-28 14:52:04 -04:00
2014-04-25 22:11:32 -04:00
$ ( document ) . bind ' contextmenu ' , @ preventDefaultContextMenu
2014-05-29 15:33:21 -04:00
2014-02-11 17:58:45 -05:00
getRenderData: (context={}) ->
2014-01-03 13:32:13 -05:00
context = super ( context )
2014-04-25 22:11:32 -04:00
return context unless @ supermodel . finished ( )
2014-01-21 02:02:23 -05:00
thangTypes = ( thangType . attributes for thangType in @ supermodel . getModels ( ThangType ) )
thangTypes = _ . uniq thangTypes , false , ' original '
2014-01-31 13:21:32 -05:00
thangTypes = _ . reject thangTypes , kind: ' Mark '
2014-01-21 02:02:23 -05:00
groupMap = { }
for thangType in thangTypes
groupMap [ thangType . kind ] ? = [ ]
groupMap [ thangType . kind ] . push thangType
2014-01-31 13:21:32 -05:00
2014-01-21 02:02:23 -05:00
groups = [ ]
for groupName in Object . keys ( groupMap ) . sort ( )
someThangTypes = groupMap [ groupName ]
someThangTypes = _ . sortBy someThangTypes , ' name '
group =
name: groupName
thangs: someThangTypes
groups . push group
2014-01-31 13:21:32 -05:00
2014-01-21 02:02:23 -05:00
context.thangTypes = thangTypes
context.groups = groups
2014-01-03 13:32:13 -05:00
context
2014-08-14 13:28:50 -04:00
undo: (e) ->
if not @ editThangView then @ thangsTreema . undo ( ) else @ editThangView . undo ( )
redo: (e) ->
if not @ editThangView then @ thangsTreema . redo ( ) else @ editThangView . redo ( )
2014-03-18 00:47:53 -04:00
2014-01-03 13:32:13 -05:00
afterRender: ->
super ( )
2014-04-25 22:11:32 -04:00
return unless @ supermodel . finished ( )
2014-05-20 19:20:24 -04:00
$ ( ' .tab-content ' ) . mousedown @ selectAddThang
2014-01-20 06:15:20 -05:00
$ ( ' # thangs-list ' ) . bind ' mousewheel ' , @ preventBodyScrollingInThangList
2014-01-21 13:42:09 -05:00
@ $el . find ( ' # extant-thangs-filter button:first ' ) . button ( ' toggle ' )
2014-08-29 20:52:47 -04:00
$ ( window ) . on ' resize ' , @ onWindowResize
2014-08-18 18:25:22 -04:00
@addThangsView = @ insertSubView new AddThangsView world: @ world
2014-05-01 18:44:50 -04:00
@ buildInterface ( ) # refactor to not have this trigger when this view re-renders?
2014-09-03 15:30:08 -04:00
if _ . keys ( @ thangsTreema . data ) . length
2014-07-25 16:21:10 -04:00
@ $el . find ( ' # canvas-overlay ' ) . css ( ' display ' , ' none ' )
2014-01-31 13:21:32 -05:00
2014-02-24 12:58:12 -05:00
onFilterExtantThangs: (e) ->
2014-03-13 02:49:51 -04:00
@ $el . find ( ' # extant-thangs-filter button.active ' ) . button ( ' toggle ' )
2014-02-24 12:58:12 -05:00
button = $ ( e . target ) . closest ( ' button ' )
button . button ( ' toggle ' )
val = button . val ( )
@ thangsTreema . $el . removeClass ( @ lastHideClass ) if @ lastHideClass
@ thangsTreema . $el . addClass ( @lastHideClass = " hide-except- #{ val } " ) if val
2014-01-03 13:32:13 -05:00
2014-01-20 06:15:20 -05:00
preventBodyScrollingInThangList: (e) ->
@ scrollTop += ( if e . deltaY < 0 then 1 else - 1 ) * 30
e . preventDefault ( )
2014-05-01 18:44:50 -04:00
buildInterface: (e) ->
2014-04-25 22:11:32 -04:00
@level = e . level if e
2014-04-16 02:28:59 -04:00
2014-09-03 15:30:08 -04:00
data = $ . extend ( true , [ ] , @ level . attributes . thangs ? [ ] )
thangsObject = @ groupThangs ( data )
schema = {
type: ' object '
format: ' thangs-folder '
additionalProperties: {
anyOf: [
2014-09-03 17:46:24 -04:00
{
type: ' object '
format: ' thang '
2014-09-03 18:38:34 -04:00
required: [ ' thangType ' , ' id ' ]
2014-09-03 17:46:24 -04:00
}
2014-09-03 15:30:08 -04:00
{ $ref: ' # ' }
]
}
}
2014-01-03 13:32:13 -05:00
treemaOptions =
2014-09-03 15:30:08 -04:00
schema: schema
data: thangsObject
skipValidation: true
2014-01-03 13:32:13 -05:00
supermodel: @ supermodel
callbacks:
change: @ onThangsChanged
select: @ onTreemaThangSelected
dblclick: @ onTreemaThangDoubleClicked
readOnly: true
nodeClasses:
thang: ThangNode
2014-09-03 15:30:08 -04:00
' thangs-folder ' : ThangsFolderNode
2014-01-03 13:32:13 -05:00
world: @ world
2014-04-16 02:28:59 -04:00
2014-01-03 13:32:13 -05:00
@thangsTreema = @ $el . find ( ' # thangs-treema ' ) . treema treemaOptions
@ thangsTreema . build ( )
@ thangsTreema . open ( )
2014-09-03 19:44:59 -04:00
@ openSmallerFolders ( @ thangsTreema )
2014-09-08 15:29:49 -04:00
2014-01-03 13:32:13 -05:00
@ onThangsChanged ( ) # Initialize the World with Thangs
@ initSurface ( )
2014-03-18 00:47:53 -04:00
thangsHeaderHeight = $ ( ' # thangs-header ' ) . height ( )
oldHeight = $ ( ' # thangs-list ' ) . height ( )
$ ( ' # thangs-list ' ) . height ( oldHeight - thangsHeaderHeight )
2014-09-03 15:30:08 -04:00
if data ? . length
2014-09-02 20:52:44 -04:00
@ $el . find ( ' .generate-terrain-button ' ) . hide ( )
2014-09-08 15:29:49 -04:00
2014-09-03 19:44:59 -04:00
openSmallerFolders: (folderTreema) ->
children = _ . values folderTreema . childrenTreemas
for child in children
continue if child . data . thangType
if _ . keys ( child . data ) . length < 5
child . open ( )
2014-09-08 15:29:49 -04:00
@ openSmallerFolders ( child )
2014-01-03 13:32:13 -05:00
initSurface: ->
2014-10-01 16:26:38 -04:00
webGLCanvas = $ ( ' canvas # webgl-surface ' , @ $el )
normalCanvas = $ ( ' canvas # normal-surface ' , @ $el )
@surface = new Surface @ world , normalCanvas , webGLCanvas , {
2014-01-03 13:32:13 -05:00
wizards: false
paths: false
2014-09-23 21:39:52 -04:00
coords: true
2014-01-03 13:32:13 -05:00
grid: true
navigateToSelection: false
thangTypes: @ supermodel . getModels ( ThangType )
showInvisible: true
2014-02-28 20:12:23 -05:00
frameRate: 15
2014-01-03 13:32:13 -05:00
}
@surface.playing = false
@ surface . setWorld @ world
2014-09-02 21:08:35 -04:00
@ centerCamera ( )
centerCamera: ->
[ width , height ] = @ world . size ( )
width = Math . max width , 80
height = Math . max height , 68
{ left , top , right , bottom } = @ world . getBounds ( )
center = x: left + width / 2 , y: bottom + height / 2
sup = @ surface . camera . worldToSurface center
zoom = 0.94 * 92.4 / width # Zoom 1.0 lets us see 92.4 meters.
@ surface . camera . zoomTo ( sup , zoom , 0 )
2014-01-03 13:32:13 -05:00
destroy: ->
@ selectAddThangType null
2014-10-03 13:44:02 -04:00
@ surface ? . destroy ( )
2014-08-29 20:52:47 -04:00
$ ( window ) . off ' resize ' , @ onWindowResize
2014-04-15 15:31:35 -04:00
$ ( document ) . unbind ' contextmenu ' , @ preventDefaultContextMenu
2014-08-29 21:02:29 -04:00
@ thangsTreema ? . destroy ( )
2014-02-14 13:57:47 -05:00
super ( )
2014-01-03 13:32:13 -05:00
onViewSwitched: (e) ->
2014-09-01 23:07:50 -04:00
@ selectAddThang null , true
2014-10-01 16:26:38 -04:00
@ surface ? . lankBoss ? . selectLank null , null
2014-01-03 13:32:13 -05:00
onSpriteMouseDown: (e) ->
2014-09-02 17:06:37 -04:00
@dragged = false
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.
2014-05-20 19:29:23 -04:00
# clearTimeout @backgroundAddClickTimeout
# if e.originalEvent.nativeEvent.button == 2
# @onSpriteContextMenu e
2014-01-06 17:53:21 -05:00
2014-09-15 17:54:21 -04:00
onStageMouseDown: (e) ->
2014-10-01 16:26:38 -04:00
return unless @ addThangLank ? . thangType . get ( ' kind ' ) is ' Wall '
2014-09-15 17:54:21 -04:00
@surface.camera.dragDisabled = true
@paintingWalls = true
2014-05-20 19:20:24 -04:00
onStageMouseUp: (e) ->
2014-09-15 17:54:21 -04:00
if @ paintingWalls
# We need to stop painting walls, but we may also stop in onExtantThangSelected.
_ . defer =>
2014-09-15 18:23:09 -04:00
@paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
2014-10-01 16:26:38 -04:00
else if @ addThangLank
2014-06-11 16:20:24 -04:00
@ surface . camera . lock ( )
2014-10-01 16:26:38 -04:00
# If we click on the background, we need to add @addThangLank, but not if onSpriteMouseUp will fire.
2014-01-06 17:53:21 -05:00
@backgroundAddClickTimeout = _ . defer => @ onExtantThangSelected { }
2014-03-31 12:33:14 -04:00
$ ( ' # contextmenu ' ) . hide ( )
2014-01-03 13:32:13 -05:00
onSpriteDragged: (e) ->
return unless @ selectedExtantThang and e . thang ? . id is @ selectedExtantThang ? . id
2014-09-02 17:06:37 -04:00
@dragged = true
2014-03-14 09:03:57 -04:00
@surface.camera.dragDisabled = true
2014-01-03 13:32:13 -05:00
{ stageX , stageY } = e . originalEvent
2014-09-11 14:23:58 -04:00
cap = @ surface . camera . screenToCanvas x: stageX , y: stageY
wop = @ surface . camera . canvasToWorld cap
2014-01-03 13:32:13 -05:00
wop.z = @ selectedExtantThang . depth / 2
2014-10-01 16:26:38 -04:00
@ adjustThangPos @ selectedExtantLank , @ selectedExtantThang , wop
2014-01-03 13:32:13 -05:00
[ w , h ] = [ @ surface . camera . canvasWidth , @ surface . camera . canvasHeight ]
2014-09-11 14:23:58 -04:00
sidebarWidths = ( ( if @ $el . find ( id ) . hasClass ( ' hide ' ) then 0 else ( @ $el . find ( id ) . outerWidth ( ) / @ surface . camera . canvasScaleFactorX ) ) for id in [ ' # all-thangs ' , ' # add-thangs-view ' ] )
w -= sidebarWidth for sidebarWidth in sidebarWidths
cap . x -= sidebarWidths [ 0 ]
@ calculateMovement ( cap . x / w , cap . y / h , w / h )
2014-01-03 13:32:13 -05:00
onSpriteMouseUp: (e) ->
2014-05-20 19:29:23 -04:00
clearTimeout @ backgroundAddClickTimeout
2014-06-11 16:20:24 -04:00
@ surface . camera . unlock ( )
2014-06-10 18:09:56 -04:00
if e . originalEvent . nativeEvent . button == 2 and @ selectedExtantThang
2014-05-20 19:29:23 -04:00
@ onSpriteContextMenu e
2014-01-03 13:32:13 -05:00
clearInterval ( @ movementInterval ) if @ movementInterval ?
@movementInterval = null
2014-03-14 09:03:57 -04:00
@surface.camera.dragDisabled = false
2014-01-03 13:32:13 -05:00
return unless @ selectedExtantThang and e . thang ? . id is @ selectedExtantThang ? . id
pos = @ selectedExtantThang . pos
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
thang = _ . find ( @ level . get ( ' thangs ' ) ? [ ] , { id: @ selectedExtantThang . id } )
path = " #{ @ pathForThang ( thang ) } /components/original= #{ LevelComponent . PhysicalID } "
2014-01-03 13:32:13 -05:00
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) ->
2014-09-02 17:06:37 -04:00
return unless e . thang and not @ dragged
2014-01-03 13:32:13 -05:00
@ editThang thangID: e . thang . id
2014-08-27 15:24:03 -04:00
onRandomTerrainGenerated: (e) ->
2014-07-13 15:58:21 -04:00
@thangsBatch = [ ]
2014-09-03 15:30:08 -04:00
@hush = true
nonRandomThangs = ( thang for thang in @ flattenThangs ( @ thangsTreema . data ) when not /Random/ . test thang . id )
@ thangsTreema . set ' ' , @ groupThangs ( nonRandomThangs )
2014-09-02 22:47:43 -04:00
listening = { }
2014-06-26 09:02:31 -04:00
for thang in e . thangs
2014-06-26 12:10:11 -04:00
@ selectAddThangType thang . id
2014-09-02 22:47:43 -04:00
# kind of a hack to get the walls to show up correctly when they load.
# might also fix other thangs who need to show up looking a certain way based on thang type components
unless @ addThangType . isFullyLoaded ( ) or listening [ @ addThangType . cid ]
listening [ @ addThangType . cid ] = true
@ listenToOnce @ addThangType , ' build-complete ' , @ onThangsChanged
2014-09-08 15:29:49 -04:00
2014-07-13 15:58:21 -04:00
@ addThang @ addThangType , thang . pos , true
2014-09-03 15:30:08 -04:00
@hush = false
@ onThangsChanged ( )
2014-07-10 15:37:03 -04:00
@ selectAddThangType null
2014-07-25 16:21:10 -04:00
2014-06-26 09:02:31 -04:00
2014-01-03 13:32:13 -05:00
# 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) ->
2014-10-01 16:26:38 -04:00
@ selectedExtantLank ? . setNameLabel ? null unless @ selectedExtantLank is e . sprite
2014-01-03 13:32:13 -05:00
@selectedExtantThang = e . thang
2014-10-01 16:26:38 -04:00
@selectedExtantLank = e . sprite
2014-09-15 18:23:09 -04:00
paintedAWall = @ paintedWalls
@paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
if paintedAWall
# Skip adding a wall now, because we already dragged to add one
null
2014-09-15 17:54:21 -04:00
else if e . thang and ( key . alt or key . meta )
2014-01-03 13:32:13 -05:00
# We alt-clicked, so create a clone addThang
@ selectAddThangType e . thang . spriteName , @ selectedExtantThang
2014-09-01 23:07:50 -04:00
else if @ justAdded ( )
2014-09-01 23:22:46 -04:00
# Skip double insert due to extra selection event
2014-09-01 23:07:50 -04:00
null
2014-10-01 16:26:38 -04:00
else if e . thang and not ( @ addThangLank and @ addThangType . get ( ' name ' ) in overlappableThangTypeNames )
2014-01-03 13:32:13 -05:00
# We clicked on a Thang (or its Treema), so select the Thang
2014-09-01 23:07:50 -04:00
@ selectAddThang null , true
2014-01-03 13:32:13 -05:00
@selectedExtantThangClickTime = new Date ( )
2014-09-03 15:30:08 -04:00
# Show the label above selected thang, notice that we may get here from thang-edit-view, so it will be selected but no label
2014-10-01 16:26:38 -04:00
@ selectedExtantLank . setNameLabel @ selectedExtantLank . thangType . get ( ' name ' ) + ' : ' + @ selectedExtantThang . id
else if @ addThangLank
2014-01-03 13:32:13 -05:00
# We clicked on the background when we had an add Thang selected, so add it
2014-10-01 16:26:38 -04:00
@ addThang @ addThangType , @ addThangLank . thang . pos
2014-09-01 23:07:50 -04:00
@lastAddTime = new Date ( )
2014-01-31 13:21:32 -05:00
2014-09-01 23:07:50 -04:00
justAdded: -> @ lastAddTime and ( new Date ( ) - @ lastAddTime ) < 150
2014-01-03 13:32:13 -05:00
2014-09-01 23:07:50 -04:00
selectAddThang: (e, forceDeselect=false) =>
2014-05-30 06:38:43 -04:00
return if e ? and $ ( e . target ) . closest ( ' # thang-search ' ) . length # Ignore if you're trying to search thangs
2014-09-02 17:52:56 -04:00
return unless ( e ? and $ ( e . target ) . closest ( ' # thangs-tab-view ' ) . length ) or key . isPressed ( ' esc ' ) or forceDeselect
2014-01-03 13:32:13 -05:00
if e then target = $ ( e . target ) else target = @ $el . find ( ' .add-thangs-palette ' ) # pretend to click on background if no event
2014-10-01 18:02:14 -04:00
return true if target . attr ( ' id ' ) is ' webgl-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 ' )
2014-01-28 12:22:23 -05:00
@ selectAddThangType ( if wasSelected then null else target . attr ' data-thang-type ' ) unless key . alt or key . meta
2014-01-03 13:32:13 -05:00
target . addClass ( ' selected ' ) if @ addThangType
2014-05-30 19:43:55 -04:00
#false # was causing #1099, any reason to keep?
2014-01-03 13:32:13 -05:00
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
2014-06-30 22:16:26 -04:00
type = _ . find @ supermodel . getModels ( ThangType ) , (m) -> m . get ( ' name ' ) is type
2014-10-01 16:26:38 -04:00
pos = @ addThangLank ? . thang . pos # Maintain old sprite's pos if we have it
@ surface . lankBoss . removeLank @ addThangLank if @ addThangLank
2014-01-03 13:32:13 -05:00
@addThangType = type
if @ addThangType
2014-10-03 14:54:06 -04:00
@surface.lankBoss.reallyStopMoving = true
2014-01-03 13:32:13 -05:00
thang = @ createAddThang ( )
2014-10-01 16:26:38 -04:00
@addThangLank = @ surface . lankBoss . addThangToLanks thang , @ surface . lankBoss . layerAdapters [ ' Floating ' ]
@addThangLank.notOfThisWorld = true
@addThangLank.sprite.alpha = 0.75
@ addThangLank . playSound ? ' selected '
2014-01-03 13:32:13 -05:00
pos ? = x: Math . round ( @ world . width / 2 ) , y: Math . round ( @ world . height / 2 )
2014-10-01 16:26:38 -04:00
@ adjustThangPos @ addThangLank , thang , pos
2014-01-03 13:32:13 -05:00
else
2014-10-01 16:26:38 -04:00
@addThangLank = null
2014-10-03 14:54:06 -04:00
@surface.lankBoss.reallyStopMoving = false
2014-01-03 13:32:13 -05:00
2014-08-15 15:09:56 -04:00
createEssentialComponents: (defaultComponents) ->
physicalConfig = { pos: { x: 10 , y: 10 , z: 1 } }
2014-09-02 14:29:24 -04:00
if physicalOriginal = _ . find ( defaultComponents ? [ ] , original: LevelComponent . PhysicalID )
2014-08-28 22:39:46 -04:00
physicalConfig.pos.z = physicalOriginal . config ? . pos ? . z ? 1 # Get the z right
2014-01-03 13:32:13 -05:00
[
2014-09-02 14:29:24 -04:00
{ original: LevelComponent . ExistsID , majorVersion: 0 , config: { } }
{ original: LevelComponent . PhysicalID , majorVersion: 0 , config: physicalConfig }
2014-01-03 13:32:13 -05:00
]
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
2014-06-30 22:16:26 -04:00
componentClass = @ world . loadClassFromCode comp . js , comp . name , ' component '
2014-01-03 13:32:13 -05:00
components . push [ componentClass , raw . config ]
2014-06-30 22:16:26 -04:00
thang = new Thang @ world , @ addThangType . get ( ' name ' ) , ' Add Thang Phantom '
2014-01-03 13:32:13 -05:00
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
2014-08-14 18:09:10 -04:00
pos.x = Math . round ( ( pos . x - ( thang . width ? 1 ) / 2 ) / snap . x ) * snap . x + ( thang . width ? 1 ) / 2
pos.y = Math . round ( ( pos . y - ( thang . height ? 1 ) / 2 ) / snap . y ) * snap . y + ( thang . height ? 1 ) / 2
2014-01-03 13:32:13 -05:00
pos.z = thang . depth / 2
thang.pos = pos
2014-10-01 18:02:14 -04:00
thang.stateChanged = true
2014-10-01 16:26:38 -04:00
@ surface . lankBoss . update true # Make sure Obstacle layer resets cache
2014-01-03 13:32:13 -05:00
onSurfaceMouseMoved: (e) ->
2014-10-01 16:26:38 -04:00
return unless @ addThangLank
2014-05-30 18:06:33 -04:00
wop = @ surface . camera . screenToWorld x: e . x , y: e . y
2014-01-03 13:32:13 -05:00
wop.z = 0.5
2014-10-01 16:26:38 -04:00
@ adjustThangPos @ addThangLank , @ addThangLank . thang , wop
2014-09-15 17:54:21 -04:00
if @ paintingWalls
2014-10-01 16:26:38 -04:00
unless _ . find @ surface . lankBoss . lankArray , ( (lank) =>
lank . thangType . get ( ' kind ' ) is ' Wall ' and
Math . abs ( lank . thang . pos . x - @ addThangLank . thang . pos . x ) < 2 and
Math . abs ( lank . thang . pos . y - @ addThangLank . thang . pos . y ) < 2 and
lank isnt @ addThangLank
2014-09-15 17:54:21 -04:00
)
2014-10-01 16:26:38 -04:00
@ addThang @ addThangType , @ addThangLank . thang . pos
2014-09-15 17:54:21 -04:00
@lastAddTime = new Date ( )
2014-09-15 18:23:09 -04:00
@paintedWalls = true
2014-01-03 13:32:13 -05:00
null
onSurfaceMouseOver: (e) ->
2014-10-01 16:26:38 -04:00
return unless @ addThangLank
@addThangLank.sprite.visible = true
2014-01-03 13:32:13 -05:00
onSurfaceMouseOut: (e) ->
2014-10-01 16:26:38 -04:00
return unless @ addThangLank
@addThangLank.sprite.visible = false
2014-01-03 13:32:13 -05:00
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
2014-06-30 22:16:26 -04:00
p = { x: c . target . x + @ moveLatitude * @ speed / c . zoom , y: c . target . y + @ moveLongitude * @ speed / c . zoom }
2014-01-03 13:32:13 -05:00
c . zoomTo ( p , c . zoom , 0 )
deleteSelectedExtantThang: (e) =>
return if $ ( e . target ) . hasClass ' treema-node '
2014-09-15 18:24:11 -04:00
return unless @ selectedExtantThang
2014-09-03 15:30:08 -04:00
thang = @ getThangByID ( @ selectedExtantThang . id )
@ thangsTreema . delete ( @ pathForThang ( thang ) )
2014-01-03 13:32:13 -05:00
Thang . resetThangIDs ( ) # TODO: find some way to do this when we delete from treema, too
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
groupThangs: (thangs) ->
# array of thangs -> foldered thangs
grouped = { }
2014-09-03 17:46:24 -04:00
for thang , index in thangs
2014-09-03 15:30:08 -04:00
path = @ folderForThang ( thang )
obj = grouped
for key in path
obj [ key ] ? = { }
obj = obj [ key ]
obj [ thang . id ] = thang
2014-09-03 17:46:24 -04:00
thang.index = index
2014-09-03 15:30:08 -04:00
grouped
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
folderForThang: (thang) ->
thangType = @ supermodel . getModelByOriginal ThangType , thang . thangType
2014-09-03 18:38:34 -04:00
[ thangType . get ( ' kind ' ) , thangType . get ( ' name ' ) ]
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
pathForThang: (thang) ->
folder = @ folderForThang ( thang )
folder . push thang . id
folder . join ( ' / ' )
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
flattenThangs: (thangs) ->
2014-09-08 15:29:49 -04:00
# foldered thangs -> array of thangs
2014-09-03 15:30:08 -04:00
flattened = [ ]
for key , value of thangs
if value . id and value . thangType
flattened . push value
else
flattened = flattened . concat @ flattenThangs ( value )
flattened
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
populateFoldersForThang: (thang) ->
thangFolder = @ folderForThang ( thang )
prefix = ' '
for segment in thangFolder
if prefix then prefix += ' / '
prefix += segment
if not @ thangsTreema . get ( prefix ) then @ thangsTreema . set ( prefix , { } )
onThangsChanged: =>
return if @ hush
2014-09-03 17:46:24 -04:00
# keep the thangs in the same order as before, roughly
thangs = @ flattenThangs ( @ thangsTreema . data )
thangs = $ . extend true , [ ] , thangs
thangs = _ . sortBy thangs , ' index '
delete thang . index for thang in thangs
@ level . set ' thangs ' , thangs
2014-03-14 14:58:29 -04:00
return if @ editThangView
2014-09-03 12:33:21 -04:00
serializedLevel = @ level . serialize @ supermodel , null , true
2014-02-03 17:01:20 -05:00
try
@ world . loadFromLevel serializedLevel , false
catch error
console . error ' Catastrophic error loading the level: ' , error
2014-01-03 13:32:13 -05:00
thang.isSelectable = not thang . isLand for thang in @ world . thangs # let us select walls and such
@ surface ? . setWorld @ world
2014-10-03 14:21:05 -04:00
@ surface ? . lankBoss.cachedObstacles = false
2014-01-28 12:22:23 -05:00
@ selectAddThangType @ addThangType , @ cloneSourceThang if @ addThangType # make another addThang sprite, since the World just refreshed
2014-09-02 19:02:30 -04:00
2014-09-02 14:53:03 -04:00
# update selection, since the thangs have been remade
if @ selectedExtantThang
2014-10-01 16:26:38 -04:00
@selectedExtantLank = @ surface . lankBoss . lanks [ @ selectedExtantThang . id ]
@selectedExtantThang = @ selectedExtantLank ? . thang
2014-08-27 21:43:17 -04:00
Backbone . Mediator . publish ' editor:thangs-edited ' , thangs: @ world . thangs
2014-01-03 13:32:13 -05:00
onTreemaThangSelected: (e, selectedTreemas) =>
selectedThangID = _ . last ( selectedTreemas ) ? . data . id
if selectedThangID isnt @ selectedExtantThang ? . id
2014-10-01 16:26:38 -04:00
@ surface . lankBoss . selectThang selectedThangID , null , true
2014-01-03 13:32:13 -05:00
onTreemaThangDoubleClicked: (e, treema) =>
id = treema ? . data ? . id
@ editThang thangID: id if id
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
getThangByID: (id) -> _ . find ( @ level . get ( ' thangs ' ) ? [ ] , { id: id } )
2014-07-13 15:58:21 -04:00
2014-07-13 21:38:24 -04:00
addThang: (thangType, pos, batchInsert=false) ->
2014-09-02 20:52:44 -04:00
@ $el . find ( ' .generate-terrain-button ' ) . hide ( )
2014-07-13 21:38:24 -04:00
if batchInsert
2014-09-01 23:22:46 -04:00
if thangType . get ( ' name ' ) is ' Hero Placeholder '
thangID = ' Hero Placeholder '
2014-09-03 15:30:08 -04:00
return if @ level . get ( ' type ' , true ) isnt ' hero ' or @ getThangByID ( thangID )
2014-09-01 23:22:46 -04:00
else
thangID = " Random #{ thangType . get ( ' name ' ) } #{ @ thangsBatch . length } "
2014-07-13 21:38:24 -04:00
else
2014-09-03 15:30:08 -04:00
thangID = Thang . nextID ( thangType . get ( ' name ' ) , @ world ) until thangID and not @ getThangByID ( thangID )
2014-01-03 13:32:13 -05:00
if @ cloneSourceThang
2014-09-03 15:30:08 -04:00
components = _ . cloneDeep @ getThangByID ( @ cloneSourceThang . id ) . components
2014-09-02 19:02:30 -04:00
else if @ level . get ( ' type ' , true ) is ' hero '
2014-08-15 15:09:56 -04:00
components = [ ] # Load them all from default ThangType Components
2014-01-03 13:32:13 -05:00
else
components = _ . cloneDeep thangType . get ( ' components ' ) ? [ ]
2014-08-15 15:09:56 -04:00
components = @ createEssentialComponents ( thangType . get ( ' components ' ) ) unless components . length
2014-01-03 13:32:13 -05:00
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
2014-07-13 15:58:21 -04:00
if batchInsert
@ thangsBatch . push thang
2014-09-03 15:30:08 -04:00
@ populateFoldersForThang ( thang )
@ thangsTreema . set ( @ pathForThang ( thang ) , thang )
2014-01-03 13:32:13 -05:00
editThang: (e) ->
if e . target # click event
thangData = $ ( e . target ) . data ' thang-data '
else # Mediator event
2014-09-03 15:30:08 -04:00
thangData = @ getThangByID ( e . thangID )
2014-10-01 16:26:38 -04:00
return unless thangData
2014-09-03 15:30:08 -04:00
@editThangView = new LevelThangEditView thangData: thangData , level: @ level , world: @ world , supermodel: @ supermodel , oldPath: @ pathForThang ( thangData ) # supermodel needed for checkForMissingSystems
2014-01-03 13:32:13 -05:00
@ insertSubView @ editThangView
2014-09-02 17:52:56 -04:00
@ $el . find ( ' > ' ) . hide ( )
@ editThangView . $el . show ( )
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' editor:view-switched ' , { }
2014-01-03 13:32:13 -05:00
2014-08-27 15:24:03 -04:00
onLevelThangDoneEditing: (e) ->
2014-01-03 13:32:13 -05:00
@ removeSubView @ editThangView
2014-03-14 14:58:29 -04:00
@editThangView = null
2014-09-17 00:43:03 -04:00
@ updateEditedThang e . thangData , e . oldPath
@ $el . find ( ' > ' ) . show ( )
onLevelThangEdited: (e) ->
@ updateEditedThang e . thangData , e . oldPath
updateEditedThang: (newThang, oldPath) ->
2014-09-03 15:30:08 -04:00
@hush = true
2014-09-17 00:43:03 -04:00
@ thangsTreema . delete oldPath
2014-09-03 15:30:08 -04:00
@ populateFoldersForThang ( newThang )
@ thangsTreema . set ( @ pathForThang ( newThang ) , newThang )
@hush = false
2014-03-14 14:58:29 -04:00
@ onThangsChanged ( )
2014-04-17 13:12:23 -04:00
2014-03-30 13:38:54 -04:00
preventDefaultContextMenu: (e) ->
2014-04-15 15:27:35 -04:00
return unless $ ( e . target ) . closest ( ' # canvas-wrapper ' ) . length
2014-03-30 13:38:54 -04:00
e . preventDefault ( )
2014-04-17 13:12:23 -04:00
2014-03-30 13:38:54 -04:00
onSpriteContextMenu: (e) ->
{ clientX , clientY } = e . originalEvent . nativeEvent
if @ addThangType
$ ( ' # duplicate a ' ) . html ' Stop Duplicate '
else
$ ( ' # duplicate a ' ) . html ' Duplicate '
$ ( ' # contextmenu ' ) . css { position: ' fixed ' , left: clientX , top: clientY }
$ ( ' # contextmenu ' ) . show ( )
2014-04-17 13:12:23 -04:00
2014-03-30 13:38:54 -04:00
onDeleteClicked: (e) ->
$ ( ' # contextmenu ' ) . hide ( )
@ deleteSelectedExtantThang e
2014-04-17 13:12:23 -04:00
2014-03-30 13:38:54 -04:00
onDuplicateClicked: (e) ->
$ ( ' # contextmenu ' ) . hide ( )
2014-05-29 15:33:21 -04:00
@ selectAddThangType @ selectedExtantThang . spriteName , @ selectedExtantThang
2014-04-21 15:15:22 -04:00
toggleThangsContainer: (e) ->
2014-09-02 17:52:56 -04:00
$ ( ' # all-thangs ' ) . toggleClass ( ' hide ' )
2014-05-29 15:33:21 -04:00
2014-04-21 15:15:22 -04:00
toggleThangsPalette: (e) ->
2014-09-02 17:52:56 -04:00
$ ( ' # add-thangs-view ' ) . toggleClass ( ' hide ' )
2014-05-29 15:33:21 -04:00
2014-09-03 15:30:08 -04:00
class ThangsFolderNode extends TreemaNode . nodeMap . object
valueClass: ' treema-thangs-folder '
2014-08-12 19:58:23 -04:00
nodeDescription: ' Thang '
2014-09-03 18:38:34 -04:00
@nameToThangTypeMap: null
2014-09-08 15:29:49 -04:00
2014-08-12 19:58:23 -04:00
getTrackedActionDescription: (trackedAction) ->
trackedActionDescription = super ( trackedAction )
if trackedActionDescription is ' Edit ' + @ nodeDescription
path = trackedAction . path . split ' / '
if path [ path . length - 1 ] is ' pos '
trackedActionDescription = ' Move Thang '
trackedActionDescription
2014-09-08 15:29:49 -04:00
2014-09-03 15:30:08 -04:00
buildValueForDisplay: (valEl, data) ->
2014-09-03 19:44:59 -04:00
el = $ ( " <span><strong> #{ @ keyForParent } </strong> <span class= ' text-muted ' >( #{ @ countThangs ( data ) } )</span></span> " )
2014-09-08 15:29:49 -04:00
2014-09-03 19:44:59 -04:00
# Kind of like having the portraits on the individual thang rows, rather than the parent folder row
# but keeping this logic here in case we want to have it the other way.
# if thangType = @nameToThangType(@keyForParent)
# el.prepend($("<img class='img-circle' src='#{thangType.getPortraitURL()}' />"))
valEl . append ( el )
2014-09-08 15:29:49 -04:00
2014-09-03 18:38:34 -04:00
countThangs: (data) ->
return 0 if data . thangType and data . id
num = 0
for key , value of data
if value . thangType and value . id
num += 1
else
num += @ countThangs ( value )
num
2014-09-08 15:29:49 -04:00
2014-09-03 18:38:34 -04:00
nameToThangType: (name) ->
if not ThangsFolderNode . nameToThangTypeMap
thangTypes = @ settings . supermodel . getModels ( ThangType )
map = { }
map [ thangType . get ( ' name ' ) ] = thangType for thangType in thangTypes
ThangsFolderNode.nameToThangTypeMap = map
ThangsFolderNode . nameToThangTypeMap [ name ]
2014-08-14 13:28:50 -04:00
2014-01-03 13:32:13 -05:00
class ThangNode extends TreemaObjectNode
valueClass: ' treema-thang '
collection: false
2014-01-21 00:19:54 -05:00
@thangNameMap: { }
2014-01-21 13:42:09 -05:00
@thangKindMap: { }
2014-08-22 14:11:05 -04:00
buildValueForDisplay: (valEl, data) ->
pos = _ . find ( data . components , (c) -> c . config ? . pos ? ) ? . config . pos # TODO: hack
2014-09-03 19:44:59 -04:00
s = data . id
2014-01-03 13:32:13 -05:00
if pos
s += " ( #{ Math . round ( pos . x ) } , #{ Math . round ( pos . y ) } ) "
else
2014-06-30 22:16:26 -04:00
s += ' (non-physical) '
2014-01-03 13:32:13 -05:00
@ buildValueForDisplaySimply valEl , s
2014-09-03 19:44:59 -04:00
thangType = @ settings . supermodel . getModelByOriginal ( ThangType , data . thangType )
if thangType
valEl . prepend ( $ ( " <img class= ' img-circle ' src= ' #{ thangType . getPortraitURL ( ) } ' /> " ) )
2014-01-03 13:32:13 -05:00
onEnterPressed: ->
2014-08-28 12:59:07 -04:00
Backbone . Mediator . publish ' editor:edit-level-thang ' , thangID: @ getData ( ) . id