mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
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:
parent
20ec35b85f
commit
4dda1b67dd
6 changed files with 208 additions and 117 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
5
app/models/GameUIState.coffee
Normal file
5
app/models/GameUIState.coffee
Normal file
|
@ -0,0 +1,5 @@
|
|||
CocoModel = require './CocoModel'
|
||||
|
||||
module.exports = class GameUIState extends CocoModel
|
||||
@className: 'GameUIState'
|
||||
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue