Refactor ThangsTabView to use GameUIState for managing all Surface mouse events

Attempting to use a react-component-like system, where the Surface simply emits everything that
happens through the shared GameUIState, and the parent (in this case the ThangsTabView, but theoretically
anything that uses the surface) handles the events manually, to enforce desired behavior for that particular
context.

It's nice that all the event handling is centralized, but it's still a bit of a mess, and not thoroughly
stateful. But it's a start. This is in preparation for allowing multi-thang selection and manipulation
in the level editor.
This commit is contained in:
Scott Erickson 2016-06-22 11:20:21 -07:00
parent 20ec35b85f
commit 4dda1b67dd
6 changed files with 208 additions and 117 deletions

View file

@ -1,4 +1,5 @@
CocoClass = require 'core/CocoClass'
GameUIState = require 'models/GameUIState'
# If I were the kind of math major who remembered his math, this would all be done with matrix transforms.
@ -44,12 +45,17 @@ module.exports = class Camera extends CocoClass
'camera:zoom-out': 'onZoomOut'
'camera:zoom-to': 'onZoomTo'
'level:restarted': 'onLevelRestarted'
'surface:mouse-scrolled': 'onMouseScrolled'
'sprite:mouse-down': 'onMouseDown'
'sprite:dragged': 'onMouseDragged'
constructor: (@canvas, angle=Math.asin(0.75), hFOV=d2r(30)) ->
constructor: (@canvas, @options={}) ->
angle=Math.asin(0.75)
hFOV=d2r(30)
super()
@gameUIState = @options.gameUIState or new GameUIState()
@listenTo @gameUIState, 'surface:stage-mouse-move', @onMouseMove
@listenTo @gameUIState, 'surface:stage-mouse-down', @onMouseDown
@listenTo @gameUIState, 'surface:stage-mouse-up', @onMouseUp
@listenTo @gameUIState, 'surface:mouse-scrolled', @onMouseScrolled
@handleEvents = @options.handleEvents ? true
@canvasWidth = parseInt(@canvas.attr('width'), 10)
@canvasHeight = parseInt(@canvas.attr('height'), 10)
@offset = {x: 0, y: 0}
@ -155,8 +161,27 @@ module.exports = class Camera extends CocoClass
onZoomIn: (e) -> @zoomTo @target, @zoom * 1.15, 300
onZoomOut: (e) -> @zoomTo @target, @zoom / 1.15, 300
onMouseDown: (e) ->
return if @dragDisabled
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
@mousePressed = true
onMouseMove: (e) ->
return unless @mousePressed and @gameUIState.get('canDragCamera')
return if @dragDisabled
target = @boundTarget(@target, @zoom)
newPos =
x: target.x + (@lastPos.x - e.originalEvent.rawX) / @zoom
y: target.y + (@lastPos.y - e.originalEvent.rawY) / @zoom
@zoomTo newPos, @zoom, 0
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
Backbone.Mediator.publish 'camera:dragged', {}
onMouseUp: (e) ->
@mousePressed = false
onMouseScrolled: (e) ->
return unless e.canvas is @canvas
ratio = 1 + 0.05 * Math.sqrt(Math.abs(e.deltaY))
ratio = 1 / ratio if e.deltaY > 0
newZoom = @zoom * ratio
@ -174,22 +199,6 @@ module.exports = class Camera extends CocoClass
target = @target
@zoomTo target, newZoom, 0
onMouseDown: (e) ->
return unless e.canvas is @canvas[0]
return if @dragDisabled
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
onMouseDragged: (e) ->
return unless e.canvas is @canvas[0]
return if @dragDisabled
target = @boundTarget(@target, @zoom)
newPos =
x: target.x + (@lastPos.x - e.originalEvent.rawX) / @zoom
y: target.y + (@lastPos.y - e.originalEvent.rawY) / @zoom
@zoomTo newPos, @zoom, 0
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
Backbone.Mediator.publish 'camera:dragged', {}
onLevelRestarted: ->
@setBounds(@firstBounds, false)

View file

@ -57,12 +57,14 @@ module.exports = Lank = class Lank extends CocoClass
'level:set-letterbox': 'onSetLetterbox'
'surface:ticked': 'onSurfaceTicked'
'sprite:move': 'onMove'
constructor: (@thangType, options) ->
constructor: (@thangType, options={}) ->
super()
spriteName = @thangType.get('name')
@isMissile = /(Missile|Arrow|Spear|Bolt)/.test(spriteName) and not /(Tower|Charge)/.test(spriteName)
@options = _.extend($.extend(true, {}, @options), options)
@gameUIState = @options.gameUIState
@handleEvents = @options.handleEvents
@setThang @options.thang
if @thang?
options = @thang?.getLankOptions?()
@ -496,6 +498,7 @@ module.exports = Lank = class Lank extends CocoClass
newEvent = sprite: @, thang: @thang, originalEvent: e, canvas: p.canvas
@trigger ourEventName, newEvent
Backbone.Mediator.publish ourEventName, newEvent
@gameUIState.trigger(ourEventName, newEvent)
addHealthBar: ->
return unless @thang?.health? and 'health' in (@thang?.hudProperties ? []) and @options.floatingLayer

View file

@ -25,10 +25,11 @@ module.exports = class LankBoss extends CocoClass
'surface:flag-appeared': 'onFlagAppeared'
'surface:remove-selected-flag': 'onRemoveSelectedFlag'
constructor: (@options) ->
constructor: (@options={}) ->
super()
@handleEvents = @options.handleEvents
@gameUIState = @options.gameUIState
@dragged = 0
@options ?= {}
@camera = @options.camera
@webGLStage = @options.webGLStage
@surfaceTextLayer = @options.surfaceTextLayer
@ -38,6 +39,8 @@ module.exports = class LankBoss extends CocoClass
@lankArray = [] # Mirror @lanks, but faster for when we just need to iterate
@createLayers()
@pendingFlags = []
if not @handleEvents
@listenTo @gameUIState, 'change:selected', @onChangeSelected
destroy: ->
@removeLank lank for thangID, lank of @lanks
@ -93,7 +96,16 @@ module.exports = class LankBoss extends CocoClass
@selectionMark = new Mark name: 'selection', camera: @camera, layer: @layerAdapters['Ground'], thangType: 'selection'
createLankOptions: (options) ->
_.extend options, camera: @camera, resolutionFactor: SPRITE_RESOLUTION_FACTOR, groundLayer: @layerAdapters['Ground'], textLayer: @surfaceTextLayer, floatingLayer: @layerAdapters['Floating'], showInvisible: @options.showInvisible
_.extend options, {
@camera
resolutionFactor: SPRITE_RESOLUTION_FACTOR
groundLayer: @layerAdapters['Ground']
textLayer: @surfaceTextLayer
floatingLayer: @layerAdapters['Floating']
showInvisible: @options.showInvisible
@gameUIState
@handleEvents
}
onSetDebug: (e) ->
return if e.debug is @debug
@ -256,6 +268,7 @@ module.exports = class LankBoss extends CocoClass
@dragged += 1
onLankMouseUp: (e) ->
return unless @handleEvents
return if key.shift #and @options.choosing
return @dragged = 0 if @dragged > 3
@dragged = 0
@ -264,9 +277,17 @@ module.exports = class LankBoss extends CocoClass
@selectLank e, lank
onStageMouseDown: (e) ->
return unless @handleEvents
return if key.shift #and @options.choosing
@selectLank e if e.onBackground
onChangeSelected: (gameUIState, selected) ->
if selected
@selectThang(selected.thang.id, selected.spellName)
else
@selectedLank?.selected = false
@selectedLank = null
selectThang: (thangID, spellName=null, treemaThangSelected = null) ->
return @willSelectThang = [thangID, spellName] unless @lanks[thangID]
@selectLank null, @lanks[thangID], spellName, treemaThangSelected
@ -275,8 +296,9 @@ module.exports = class LankBoss extends CocoClass
return if e and (@disabled or @selectLocked) # Ignore clicks for selection/panning/wizard movement while disabled or select is locked
worldPos = lank?.thang?.pos
worldPos ?= @camera.screenToWorld {x: e.originalEvent.rawX, y: e.originalEvent.rawY} if e?.originalEvent
if (not @reallyStopMoving) and worldPos and (@options.navigateToSelection or not lank or treemaThangSelected) and e?.originalEvent?.nativeEvent?.which isnt 3
@camera.zoomTo(lank?.sprite or @camera.worldToSurface(worldPos), @camera.zoom, 1000, true)
if @handleEvents
if (not @reallyStopMoving) and worldPos and (@options.navigateToSelection or not lank or treemaThangSelected) and e?.originalEvent?.nativeEvent?.which isnt 3
@camera.zoomTo(lank?.sprite or @camera.worldToSurface(worldPos), @camera.zoom, 1000, true)
lank = null if @options.choosing # Don't select lanks while choosing
if lank isnt @selectedLank
@selectedLank?.selected = false

View file

@ -18,6 +18,7 @@ LankBoss = require './LankBoss'
PointChooser = require './PointChooser'
RegionChooser = require './RegionChooser'
MusicPlayer = require './MusicPlayer'
GameUIState = require 'models/GameUIState'
resizeDelay = 500 # At least as much as $level-resize-transition-time.
@ -87,6 +88,10 @@ module.exports = Surface = class Surface extends CocoClass
@normalLayers = []
@options = _.clone(@defaults)
@options = _.extend(@options, givenOptions) if givenOptions
@handleEvents = @options.handleEvents ? true
@gameUIState = @options.gameUIState or new GameUIState({
canDragCamera: true
})
@initEasel()
@initAudio()
@onResize = _.debounce @onResize, resizeDelay
@ -98,7 +103,7 @@ module.exports = Surface = class Surface extends CocoClass
@normalStage = new createjs.Stage(@normalCanvas[0])
@webGLStage = new createjs.SpriteStage(@webGLCanvas[0])
@normalStage.nextStage = @webGLStage
@camera = new Camera @webGLCanvas
@camera = new Camera(@webGLCanvas, { @gameUIState, @handleEvents })
AudioPlayer.camera = @camera unless @options.choosing
@normalLayers.push @surfaceTextLayer = new Layer name: 'Surface Text', layerPriority: 1, transform: Layer.TRANSFORM_SURFACE_TEXT, camera: @camera
@ -112,7 +117,19 @@ module.exports = Surface = class Surface extends CocoClass
canvasHeight = parseInt @normalCanvas.attr('height'), 10
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
@lankBoss = new LankBoss camera: @camera, webGLStage: @webGLStage, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible, playerNames: if @options.levelType is 'course-ladder' then @options.playerNames else null
@lankBoss = new LankBoss({
@camera
@webGLStage
@surfaceTextLayer
@world
thangTypes: @options.thangTypes
choosing: @options.choosing
navigateToSelection: @options.navigateToSelection
showInvisible: @options.showInvisible
playerNames: if @options.levelType is 'course-ladder' then @options.playerNames else null
@gameUIState
@handleEvents
})
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
@ -121,7 +138,7 @@ module.exports = Surface = class Surface extends CocoClass
@webGLStage.enableMouseOver(10)
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
@webGLStage.addEventListener 'stagemousedown', @onMouseDown
@webGLCanvas[0].addEventListener 'mouseup', @onMouseUp
@webGLStage.addEventListener 'stagemouseup', @onMouseUp
@webGLCanvas.on 'mousewheel', @onMouseWheel
@hookUpChooseControls() if @options.choosing # TODO: figure this stuff out
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
@ -222,8 +239,9 @@ module.exports = Surface = class Surface extends CocoClass
updateState: (frameChanged) ->
# world state must have been restored in @restoreWorldState
if @playing and @currentFrame < @world.frames.length - 1 and @heroLank and not @mouseIsDown and @camera.newTarget isnt @heroLank.sprite and @camera.target isnt @heroLank.sprite
@camera.zoomTo @heroLank.sprite, @camera.zoom, 750
if @handleEvents
if @playing and @currentFrame < @world.frames.length - 1 and @heroLank and not @mouseIsDown and @camera.newTarget isnt @heroLank.sprite and @camera.target isnt @heroLank.sprite
@camera.zoomTo @heroLank.sprite, @camera.zoom, 750
@lankBoss.update frameChanged
@camera.updateZoom() # Make sure to do this right after the LankBoss updates, not before, so it can properly target sprite positions.
@dimmer?.setSprites @lankBoss.lanks
@ -371,7 +389,8 @@ module.exports = Surface = class Surface extends CocoClass
target = null
@camera.setBounds e.bounds if e.bounds
# @cameraBorder.updateBounds @camera.bounds
@camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
if @handleEvents
@camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
onZoomUpdated: (e) ->
if @ended
@ -476,6 +495,7 @@ module.exports = Surface = class Surface extends CocoClass
@mouseScreenPos = {x: e.stageX, y: e.stageY}
return if @disabled
Backbone.Mediator.publish 'surface:mouse-moved', x: e.stageX, y: e.stageY
@gameUIState.trigger('surface:stage-mouse-move', { originalEvent: e })
onMouseDown: (e) =>
return if @disabled
@ -484,16 +504,19 @@ module.exports = Surface = class Surface extends CocoClass
onBackground = not @webGLStage._getObjectsUnderPoint(e.stageX, e.stageY, null, true)
wop = @camera.screenToWorld x: e.stageX, y: e.stageY
event = onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e, worldPos: wop
event = { onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e, worldPos: wop }
Backbone.Mediator.publish 'surface:stage-mouse-down', event
Backbone.Mediator.publish 'tome:focus-editor', {}
@gameUIState.trigger('surface:stage-mouse-down', event)
@mouseIsDown = true
onMouseUp: (e) =>
return if @disabled
onBackground = not @webGLStage.hitTest e.stageX, e.stageY
Backbone.Mediator.publish 'surface:stage-mouse-up', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
event = { onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e }
Backbone.Mediator.publish 'surface:stage-mouse-up', event
Backbone.Mediator.publish 'tome:focus-editor', {}
@gameUIState.trigger('surface:stage-mouse-up', event)
@mouseIsDown = false
onMouseWheel: (e) =>
@ -506,6 +529,7 @@ module.exports = Surface = class Surface extends CocoClass
canvas: @webGLCanvas
event.screenPos = @mouseScreenPos if @mouseScreenPos
Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled
@gameUIState.trigger('surface:mouse-scrolled', event)
#- Canvas callbacks
@ -585,8 +609,9 @@ module.exports = Surface = class Surface extends CocoClass
@onResize()
_.delay @onResize, resizeDelay + 100 # Do it again just to be double sure that we don't stay zoomed in due to timing problems.
@normalCanvas.add(@webGLCanvas).removeClass 'flag-color-selected'
if @previousCameraZoom
@camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
if @handleEvents
if @previousCameraZoom
@camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
onFlagColorSelected: (e) ->
@normalCanvas.add(@webGLCanvas).toggleClass 'flag-color-selected', Boolean(e.color)

View file

@ -0,0 +1,5 @@
CocoModel = require './CocoModel'
module.exports = class GameUIState extends CocoModel
@className: 'GameUIState'

View file

@ -11,6 +11,7 @@ Thang = require 'lib/world/thang'
LevelThangEditView = require './LevelThangEditView'
ComponentsCollection = require 'collections/ComponentsCollection'
require 'vendor/treema'
GameUIState = require 'models/GameUIState'
# Moving the screen while dragging thangs constants
MOVE_MARGIN = 0.15
@ -29,7 +30,6 @@ module.exports = class ThangsTabView extends CocoView
template: thangs_template
subscriptions:
'surface:sprite-selected': 'onExtantThangSelected'
'surface:mouse-moved': 'onSurfaceMouseMoved'
'surface:mouse-over': 'onSurfaceMouseOver'
'surface:mouse-out': 'onSurfaceMouseOut'
@ -39,7 +39,6 @@ module.exports = class ThangsTabView extends CocoView
'editor:view-switched': 'onViewSwitched'
'sprite:dragged': 'onSpriteDragged'
'sprite:mouse-up': 'onSpriteMouseUp'
'sprite:mouse-down': 'onSpriteMouseDown'
'sprite:double-clicked': 'onSpriteDoubleClicked'
'surface:stage-mouse-down': 'onStageMouseDown'
'surface:stage-mouse-up': 'onStageMouseUp'
@ -78,6 +77,11 @@ module.exports = class ThangsTabView extends CocoView
constructor: (options) ->
super options
@world = options.world
@gameUIState = new GameUIState()
@listenTo(@gameUIState, 'sprite:mouse-down', @onSpriteMouseDown)
@listenTo(@gameUIState, 'surface:stage-mouse-move', @onStageMouseMove)
@listenTo(@gameUIState, 'change:selected', @onChangeSelected)
@willRepositionCamera =true
# should load depended-on Components, too
@thangTypes = @supermodel.loadCollection(new ThangTypeSearchCollection(), 'thangs').model
@ -203,7 +207,7 @@ module.exports = class ThangsTabView extends CocoView
initSurface: ->
webGLCanvas = $('canvas#webgl-surface', @$el)
normalCanvas = $('canvas#normal-surface', @$el)
@surface = new Surface @world, normalCanvas, webGLCanvas, {
@surface = new Surface(@world, normalCanvas, webGLCanvas, {
paths: false
coords: true
grid: true
@ -212,7 +216,9 @@ module.exports = class ThangsTabView extends CocoView
showInvisible: true
frameRate: 15
levelType: @level.get 'type', true
}
@gameUIState
handleEvents: false
})
@surface.playing = false
@surface.setWorld @world
@surface.lankBoss.suppressSelectionSounds = true
@ -240,38 +246,60 @@ module.exports = class ThangsTabView extends CocoView
@selectAddThang null, true
@surface?.lankBoss?.selectLank null, null
onSpriteMouseDown: (e) ->
@dragged = false
# Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked.
# clearTimeout @backgroundAddClickTimeout
# if e.originalEvent.nativeEvent.button == 2
# @onSpriteContextMenu e
onStageMouseDown: (e) ->
return unless @addThangLank?.thangType.get('kind') is 'Wall'
@surface.camera.dragDisabled = true
@paintingWalls = true
@dragged = false
@willRepositionCamera = true
@gameUIState.set('canDragCamera', true)
if @addThangLank?.thangType.get('kind') is 'Wall'
@paintingWalls = true
@gameUIState.set('canDragCamera', false)
else if @addThangLank
# We clicked on the background when we had an add Thang selected, so add it
@addThang @addThangType, @addThangLank.thang.pos
@willRepositionCamera = false
else if e.onBackground
@gameUIState.set('selected', null)
onStageMouseMove: (e) ->
@willRepositionCamera = false
onStageMouseUp: (e) ->
if @paintingWalls
# We need to stop painting walls, but we may also stop in onExtantThangSelected.
_.defer =>
@paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
else if @addThangLank
@surface.camera.lock()
# If we click on the background, we need to add @addThangLank, but not if onSpriteMouseUp will fire.
@backgroundAddClickTimeout = _.defer => @onExtantThangSelected {}
if @willRepositionCamera
worldPos = @surface.camera.screenToWorld {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
@surface.camera.zoomTo(@surface.camera.worldToSurface(worldPos), @surface.camera.zoom, 1000)
@paintingWalls = false
$('#contextmenu').hide()
onSpriteMouseDown: (e) ->
# update selection
# TODO: Handle key.shift, lankBoss.dragged property
selected = null
if e.thang?.isSelectable
selected = { thang: e.thang, sprite: e.sprite, spellName: e.spellName }
if selected and (key.alt or key.meta)
# Clone selected thang instead of selecting it
@willRepositionCamera = false
@selectAddThangType selected.thang.spriteName, selected.thang
selected = null
@gameUIState.set('selected', selected)
if selected
@willRepositionCamera = false
@gameUIState.set('canDragCamera', false)
onSpriteDragged: (e) ->
return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
selected = @gameUIState.get('selected')
return unless selected and e.thang?.id is selected.thang.id
@dragged = true
@surface.camera.dragDisabled = true
@willRepositionCamera = false
{stageX, stageY} = e.originalEvent
cap = @surface.camera.screenToCanvas x: stageX, y: stageY
wop = @surface.camera.canvasToWorld cap
wop.z = @selectedExtantThang.depth / 2
@adjustThangPos @selectedExtantLank, @selectedExtantThang, wop
wop.z = selected.thang.depth / 2
@adjustThangPos selected.sprite, selected.thang, wop
[w, h] = [@surface.camera.canvasWidth, @surface.camera.canvasHeight]
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
@ -279,24 +307,23 @@ module.exports = class ThangsTabView extends CocoView
@calculateMovement(cap.x / w, cap.y / h, w / h)
onSpriteMouseUp: (e) ->
clearTimeout @backgroundAddClickTimeout
@surface.camera.unlock()
if e.originalEvent.nativeEvent.button == 2 and @selectedExtantThang
selected = @gameUIState.get('selected')
if e.originalEvent.nativeEvent.button == 2 and selected
@onSpriteContextMenu e
clearInterval(@movementInterval) if @movementInterval?
@movementInterval = null
@surface.camera.dragDisabled = false
return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
pos = @selectedExtantThang.pos
return unless selected and e.thang?.id is selected.thang.id
pos = selected.thang.pos
thang = _.find(@level.get('thangs') ? [], {id: @selectedExtantThang.id})
thang = _.find(@level.get('thangs') ? [], {id: selected.thang.id})
path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
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 and not @dragged
return unless e.thang
@editThang thangID: e.thang.id
onRandomTerrainGenerated: (e) ->
@ -320,35 +347,21 @@ module.exports = class ThangsTabView extends CocoView
@onThangsChanged()
@selectAddThangType null
onChangeSelected: (gameUIState, selected) ->
previousSprite = gameUIState.previousAttributes()?.selected?.sprite
sprite = selected?.sprite
thang = selected?.thang
# 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) ->
@selectedExtantLank?.setNameLabel? null unless @selectedExtantLank is e.sprite
@selectedExtantThang = e.thang
@selectedExtantLank = e.sprite
paintedAWall = @paintedWalls
@paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
if paintedAWall
# Skip adding a wall now, because we already dragged to add one
null
else if e.thang and (key.alt or key.meta)
# We alt-clicked, so create a clone addThang
@selectAddThangType e.thang.spriteName, @selectedExtantThang
else if @justAdded()
# Skip double insert due to extra selection event
null
else if e.thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
previousSprite?.setNameLabel?(null) unless previousSprite is sprite
if thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
# We clicked on a Thang (or its Treema), so select the Thang
@selectAddThang null, true
@selectAddThang(null, true)
@selectedExtantThangClickTime = new Date()
# Show the label above selected thang, notice that we may get here from thang-edit-view, so it will be selected but no label
@selectedExtantLank.setNameLabel @selectedExtantLank.thangType.get('name') + ': ' + @selectedExtantThang.id
@selectedExtantLank.updateLabels()
@selectedExtantLank.updateMarks()
else if @addThangLank
# We clicked on the background when we had an add Thang selected, so add it
@addThang @addThangType, @addThangLank.thang.pos
@lastAddTime = new Date()
sprite.setNameLabel(sprite.thangType.get('name') + ': ' + thang.id)
sprite.updateLabels()
sprite.updateMarks()
justAdded: -> @lastAddTime and (new Date() - @lastAddTime) < 150
@ -430,6 +443,7 @@ module.exports = class ThangsTabView extends CocoView
@surface.lankBoss.update true # Make sure Obstacle layer resets cache
onSurfaceMouseMoved: (e) ->
@dragged = true
return unless @addThangLank
wop = @surface.camera.screenToWorld x: e.x, y: e.y
wop.z = 0.5
@ -482,8 +496,9 @@ module.exports = class ThangsTabView extends CocoView
deleteSelectedExtantThang: (e) =>
return if $(e.target).hasClass 'treema-node'
return unless @selectedExtantThang
thang = @getThangByID(@selectedExtantThang.id)
selected = @gameUIState.get('selected')
return unless selected
thang = @getThangByID(selected.thang.id)
@thangsTreema.delete(@pathForThang(thang))
@deleteEmptyTreema(thang)
Thang.resetThangIDs() # TODO: find some way to do this when we delete from treema, too
@ -564,14 +579,19 @@ module.exports = class ThangsTabView extends CocoView
@selectAddThangType @addThangType, @cloneSourceThang if @addThangType # make another addThang sprite, since the World just refreshed
# update selection, since the thangs have been remade
if @selectedExtantThang
@selectedExtantLank = @surface.lankBoss.lanks[@selectedExtantThang.id]
@selectedExtantThang = @selectedExtantLank?.thang
selected = @gameUIState.get('selected')
if selected
sprite = @surface.lankBoss.lanks[selected.thang.id]
if sprite
thang = sprite.thang
@gameUIState.set('selected', _.extend({}, selected, { sprite, thang }))
else
@gameUIState.set('selected', null)
Backbone.Mediator.publish 'editor:thangs-edited', thangs: @world.thangs
onTreemaThangSelected: (e, selectedTreemas) =>
selectedThangID = _.last(selectedTreemas)?.data.id
if selectedThangID isnt @selectedExtantThang?.id
if selectedThangID isnt @gameUIState.get('selected')?.thang.id
@surface.lankBoss.selectThang selectedThangID, null, true
onTreemaThangDoubleClicked: (e, treema) =>
@ -655,7 +675,8 @@ module.exports = class ThangsTabView extends CocoView
onDuplicateClicked: (e) ->
$('#contextmenu').hide()
@selectAddThangType @selectedExtantThang.spriteName, @selectedExtantThang
selected = @gameUIState.get('selected')
@selectAddThangType(selected.thang.spriteName, selected.thang)
onClickRotationButton: (e) ->
$('#contextmenu').hide()
@ -667,7 +688,8 @@ module.exports = class ThangsTabView extends CocoView
@hush = true
thangData = @getThangByID thang.id
thangData = $.extend true, {}, thangData
unless component = _.find thangData.components, {original: componentOriginal}
component = _.find thangData.components, {original: componentOriginal}
unless component
component = original: componentOriginal, config: {}, majorVersion: 0
thangData.components.push component
modificationFunction component
@ -682,34 +704,39 @@ module.exports = class ThangsTabView extends CocoView
lank.setDebug true
rotateSelectedThangTo: (radians) ->
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
selectedThang = @gameUIState.get('selected')?.thang
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
component.config.rotation = radians
@selectedExtantThang.rotation = component.config.rotation
selectedThang.rotation = component.config.rotation
rotateSelectedThangBy: (radians) ->
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
selectedThang = @gameUIState.get('selected')?.thang
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
component.config.rotation = ((component.config.rotation ? 0) + radians) % (2 * Math.PI)
@selectedExtantThang.rotation = component.config.rotation
selectedThang.rotation = component.config.rotation
moveSelectedThangBy: (xDir, yDir) ->
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
selectedThang = @gameUIState.get('selected')?.thang
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
component.config.pos.x += 0.5 * xDir
component.config.pos.y += 0.5 * yDir
@selectedExtantThang.pos.x = component.config.pos.x
@selectedExtantThang.pos.y = component.config.pos.y
selectedThang.pos.x = component.config.pos.x
selectedThang.pos.y = component.config.pos.y
resizeSelectedThangBy: (xDir, yDir) ->
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
selectedThang = @gameUIState.get('selected')?.thang
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
component.config.width = (component.config.width ? 4) + 0.5 * xDir
component.config.height = (component.config.height ? 4) + 0.5 * yDir
@selectedExtantThang.width = component.config.width
@selectedExtantThang.height = component.config.height
selectedThang.width = component.config.width
selectedThang.height = component.config.height
toggleSelectedThangCollision: ->
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.CollidesID, (component) =>
selectedThang = @gameUIState.get('selected')?.thang
@modifySelectedThangComponentConfig selectedThang, LevelComponent.CollidesID, (component) =>
component.config ?= {}
component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none'
@selectedExtantThang.collisionCategory = component.config.collisionCategory
selectedThang.collisionCategory = component.config.collisionCategory
toggleThangsContainer: (e) ->
$('#all-thangs').toggleClass('hide')