diff --git a/app/lib/surface/Camera.coffee b/app/lib/surface/Camera.coffee index 0e37a5a12..c6b6ad65c 100644 --- a/app/lib/surface/Camera.coffee +++ b/app/lib/surface/Camera.coffee @@ -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) diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee index a116f3fa2..eebd24a17 100644 --- a/app/lib/surface/Lank.coffee +++ b/app/lib/surface/Lank.coffee @@ -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 diff --git a/app/lib/surface/LankBoss.coffee b/app/lib/surface/LankBoss.coffee index d1187c648..5185dc206 100644 --- a/app/lib/surface/LankBoss.coffee +++ b/app/lib/surface/LankBoss.coffee @@ -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 diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 51b2e2789..7e6cf6482 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -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) diff --git a/app/models/GameUIState.coffee b/app/models/GameUIState.coffee new file mode 100644 index 000000000..666b00f48 --- /dev/null +++ b/app/models/GameUIState.coffee @@ -0,0 +1,5 @@ +CocoModel = require './CocoModel' + +module.exports = class GameUIState extends CocoModel + @className: 'GameUIState' + diff --git a/app/views/editor/level/thangs/ThangsTabView.coffee b/app/views/editor/level/thangs/ThangsTabView.coffee index c5ff8c65f..6d66e16dc 100644 --- a/app/views/editor/level/thangs/ThangsTabView.coffee +++ b/app/views/editor/level/thangs/ThangsTabView.coffee @@ -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')