PlayState = class(Group, function(o, waveData)
	-- this might stop the stuttering when loading
	pd:startList("fromOF")
	pd:addSymbol("bpm")
	pd:addFloat(0)
	pd:finish("fromOF")

	Group.init(o)

	o:loadAdditionalUi()

	o:setupBackground()
	o:setupBox2d()
	-- a table for holding the room sounds
	o.roomSounds = {}
	o.waveData = waveData
	local yOff = 97*r_scale
	-- cancel any scrolls we have running
	if bludG.camera.tween then Tweener:removeTween(bludG.camera.tween) end
	if bludG.camera.scaletween then Tweener:removeTween(bludG.camera.scaletween) end

	bludG.camera.scroll = Vec2(0,yOff)
	bludG.camera.scale = 1
	o.enemyBullets = o:add(Group())
	o.enemies = o:add(Group())

	o.rooms = o:add(Group())
	local size = 60*r_scale
	o.rootRoom = o.rooms:add(RoomRoot(bludG.camera.w/2-size/2,bludG.camera.h-size*2, o))
	o.rootRoom.typecount = 0
	o.rootRoom:addBox2d()
	o.drag = o:add(DragArea(0,0,bludG.camera.w, bludG.camera.h))
	o.drag.onStart = function(da,x,y,id)
		o:dragStart(da, x, y, id)
	end
	o.drag.onMove = function(da,x,y,id)
		if o.newType then
			o:blockAudit(da, x, y, id)
		else
			o:panModeHandler(da, x, y, id)
		end
	end
	o.drag.onPress = function(da,x,y,id)
		o:clearHelpers()
		if o.newType then
			o:blockCommit(da, x, y, id)
		else
			o:fingerRelease()
		end
	end
	o:loadRoomButtons(waveData.rooms)
	o.waveController = o:add(WaveDisplay(waveData.waves, o.enemies, o))
	o.moneyController = o:add(MoneyController(waveData.money, o))
	o.waveController:setup()
	o.pulsers = o:add(Pulsers())
	o.bullets = o:add(Group())
	o.bullets.addRecycle = function(bs, b)
		player:addShot()
		return Group.addRecycle(bs, b)
	end
	pd:startList("fromOF")
	pd:addSymbol("levelStart")
	pd:addFloat(1)
	pd:finish("fromOF")


	player:resetShotCount()
	player:resetDestroyCount()

	pd:startList("fromOF")
	pd:addSymbol("bpm")
	local bpm = o.waveData.bpm or 350
	bpm = bpm/2
	pd:addFloat(bpm)
	pd:finish("fromOF")

	if mainState.bulletParticles then mainState.particles:remove(mainState.bulletParticles) end
	mainState.bulletParticles = mainState.particles:addRecycle(ParticleHandler(0,0, "particle_data_bullet_explode_".. player.currentWorld.id ..".lua", sprites['tiles']['circle.png'], {immortal=true}))

	if mainState.moneyParticles then mainState.particles:remove(mainState.moneyParticles) end
	mainState.moneyParticles = mainState.particles:addRecycle(ParticleHandler(0,0, "particle_data_money.lua", sprites['tiles']['money_particle.png'], {immortal=true}))

	-- add in the overlay if it exists
	-- confirm that we are using the correct data
	o:checkDraftState(waveData)
	o:add(OverlayState(overlay_data[o.waveData.filename], o))
	player.lastLevel = waveData.filename
	-- send the data for level started
	-- analytics:logEvent("level_start", {['filename']=waveData.filename})
	-- analytics:levelCounterMetric("began", waveData.filename)
end)
function PlayState:checkDraftState(waveData)
	local freshData = persistence.load(blud.bundle_root .. "/badHotel_/assets/levelData/level_data_" .. waveData.filename .. ".lua");
	freshData = freshData.rooms
	local dirty = false
	for k,v in pairs(waveData.rooms) do
		-- if it isn't here, and it is in the fresh data
		if not v and freshData[k] then
			dirty = true
		end
		-- if it is here, but not in the fresh data
		if v and not freshData[k] then
			dirty = true
		end
	end
	-- do the same thing in reverse
	for k,v in pairs(freshData) do
		-- if it isn't here, and it is in the fresh data
		if not v and waveData.rooms[k] then
			dirty = true
		end
		-- if it is here, but not in the fresh data
		if v and not waveData.rooms[k] then
			dirty = true
		end
	end
	if dirty then self.customDraft = true end
end
-- preload the svgs for pause, win, lose
function PlayState:loadAdditionalUi()
	pauseSvg = SvgLoader(svg_root .. "interface/pause_menu_" .. player.currentWorld.id .. ".svg", false)
	pauseSvgData = {}	
	-- might need to do a deep copy here
	for key, rbd in pairs(pauseSvg.id) do
		pauseSvgData[key] = rbd
	end
end
function PlayState:addTutorialArrow()
	-- add the tutorial arrow
	local arrowSvg = SvgLoader(svg_root .. "interface/help_arrow" .. ipad .. ".svg", true)
	self.tutorialArrow = self:add(Object(0,0,arrowSvg:getMaxX(), arrowSvg:getMaxY()))
	local ta = self.tutorialArrow
	ta.svg = arrowSvg
	ta.svg:setScrollFactor(0,0)
	ta.drawType = 'svg'
	local button = self.buttons.members[1]
	ta:setPosition(button.pos.x-ta.w-button.w/2, button.pos.y-ta.h+button.h/2)
	-- attach a function call to the block commit that removes the arrow once a room is placed
	local obc = self.blockCommit
	function self:blockCommit()
		obc(self)
		if #self.rooms.members >= 2 then
			self:remove(ta)
		end
	end
end
function PlayState:setupBox2d()
	-- setup box2d so that it can be used for all the collisions in the game
	if not bhComponentManager then
		bhComponentManager = bludComponentManager()
	end
	if box2d then
		bhComponentManager:removeComponent(box2d)
	end
	box2d = BludBox2d();
	bhComponentManager:addComponent(box2d)
	box2d:setStartCallback(function(a, b)
		self:collisionStarted(a, b)
	end)
	box2d:setEndCallback(function(a, b)
		self:collisionEnded(a, b)
	end)
end
function PlayState:collisionStarted(a, b)
	if type(a) == 'table' and a.handleCollision then a:handleCollision(b) end
	if type(b) == 'table' and b.handleCollision then b:handleCollision(a) end
end
function PlayState:collisionEnded(a, b)
	if type(a) == 'table' and a.handleCollisionEnd then a:handleCollisionEnd(b) end
	if type(b) == 'table' and b.handleCollisionEnd then b:handleCollisionEnd(a) end
end
function PlayState:loadRoomButtons(waveData)
	local buttonHeight = 50*r_scale
	local buttonY = bludG.camera.h-60*r_scale
	
	-- put in a background color behind the rooms
--	local buttonBG = self:add(BottomBar())

	self.buttons = self:add(Group()) 
	local i = 1
	-- resort the buttons into alphabetical order
	local rooms = {}
	for k,v in pairs(waveData) do
		if v then table.insert(rooms, k) end
	end
	-- sort the rooms into alphabetical
	table.sort(rooms, function(a, b)
		local a1, b1
		for i,v in ipairs(roomTypes) do
			for j,w in ipairs(v) do
				if w == a then
					a1 = i .. '.' .. j
				end
				if w == b then
					b1 = i .. '.' .. j
				end
			end
		end
		return a1 < b1
	end)
	for i, k in ipairs(rooms) do
		-- add the room to the player unlocks before adding the button
		if not player.unlocks[k] then
			player.unlocks[k] = true
		end
		-- add the button
		local b = self.buttons:add(TypeButton(bludG.camera.w/5*(i-1),buttonY,buttonHeight, buttonHeight, self, k))
		k = _G[k]
		b.tint = k.color
		i = i+1
	end
	-- fix the positioning of the buttons
	local subWidth = bludG.camera.w/#self.buttons.members
	for i,v in ipairs(self.buttons.members) do
		local bx = (subWidth*i)-subWidth/2-buttonHeight/2
		v:setPosition(math.floor(bx), v.pos.y)
		-- attach text for the prices above the buttons
		local cost = game_settings[v.room.settingName .. "cost"]
		v.priceString = self:add(StringObject(bx, v.pos.y, "$" .. cost,nil, 0.4))
		v.priceString:setPosition(v.pos.x+v.w/2-v.priceString.w/2, v.priceString.pos.y-v.priceString.h)
		if player.currentWorld.type_button_color then
			local pctbc = player.currentWorld.type_button_color
			v.priceString:setColor(pctbc[1], pctbc[2], pctbc[3])
		else
			v.priceString:setColor(0,0,0)
		end
		v.priceString:setScrollFactor(Vec2(0,0))
	end
end
function PlayState:sync()
	for i,v in ipairs(self.enemies.members) do
		if v.sync then v:sync() end
	end
	for i,v in ipairs(self.rooms.members) do
		if v.sync then
			v:sync()
		end
	end
	if not self.pulseQueue then
		if self.rooms:countLiving() == 1 then
			self.pulseQueue = PulseWait(function()
				self.rootRoom:pulse()
			end, 1)
		else
			local waitingToFinish = false
			for i,v in ipairs(self.rooms.members) do
				waitingToFinish = waitingToFinish or (v.waitingToFinish and v.alive)
			end
			if not waitingToFinish then
				self.pulseQueue = PulseWait(function()
					self.rootRoom:pulse()
				end, 1)
			end
		end
	else
		self.pulseQueue.count = self.pulseQueue.count - 1
		if self.pulseQueue.count <= 0 then
			self.pulseQueue.fn()
			self.pulseQueue = nil
		end
	end
	pd:startList("fromOF")
	pd:addSymbol("monoMode")
	for i,v in ipairs(self.roomSounds) do
		pd:addFloat(v[1])
		pd:addFloat(v[2])
	end
	pd:finish("fromOF")
	self.roomSounds = {}
end
function PlayState:addRoomSound(typecount, note)
	table.insert(self.roomSounds, {typecount, note})
end
function PlayState:update()
	-- check for win or lose conditions
	if not self.rootRoom.alive then
		mainState:changeState(LoseState(self))
	elseif self.waveController:setOver() and self.enemies:countLiving() <= 0 and not self.over then
		-- load the unlocks from the unlock data
		if unlock_data[self.waveData.filename] then
			for i,v in ipairs(unlock_data[self.waveData.filename]) do
				player.unlocks[v] = true
			end
		end
		self.over = true
		mainState:changeState(WinState(self))
	end
	-- check bullet / enemy collision
	-- for now, just kill all the enemies
	-- for i,v in ipairs(self.enemies.members) do
	-- 	v:hit(10)
	-- end
	Group.update(self)
	self:updatePriceDisplays()
end
function PlayState:updatePriceDisplays()
	for i,v in ipairs(self.buttons.members) do
		local cost = game_settings[v.room.settingName .. "cost"]
		if self.moneyController:getAmount() >= cost then
			if player.currentWorld.type_button_color then
				local pctbc = player.currentWorld.type_button_color
				v.priceString:setColor(pctbc[1], pctbc[2], pctbc[3], 255)
			else
				v.priceString:setColor(0,0,0, 255)
			end
			v:afford(true)
		else
			if player.currentWorld.type_button_color then
				local pctbc = player.currentWorld.type_button_color
				v.priceString:setColor(pctbc[1], pctbc[2], pctbc[3], 128)
			else
				v.priceString:setColor(0,0,0, 128)
			end
			v:afford(false)
		end
	end	
	if self.ghostBlock and self.newType then
		if game_settings[self.newType.settingName .. "cost"] > self.moneyController:getAmount() then
			self.ghostBlock:setColor(200, 80, 80, 100)
		else
			self.ghostBlock:setColor(20, 255,80, 100)
		end
	end
end
function PlayState:setupUnderBg()
	bg:setBackground(player.currentWorld.bg_color[1], player.currentWorld.bg_color[2], player.currentWorld.bg_color[3])
	if player.currentSheet then render:removeSheet(player.currentSheet) end
	sheets["under_bg"], sprites["under_bg"] = spriteLoader(player.currentWorld.bg_sheet_name, 'png')
	sheets["under_bg"]:setBlendMode(1)
	mainbg = {}
	player.currentBg = player.currentWorld.bg_sheet_name
	player.currentSheet = sheets["under_bg"]
	render:addAtStart(player.currentSheet)
	for i,v in ipairs({3,2,1}) do
		local b = SHSpace(sheets['under_bg'])
		b:setSpaceSprite(sprites["under_bg"][player.currentWorld.bg_prefix .. v .. ".png"]);
		b:setColor(255, 255, 255, 255)
		b:setSpeed(1/(v+1), 1/(v+1))
		b:setLoopY(false)
		-- b:setGridSize(100*(bg:getHeight()/1024), 100*(bg:getHeight()/1024))
		-- if ipad == '_ipad' then
		-- 	b:setOffset(0, bludG.camera.h-960)
		-- end
		table.insert(mainbg, b)
	end
end
function PlayState:setupBackground()
	if player.currentBg ~= player.currentWorld.bg_sheet_name then
		self:setupUnderBg()
	end
	self.bgLayers = Group()
	table.insert(self.members, 1, self.bgLayers)
	for i,v in ipairs(player.currentWorld.bg_gradients) do
		local gOver = self.bgLayers:add(MultiStopGradient(0,0,bludG.camera.w, bludG.camera.h, v))
		gOver.scrollFactor = Vec2(0,0)
	end
	local gc = player.currentWorld.ground_color;
	self.groundPlane = self.bgLayers:add(Object(-bludG.camera.w*100, bludG.camera.h, bludG.camera.w*200, bludG.camera.h*2))
	self.groundPlane:setColor(gc[1], gc[2], gc[3])
	self.groundPlane.sprite = sprites['tiles']['white.png']
	self.groundPlane.drawType = 'stretched'
	self.groundPlane.scrollFactor = Vec2(0, 1)

	local padding = 10*r_scale
	local groundSvg = SvgLoader(svg_root .. "hotels/base_top_" .. player.currentWorld.id .. ".svg", true)
	for i=-2,2 do
		local svgGround = self.bgLayers:add(Object(groundSvg:getMaxX()*i, bludG.camera.h+10, bludG.camera.w*5, bludG.camera.h*2))
		svgGround.svg = groundSvg
		svgGround.drawType = 'svg'		
	end
end

-- first build a list of all possible positions, these are places where the block is on the same axis and aligns
-- then order this list by distance from the current position
-- then go through the list and eliminate positions as they overlap with the world
function PlayState:possiblePositions(placement)
	local possiblePositions = {}
	for i,v in ipairs(self.rooms.members) do
		-- if the block intersects on the x axis
		if placement.pos.x+placement.w > v.pos.x and 
			placement.pos.x < v.pos.x+v.w
		then
			table.insert(possiblePositions, {x=placement.pos.x, y=v.pos.y-placement.h, parent=v})
			table.insert(possiblePositions, {x=placement.pos.x, y=v.pos.y+v.h, parent=v})
		end
		if placement.pos.y+placement.h > v.pos.y and 
			placement.pos.y < v.pos.y+v.h
		then
			table.insert(possiblePositions, {x=v.pos.x-placement.w, y=placement.pos.y, parent=v})
			table.insert(possiblePositions, {x=v.pos.x+v.w, y=placement.pos.y, parent=v})
		end
	end
	-- go through the possible positions and sort by distance from the placement block
	local p_a = Vec2(0,0)
	local p_b = Vec2(0,0)
	local p_l = Vec2(placement.pos.x+placement.w/2, placement.pos.y+placement.h/2)
	table.sort(possiblePositions, function(a, b)
		p_a.x, p_a.y = a.x+placement.w/2, a.y+placement.h/2
		p_b.x, p_b.y = b.x+placement.w/2, b.y+placement.h/2
		return p_a:distance(p_l) < p_b:distance(p_l)
	end)
	return possiblePositions
end

local dirVec = Vec2(0,0)
local function fixPoint(p,e)
	if e.d=='n' or e.d=='s' then
		if p.x<e.r.pos.x+e.r.w/2 then
			p.x = p.x+1
		else
			p.x = p.x-1
		end
	end
	if e.d=='e' or e.d=='w' then
		if p.y<e.r.pos.y+e.r.h/2 then
			p.y = p.y+1
		else
			p.y = p.y-1
		end
	end
end
function getSortFunction(c,sV,sV2)
	return function(a,b)
		local ad, bd = c:distanceSq(a.e),c:distanceSq(b.e)
		if ad==bd then
			sV.x, sV.y = a.e.x, a.e.y
			sV2.x, sV2.y = b.e.x, b.e.y
			fixPoint(sV, a, c)
			fixPoint(sV2, b)
			return c:distanceSq(sV)<c:distanceSq(sV2)
		end
		return ad<bd
	end
end
-- returns a collection of edges, sorted by distance
local r_val = {}
function collectEdgePositions(rooms, incoming)
	for k,v in pairs(r_val) do r_val[k]=nil end
	-- returns a set of tables with the position, the direction, and the parent
	local c = incoming:center()
	for i,v in ipairs(rooms) do
		if v.alive then
			-- if it doesn't have edges yet, then setup
			if not v.edges then
				v.edges = {};
				v.edges['n'] = {
					['p']=Vec2(0,0),
					['e']=Vec2(0,0),
					['d']='n',
					['r']=v
				}
				v.edges['s'] = {
					['p']=Vec2(0,0),
					['e']=Vec2(0,0),
					['d']='s',
					['r']=v
				}
				v.edges['e'] = {
					['p']=Vec2(0,0),
					['e']=Vec2(0,0),
					['d']='e',
					['r']=v
				}
				v.edges['w'] = {
					['p']=Vec2(0,0),
					['e']=Vec2(0,0),
					['d']='w',
					['r']=v
				}
			end
			-- go through n, s, e, w
			local n = v.edges['n']
			local s = v.edges['s']
			local e = v.edges['e']
			local w = v.edges['w']
			-- n
			n.p.x, n.p.y = math.max(math.min(incoming.pos.x, v.pos.x+v.w), v.pos.x-incoming.w), v.pos.y-incoming.h
			n.e.x, n.e.y = math.max(math.min(c.x, v.pos.x+v.w), v.pos.x), v.pos.y
			-- s
			s.p.x, s.p.y = math.max(math.min(incoming.pos.x, v.pos.x+v.w), v.pos.x-incoming.w), v.pos.y+v.h
			s.e.x, s.e.y = math.max(math.min(c.x, v.pos.x+v.w), v.pos.x), v.pos.y+v.h
			-- e
			e.p.x, e.p.y = v.pos.x-incoming.w, math.max(math.min(incoming.pos.y, v.pos.y+v.h), v.pos.y-incoming.h)
			e.e.x, e.e.y = v.pos.x, math.max(math.min(c.y, v.pos.y+v.h),v.pos.y)
			-- w
			w.p.x, w.p.y = v.pos.x+v.w, math.max(math.min(incoming.pos.y, v.pos.y+v.h), v.pos.y-incoming.h)
			w.e.x, w.e.y = v.pos.x+v.w, math.max(math.min(c.y, v.pos.y+v.h),v.pos.y)
			-- insert edges into return table
			table.insert(r_val, n); table.insert(r_val, e); table.insert(r_val, s); table.insert(r_val, w);
		end
	end
	local sV = Vec2(0,0)
	local sV2 = Vec2(0,0)
	-- sort the table by distance
	local sfunc = getSortFunction(c, sV, sV2)
	table.sort(r_val, sfunc)
	return r_val
end
-- returns a collection of positions that the current block can connect to
function PlayState:blockAudit(da, x, y, id)
	x, y = bludG.camera:getPositionZoom(x, y)
	-- make sure that we have a block that we can use for ghosting in
	if not self.ghostBlock then
		self.ghostBlock = self:add(Room(0,0, nil, {noHealth=true}))
	end
	if not self.fingerBlock then
		self.fingerBlock = self:add(self.newType.rtype(0, 0, self, {noHealth=true}));
		self.fingerBlock:setColor(255, 255, 255, 100)
	end
	local edges = collectEdgePositions(self.rooms.members, self.fingerBlock)
	for i,v in ipairs(edges) do
		self.ghostBlock:setPosition(v.p.x, v.p.y)
		-- check for overlaps
		self.commitParent = v.r
		local fudged = false
		if not self.rooms:overlap(self.ghostBlock, function(a, b)
			-- check to see if we can fudge it, if not then continue looping
			if v.d == 'e' or v.d == 'w' then
				local oy = b.pos.y
				if a.pos.y < b.pos.y then
					b.pos.y = math.min(b.pos.y+b.h/4, a.pos.y+b.h)
				else
					b.pos.y = math.max(b.pos.y-b.h/4, a.pos.y-b.h)
				end
				if b.pos.y >= v.r.pos.y-b.h and b.pos.y <= v.r.pos.y+v.r.h and not self.rooms:overlap(b) then
					fudged = true
				else
					b.pos.y = oy
				end
			else
				local ox = b.pos.x
				if a.pos.x < b.pos.x then
					b.pos.x = math.min(b.pos.x+b.w/4, a.pos.x+b.w)
				else
					b.pos.x = math.max(b.pos.x-b.w/4, a.pos.x-b.w)
				end
				if b.pos.x >= v.r.pos.x-b.w and b.pos.x <= v.r.pos.x+v.r.w and not self.rooms:overlap(b) then
					fudged = true
				else
					b.pos.x = ox
				end				
			end
		end) then
			break;
		end
		if fudged then
			print('fudged')
			break;
		end
	end
	self.fingerBlock:setPosition(x-self.fingerBlock.w/2, y-self.fingerBlock.h/2)
	self.ghostBlock:setColor(255, 255, 255, 60)
	self.ghostBlock:setColor(20, 255, 80, 70)
	self.commitReady = true
	self:addLine()
end
function PlayState:addLine()
	if not self.line then
		self.line = self:add(PlayerLine(self.commitParent:center(), self.ghostBlock:center()))
		-- self.line = self:add(PlayerLine(Vec2(x, y), self.ghostBlock:center()))
		self.line.sprite = sprites['tiles']['circle.png'] 
		self.line.w = 2
	  	self.line:setColor(255, 255, 255, 205)
	end
	-- self.line.a = self.rootRoom:center()
	self.line.b = self.ghostBlock:center()
	self.line.a = self.commitParent:center()
end
function PlayState:getTowerLimits()
	if not self.limits then 
		self.refreshLimits = true
		self.limits = {}
	end
	if self.refreshLimits then
		self.limits = {}
		for i,v in ipairs(self.rooms.members) do
			if v.alive then
				if not self.limits.minX or v.pos.x < self.limits.minX then
					self.limits.minX = v.pos.x
				end
				if not self.limits.minY or v.pos.y < self.limits.minY then
					self.limits.minY = v.pos.y
				end
				if not self.limits.maxX or v.pos.x+v.w > self.limits.maxX then
					self.limits.maxX = v.pos.x+v.w
				end
				if not self.limits.maxY or v.pos.y+v.h > self.limits.maxY then
					self.limits.maxY = v.pos.y+v.h
				end
			end
		end
	end
	return self.limits
end
function PlayState:blockCommit(da, x, y, id)
	if self.commitReady and self.commitParent and self.commitParent.alive then
		self.ghostBlock:setColor(255, 255, 255, 0)
		-- double check to make sure we have enough money
		local cost = game_settings[self.newType.settingName .. "cost"]
		if self.moneyController:getAmount() >= cost then
			self.moneyController:setAmount(self.moneyController:getAmount()-cost)
			local r = self.rooms:addRecycle(self.newType.rtype(self.ghostBlock.pos.x, self.ghostBlock.pos.y, self))
			r.parent = self.commitParent
			self.commitParent:addChild(r)
			player.achievements:addRoom()
			r.typecount = self.newType.typecount
			-- add greebles
			if self.greeble then self:greeble(r) end
			r:addBox2d()
			pd:startList("fromOF")
			pd:addSymbol("roomAdded")
			pd:addFloat(1)
			pd:finish("fromOF")
			self.refreshLimits = true
			local limits = self:getTowerLimits()
			local height = math.floor(((limits.maxY-limits.minY)/r_scale))
			player.achievements:newHeight(height)
		end
		self.newType = nil
		if not self.waveController.started then
			self.waveController:startWaves()
		end
		-- fix the camera position to contain the building
		self:fixCameraPosition()
	end
end
function PlayState:clearHelpers()
	if self.fingerBlock then
		self:remove(self.fingerBlock);
		self.fingerBlock = nil
	end
	if self.line then
		self:remove(self.line)
		self.line = nil
	end
	self.ghostBlock:setColor(0,0,0,0)
end
function PlayState:fixCameraPosition(padding)
	padding = padding or 80
	local limits = self:getTowerLimits()
	-- pad the limits enough so that we have a bit of space to play in
	local uiConstant = 100*r_scale
	padding = padding*r_scale
	
	-- I think the problem here is that the ui constant should be getting bigger as the zoom changes,
	-- but that feels like algebra, and I cant figure it out

	limits.minX = limits.minX - padding
	limits.minY = limits.minY - padding
	limits.maxX = limits.maxX + padding
	limits.maxY = limits.maxY + padding
	
	-- determine the zoom level necessary to contain the new building
	-- the padding actually needs to be integrated here.

	local wZ = bludG.camera.w/(limits.maxX-limits.minX)
	local hZ = bludG.camera.h/(limits.maxY-limits.minY)
	-- this is my terrible attempt at integration
	hZ = bludG.camera.h/(limits.maxY-limits.minY+(uiConstant/hZ)*2)

	local nscale = math.min(wZ, hZ)

	-- currently going to the limits of the building
	-- may eventually want to center on the core of the building
	local nsx = (limits.maxX-limits.minX)/2+limits.minX - (bludG.camera.w/2*(1/nscale))
	-- local uiConstant = 60*r_scale
	-- convert back to normal limit
	limits.maxY = limits.maxY-padding
	-- convert to screen space at the new zoom
	--   _y = (v.y - (camera.scroll.y*1) - 0)*(1+scaleDiff*1) + self.pos.y
	limits.maxY = limits.maxY*nscale
	-- this isn't working properly.
	-- what should happen
	-- determine the (world)height of the screen at the current scale
	-- determine the (world)height of the bottom padding at the current scale
	-- the bottom of the hotel should be at the world height of the bottom padding

	-- [                 || ]
	local wheight = bludG.camera.h/nscale
	local wbpad = uiConstant/nscale

	local nsy = (limits.maxY/nscale)-wheight+wbpad
	bludG.camera:zoomTo(nsx, nsy, nscale)
end


function PlayState:fingerRelease(da, x, y, id)
	-- self:fixCameraPosition(200)
end


function PlayState:fingerRelease(da, x, y, id)
	-- -- make sure the camera isn't past the last point of the ground 
	-- local wcx = bludG.camera.scale*bludG.camera.scroll.x
	-- wcx = math.min(math.max(wcx, -630*2*r_scale*.5), 630*3*r_scale*.5)

	-- bludG.camera:zoomTo(wcx, bludG.camera.scroll.y, bludG.camera.scale)
end
function PlayState:panModeHandler(da, x, y, id)
	local p = Vec2(x, y)
	p:sub(da.lastPos[id])
	-- convert to the screen scale
	p:mult(1/bludG.camera.scale)

	-- because we are just a horizontal slider, limit the slide to one axis
	if id == 0 and not self.nopan[id] then
		if bludG.camera.tween then Tweener:removeTween(bludG.camera.tween) end
		bludG.camera.scroll:sub(p)
		-- calculate the maximum camera height at this zoom level
		local shw = bludG.camera.h/bludG.camera.scale
		bludG.camera.scroll.y = math.min(bludG.camera.scroll.y, self.groundPlane.pos.y+self.groundPlane.h-shw)
	end
end
function PlayState:dragStart(da, x, y, id)
	self.commitReady = false
	local uiConstant = 100*r_scale
	if not self.nopan then self.nopan = {} end
	if bludG.camera.h-uiConstant < y then
		self.nopan[id] = true
	else
		self.nopan[id] = false
	end
end
function PlayState:greeble(newRoom)
	local addedGreeble = false
	local attemptCount = 0
	-- try to draw a pipe that goes to the parent room
	-- for now just draw a line from the center to the center
	newRoom.greeble = self:addRecycle(PlayerLine(newRoom:center(), newRoom.parent:center()))
	newRoom.greeble.w = 2
	newRoom.greeble:setColor(255,255,255,200)
	newRoom.greeble:fadeNoKill()
end
