
dofile("shellsort.lua")

local playerInfo = playerInfo

local daisy, math = daisy, math
local isMultiplayer, localPlayer, externalPlayer, aiPlayer, mapSeed

local playerSelected = false

local mouse = {
	left = false, right = false, prevLeft = false, prevRight = false,
	selecting = false, selX = 0, selY = 0, curX = 1, curY = 1, selectionTimer = 0, selectionObject = nil,
	activated = false
}
local gameObjects = {}
local nextObjectId = 1
local particleObjects = {}
local backgroundObjects = {}
local viewPos = { x = 0, y = 0 }
local gameInfo = {
	selectedObjects = {},
	commandGroups = {},
	commandGroupTimer = 0, commandGroupTimerId = 0,
	normalMode = true,
	buildMode = false,
	chatMode = false,
	voiceDelay = 0,
	warningDelay = 0,
	commandShips = {},
	nextSightUpdate = 0,
	infoTimer = 5,
	highlightBuildOption = nil
}
local chatLines = {}
local playerStats = {
	{ resources = 200, buildQueue = {}, researchItem = nil, buildTime = 0, researchTime = 0, technologies = {} },
	{ resources = 200, buildQueue = {}, researchItem = nil, buildTime = 0, researchTime = 0, technologies = {} }
}
--playerStats[1].technologies[14] = true
local aiTimers = {
	nextUnitCommand = 0,
	nextBuildCommand = 0,
	detectionTimer = 0
}
local aiBuildQueue = {}
local aiAsteroidGroup = {} -- used to help the AI track asteroid belts
local aiDetectionFlags = {}
local stars, starSprites, bgSprite, bgPosition, buildSprite, buildBackground = {}, {}, nil, {}, nil, nil
local unknownShipSprite, webSprite
local musicInfo = { introTimer = .75, currentMusic = nil }

local TYPE_CATCHER, TYPE_BLASTER, TYPE_HARVESTER, TYPE_FRIGATE, TYPE_PLASMAGUN, TYPE_MATERNAL, TYPE_INSPECTOR, TYPE_CLOAKING, TYPE_SUPER = 1, 2, 3, 4, 5, 6, 7, 8, 9
local REST_FIGHTER, REST_FRIGATE, REST_SPEED, REST_HITECH, REST_CLOAKING, REST_SUPER, REST_BUILD, REST_SUPERRANGE, REST_WEB = 10, 11, 12, 13, 14, 15, 16, 17, 18

local DEF_SIGHT, DEF_RADAR = 350*350, 500*500
local INSPECTOR_RADAR = 1200*1200
local MATERNAL_RADAR = 800*800

local particleLists = {
	smallExplosions = { { sprite = "explA", sound = "SmallExpl", numSounds = 3 }, { sprite = "explC", sound = "SmallExpl", numSounds = 3 } },
	largeExplosions = { { sprite = "explA", sound = "SmallExpl", numSounds = 3 }, { sprite = "explC", sound = "SmallExpl", numSounds = 3 },
						{ sprite = "explB", sound = "LargeExpl", numSounds = 3 }, { sprite = "fexplA", sound = "LargeExpl", numSounds = 3 } }
}

local objectTypes = {

	catcher = {
		name = "Catcher", typeId = TYPE_CATCHER,
		health = 50, armorType = 1,
		maxSpeed = 80, accel = 6, speedTechnology = REST_SPEED,
		isAttacker = true, attackAngle = math.pi * .35, attackRange = 225, attackRate = 1.5, attackWarmup = 0, attackCooldown = 0,
		trailName = "trail", selectSounds = 5, orderSounds = 3, soundName = "Catcher",
		muzzleOffset = {x = 4, y = 8},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 1
	},
	blaster = {
		name = "Blaster", typeId = TYPE_BLASTER,
		health = 60, armorType = 1,
		maxSpeed = 80, accel = 3,
		isAttacker = true, attackAngle = math.pi, attackRange = 125, attackRate = 2.0, attackWarmup = 1.00, attackCooldown = 0,
		warmupSprite = "plasmaFire",
		trailName = "trail", selectSounds = 4, orderSounds = 5, soundName = "Blaster",
		muzzleOffset = {x = 4, y = 8},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 1,
		warmupSound = "BlasterCharge", numWarmupSounds = 1
	},
	harvester = {
		name = "Harvester", typeId = TYPE_HARVESTER,
		health = 400, armorType = 2,
		maxSpeed = 40, accel = .75,
		isHarvester = true,
		trailName = "trail", selectSounds = 4, orderSounds = 6, soundName = "Harvester",
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 3
	},
	frigate = {
		name = "Attack Frigate", typeId = TYPE_FRIGATE,
		health = 600, armorType = 2,
		maxSpeed = 50, accel = .5,
		isAttacker = true, attackAngle = math.pi * .35, attackRange = 225, attackRate = 0.15, attackWarmup = 0, attackCooldown = 0,
		selectSounds = 1, orderSounds = 4, soundName = "Frigate",
		muzzleOffset = {x = 4, y = 28},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.largeExplosions, deathChainCount = 3
	},
	plasmagun = {
		name = "Plasmagun Frigate", typeId = TYPE_PLASMAGUN,
		health = 800, armorType = 2,
		maxSpeed = 50, accel = .75,
		isAttacker = true, attackAngle = math.pi * .05, attackRange = 200, attackRate = 0.0125, attackWarmup = 0, attackCooldown = 0,
		selectSounds = 3, orderSounds = 5, soundName = "Plasmagun",
		muzzleOffset = {x = 2, y = 26},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.largeExplosions, deathChainCount = 4
	},
	maternal = {
		name = "Maternal Vessel", typeId = TYPE_MATERNAL, health = 4000, armorType = 2,
		maxSpeed = 5, accel = 2,
		orderSounds = 2, soundName = "Maternal",
		sightRange = DEF_SIGHT, radarRange = MATERNAL_RADAR,
		muzzleOffset = {x = 6, y = 186}, attackRange = 300,
		deathChain = particleLists.largeExplosions, deathChainCount = 20, regenerates = true
	},
	inspector = {
		name = "Inspector", typeId = TYPE_INSPECTOR,
		health = 30, armorType = 1,
		maxSpeed = 120, accel = 6, isDetector = true, cloakTechnology = REST_CLOAKING,
		selectSounds = 11, orderSounds = 11, soundName = "Inspector", specialSoundCase = true,
		sightRange = DEF_SIGHT, radarRange = INSPECTOR_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 1
	},
	cloaking = {
		name = "Shadow Fighter", typeId = TYPE_CLOAKING,
		health = 50, armorType = 1,
		maxSpeed = 80, accel = 6, isCloaked = true,
		isAttacker = true, attackAngle = math.pi * .35, attackRange = 200, attackRate = 1.5, attackWarmup = 0, attackCooldown = 0,
		trailName = "trail", selectSounds = 3, orderSounds = 5, soundName = "Shadow",
		muzzleOffset = {x = 8, y = 26}, weaponTechnology = REST_WEB,
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 2
	},
	super = {
		name = "Missile Hulk", typeId = TYPE_SUPER,
		health = 1000, armorType = 2,
		maxSpeed = 45, accel = .3,
		isAttacker = true, attackAngle = math.pi, attackRange = 350, attackRate = 1.0, attackWarmup = 0, attackCooldown = 0, multiMissile = true,
		selectSounds = 6, orderSounds = 11, soundName = "Hulk",
		muzzleOffset = {x = 18, y = 28}, rangeTechnology = REST_SUPERRANGE,
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.largeExplosions, deathChainCount = 7
	},
	
	catcherT = {
		name = "Catcher", typeId = TYPE_CATCHER,
		health = 50, armorType = 1,
		maxSpeed = 80, accel = 6, speedTechnology = REST_SPEED,
		isAttacker = true, attackAngle = math.pi * .35, attackRange = 225, attackRate = 1.5, attackWarmup = 0, attackCooldown = 0,
		trailName = "trail", selectSounds = 5, orderSounds = 3, soundName = "Catcher",
		muzzleOffset = {x = 4, y = 10},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 1
	},
	blasterT = { 
		name = "Blaster", typeId = TYPE_BLASTER,
		health = 60, armorType = 1,
		maxSpeed = 80, accel = 3, 
		isAttacker = true, attackAngle = math.pi, attackRange = 125, attackRate = 2.0, attackWarmup = 1.00, attackCooldown = 0,
		warmupSprite = "plasmaFire",
		trailName = "trail", selectSounds = 4, orderSounds = 5, soundName = "Blaster",
		muzzleOffset = {x = 2, y = 10},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 1,
		warmupSound = "BlasterCharge", numWarmupSounds = 1
	},
	harvesterT = { 
		name = "Harvester", typeId = TYPE_HARVESTER,
		health = 400, armorType = 2,
		maxSpeed = 40, accel = .75,
		isHarvester = true,
		trailName = "trail", selectSounds = 4, orderSounds = 6, soundName = "Harvester",
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 3
	},
	frigateT = {
		name = "Attack Frigate", typeId = TYPE_FRIGATE,
		health = 600, armorType = 2,
		maxSpeed = 50, accel = .5,
		isAttacker = true, attackAngle = math.pi * .35, attackRange = 225, attackRate = 0.15, attackWarmup = 0, attackCooldown = 0,
		selectSounds = 1, orderSounds = 4, soundName = "Frigate",
		muzzleOffset = {x = 2, y = 18},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.largeExplosions, deathChainCount = 3
	},
	plasmagunT = {
		name = "Plasmagun Frigate", typeId = TYPE_PLASMAGUN,
		health = 800, armorType = 2,
		maxSpeed = 50, accel = .75,
		isAttacker = true, attackAngle = math.pi * .05, attackRange = 200, attackRate = 0.0125, attackWarmup = 0, attackCooldown = 0,
		selectSounds = 3, orderSounds = 5, soundName = "Plasmagun",
		muzzleOffset = {x = 12, y = 22},
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.largeExplosions, deathChainCount = 4
	},
	maternalT = {
		name = "Command Vessel", health = 4000, armorType = 2, typeId = TYPE_MATERNAL,
		maxSpeed = 5, accel = 2,
		orderSounds = 2, soundName = "Maternal",
		sightRange = DEF_SIGHT, radarRange = MATERNAL_RADAR,
		muzzleOffset = {x = 38, y = 30}, attackRange = 300,
		deathChain = particleLists.largeExplosions, deathChainCount = 20, regenerates = true
	},
	inspectorT = {
		name = "Inspector", typeId = TYPE_INSPECTOR,
		health = 30, armorType = 1,
		maxSpeed = 120, accel = 6, isDetector = true, cloakTechnology = REST_CLOAKING,
		selectSounds = 11, orderSounds = 11, soundName = "Inspector", specialSoundCase = true,
		sightRange = DEF_SIGHT, radarRange = INSPECTOR_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 1
	},
	cloakingT = {
		name = "Shadow Fighter", typeId = TYPE_CLOAKING,
		health = 50, armorType = 1,
		maxSpeed = 80, accel = 6, isCloaked = true,
		isAttacker = true, attackAngle = math.pi * .35, attackRange = 200, attackRate = 1.5, attackWarmup = 0, attackCooldown = 0,
		trailName = "trail", selectSounds = 3, orderSounds = 5, soundName = "Shadow",
		muzzleOffset = {x = 10, y = 20}, weaponTechnology = REST_WEB,
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.smallExplosions, deathChainCount = 2
	},
	superT = {
		name = "Missile Hulk", typeId = TYPE_SUPER,
		health = 1000, armorType = 2,
		maxSpeed = 45, accel = .3,
		isAttacker = true, attackAngle = math.pi, attackRange = 350, attackRate = 1.0, attackWarmup = 0, attackCooldown = 0, multiMissile = true,
		selectSounds = 6, orderSounds = 11, soundName = "Hulk",
		muzzleOffset = {x = 26, y = 20}, rangeTechnology = REST_SUPERRANGE,
		sightRange = DEF_SIGHT, radarRange = DEF_RADAR,
		deathChain = particleLists.largeExplosions, deathChainCount = 7
	},
	
	resourceSmall1 = { isResource = true, resourceValue = 16 },
	resourceSmall2 = { isResource = true, resourceValue = 17  },
	resourceSmall3 = { isResource = true, resourceValue = 17  },
	resourceSmall4 = { isResource = true, resourceValue = 18  },
	resourceMedium1 = { isResource = true, resourceValue = 20  },
	resourceMedium2 = { isResource = true, resourceValue = 21 },
	resourceMedium3 = { isResource = true, resourceValue = 22  },
	resourceMedium4 = { isResource = true, resourceValue = 24  },
	resourceLarge1 = { isResource = true, resourceValue = 25  },
	resourceLarge2 = { isResource = true, resourceValue = 26  },
	resourceLarge3 = { isResource = true, resourceValue = 27  },
	resourceLarge4 = { isResource = true, resourceValue = 29  },
	
	-- particle types
	
	smallBullet = { isProjectile = true, velocity = 350, damage = 8, damageType = 1, muzzleSprite = "catcherFire", muzzleSound = "Gun", numMuzzleSounds = 3, impactSound = "BulletImpact", numImpactSounds = 2, impactParticle = "bulletHit" },
	blastBomb = { isProjectile = true, velocity = 300, damage = 60, damageType = 2, deathParticle = "pexplA", impactSound = "BlasterHit", numImpactSounds = 1, muzzleSound = "BlasterShoot", numMuzzleSounds = 1 },
	plasmaRay = { isProjectile = true, velocity = 320, damage = 3, damageType = 2, impactParticle = "trailFrigate" },
	hulkMissile = { isProjectile = true, velocity = 350, initialVelocity = 50, accel = 3, damage = 30, damageType = 3, isTracking = true, animatedSprite = "missileA", deathParticle = "poff", muzzleSprite = "catcherFire", impactSound = "MissileImpact", numImpactSounds = 1, muzzleSound = "MissileLaunch", numMuzzleSounds = 1 },
	shadowBulletNormal = { isProjectile = true, velocity = 350, damage = 16, damageType = 3, muzzleSprite = "catcherFire", muzzleSound = "Gun", numMuzzleSounds = 3, impactSound = "BulletImpact", numImpactSounds = 2, impactParticle = "bulletHit", animatedSprite = "shadowBullet" },
	shadowBulletWeb = { isProjectile = true, velocity = 350, damage = 8, damageType = 1, muzzleSprite = "catcherFire", muzzleSound = "Gun", numMuzzleSounds = 3, impactSound = "BulletImpact", numImpactSounds = 2, impactParticle = "shadowHit", animatedSprite = "shadowBullet", doWeb = true },
	
	chainExplosion = { renderFunction = function(obj) end },
	spriteParticle = {  },
	staticSprite = {  },
}
objectTypes.catcher.projectileType = objectTypes.smallBullet
objectTypes.catcherT.projectileType = objectTypes.smallBullet
objectTypes.blaster.projectileType = objectTypes.blastBomb
objectTypes.blasterT.projectileType = objectTypes.blastBomb
objectTypes.frigate.projectileType = objectTypes.smallBullet
objectTypes.frigateT.projectileType = objectTypes.smallBullet
objectTypes.plasmagun.projectileType = objectTypes.plasmaRay
objectTypes.plasmagunT.projectileType = objectTypes.plasmaRay
objectTypes.cloaking.projectileType = objectTypes.smallBullet
objectTypes.cloakingT.projectileType = objectTypes.smallBullet
objectTypes.cloaking.projectileType2 = objectTypes.shadowBulletWeb
objectTypes.cloakingT.projectileType2 = objectTypes.shadowBulletWeb
objectTypes.super.projectileType = objectTypes.hulkMissile
objectTypes.superT.projectileType = objectTypes.hulkMissile
objectTypes.maternal.projectileType = objectTypes.smallBullet
objectTypes.maternalT.projectileType = objectTypes.smallBullet

local function doNothingUpdate(obj, time)
end
local function resourceUpdate(obj, time)
	if obj.carrier and obj.carrier.died then
		obj.carrier = nil
	end
end
objectTypes.resourceSmall1.updateFunction = resourceUpdate
objectTypes.resourceSmall2.updateFunction = resourceUpdate
objectTypes.resourceSmall3.updateFunction = resourceUpdate
objectTypes.resourceSmall4.updateFunction = resourceUpdate
objectTypes.resourceMedium1.updateFunction = resourceUpdate
objectTypes.resourceMedium2.updateFunction = resourceUpdate
objectTypes.resourceMedium3.updateFunction = resourceUpdate
objectTypes.resourceMedium4.updateFunction = resourceUpdate
objectTypes.resourceLarge1.updateFunction = resourceUpdate
objectTypes.resourceLarge2.updateFunction = resourceUpdate
objectTypes.resourceLarge3.updateFunction = resourceUpdate
objectTypes.resourceLarge4.updateFunction = resourceUpdate
objectTypes.staticSprite.updateFunction = doNothingUpdate

local constructionTypes = {
	catcher = { key = 67, keyString = "C", name = "Catcher", cost = 45, buildTime = 4.5, buildId = TYPE_CATCHER, result = { objectTypes.catcher, objectTypes.catcherT }, requirement = { REST_FIGHTER } },
	blaster = { key = 65, keyString = "A", name = "Blaster", cost = 65, buildTime = 6.5, buildId = TYPE_BLASTER, result = { objectTypes.blaster, objectTypes.blasterT }, requirement = { REST_HITECH } },
	harvester = { key = 72, keyString = "H", name = "Harvester", cost = 150, buildTime = 15, buildId = TYPE_HARVESTER, result = { objectTypes.harvester, objectTypes.harvesterT }, requirement = nil },
	frigate = { key = 70, keyString = "F", name = "Attack Frigate", cost = 350, buildTime = 28, buildId = TYPE_FRIGATE, result = { objectTypes.frigate, objectTypes.frigateT }, requirement = { REST_FRIGATE } },
	plasmagun = { key = 80, keyString = "P", name = "Plasmagun Frigate", cost = 450, buildTime = 38, buildId = TYPE_PLASMAGUN, result = { objectTypes.plasmagun, objectTypes.plasmagunT }, requirement = { REST_FRIGATE, REST_HITECH } },
	inspector = { key = 73, keyString = "I", name = "Inspector", cost = 30, buildTime = 15.0, buildId = TYPE_INSPECTOR, result = { objectTypes.inspector, objectTypes.inspectorT }, requirement = nil },
	cloaking = { key = 83, keyString = "S", name = "Shadow Fighter", cost = 80, buildTime = 7, buildId = TYPE_CLOAKING, result = { objectTypes.cloaking, objectTypes.cloakingT }, requirement = { REST_CLOAKING } },
	super = { key = 77, keyString = "M", name = "Missile Hulk", cost = 575, buildTime = 53, buildId = TYPE_SUPER, result = { objectTypes.super, objectTypes.superT }, requirement = { REST_SUPER } },
	
	-- research types
	fighterResearch = { key = 67, keyString = "C", name = "Fighter Chassi Research", cost = 300, buildTime = 60, buildId = REST_FIGHTER, isResearch = true, requirement = nil },
	frigateResearch = { key = 70, keyString = "F", name = "Frigate Chassi Research", cost = 300, buildTime = 60, buildId = REST_FRIGATE, isResearch = true, requirement = { REST_FIGHTER } },
	speedResearch = { key = 69, keyString = "E", name = "Catcher Speed Research", cost = 150, buildTime = 60, buildId = REST_SPEED, isResearch = true, requirement = { REST_FIGHTER } },
	hitechResearch = { key = 84, keyString = "T", name = "High-Technology Research", cost = 150, buildTime = 60, buildId = REST_HITECH, isResearch = true, requirement = { REST_FIGHTER } },
	cloakingResearch = { key = 83, keyString = "S", name = "Stealth Technology Research", cost = 150, buildTime = 60, buildId = REST_CLOAKING, isResearch = true, requirement = { REST_HITECH } },
	superResearch = { key = 85, keyString = "U", name = "Super-Class Chassi Research", cost = 150, buildTime = 80, buildId = REST_SUPER, isResearch = true, requirement = { REST_HITECH, REST_FRIGATE } },
	buildResearch = { key = 79, keyString = "O", name = "Construction Rate Research", cost = 150, buildTime = 120, buildId = REST_BUILD, isResearch = true, requirement = { REST_FIGHTER } },
	webResearch = { key = 87, keyString = "W", name = "Shadow Fighter Web Research", cost = 150, buildTime = 80, buildId = REST_WEB, isResearch = true, requirement = { REST_CLOAKING } },
	rangeResearch = { key = 85, keyString = "U", name = "Missile Hulk Range Research", cost = 300, buildTime = 60, buildId = REST_SUPERRANGE, isResearch = true, requirement = { REST_SUPER } }
}
local constructionTypeGui = {
	{ type = constructionTypes.harvester, spriteName = { "Iconharvester", "IconTharvester" }, x = 278, y = 410, lines = nil },
	{ type = constructionTypes.catcher, spriteName = { "Iconcatcher", "IconTcatcher" }, x = 386, y = 400-44, lines = nil },
	{ type = constructionTypes.blaster, spriteName = { "Iconblaster", "IconTblaster" }, x = 188, y = 334-44, lines = nil },
	{ type = constructionTypes.frigate, spriteName = { "Iconattackfrigate", "IconTattackfrigate" }, x = 452, y = 334-44, lines = nil },
	{ type = constructionTypes.plasmagun, spriteName = { "Iconplasmagunfrigate", "IconTplasmagunfrigate" }, x = 320, y = 334-44, lines = nil },
	{ type = constructionTypes.cloaking, spriteName = { "Iconshadow", "IconTshadow" }, x = 254, y = 268-44, lines = nil },
	{ type = constructionTypes.super, spriteName = { "Iconmissilehulk", "IconTmissilehulk" }, x = 386, y = 268-44, lines = nil },
	{ type = constructionTypes.inspector, spriteName = { "Iconinspector", "IconTinspector" }, x = 352, y = 410, lines = nil },
	{ type = constructionTypes.fighterResearch, spriteName = { "IconRFighter", "IconRFighter" }, x = 320, y = 400-44, lines = { { x = 320-33, y = 356, type = 1 }, { x = 320+33, y = 356, type = 1 }, { x = 320+33, y = 356-33, type = 4 }, { x = 320-33, y = 356-33, type = 3 } } },
	{ type = constructionTypes.frigateResearch, spriteName = { "IconRFrigate", "IconRFrigate" }, x = 386, y = 334-44, lines = { { x = 386-33, y = 290, type = 1 }, { x = 386+33, y = 290, type = 1 }, { x = 386-33, y = 290-33, type = 3 } } },
	{ type = constructionTypes.hitechResearch, spriteName = { "IconRHighTech", "IconRHighTech" }, x = 254, y = 334-44, lines = { { x = 254-33, y = 290, type = 1 }, { x = 254+33, y = 290, type = 1 }, { x = 254+33, y = 290-33, type = 4 }, { x = 254-33, y = 290-33, type = 3 } } },
	{ type = constructionTypes.cloakingResearch, spriteName = { "IconRCloaking", "IconRCloaking" }, x = 188, y = 268-44, lines = { { x = 188+33, y = 268-44, type = 1 } } },
	{ type = constructionTypes.superResearch, spriteName = { "IconRSuper", "IconRSuper" }, x = 320, y = 268-44, lines = { { x = 320+33, y = 268-44, type = 1 } } },
	{ type = constructionTypes.speedResearch, spriteName = { "IconRCatcherSpeed", "IconRCatcherSpeed" }, x = 430, y = 400-44, lines = nil },
	{ type = constructionTypes.rangeResearch, spriteName = { "IconRHulkRange", "IconRHulkRange" }, x = 386, y = 224-44, lines = nil },
	{ type = constructionTypes.webResearch, spriteName = { "IconRWeb", "IconRWeb" }, x = 254, y = 224-44, lines = nil },
	{ type = constructionTypes.buildResearch, spriteName = { "IconRConstructionRate", "IconRConstructionRate" }, x = 254, y = 400-44, lines = nil }
}
local constructionTypeLines = {
}

local MAP_SIZE, FIELD_LEFT, FIELD_RIGHT, FIELD_TOP, FIELD_BOTTOM, FIELD_WIDTH, FIELD_HEIGHT

local ORDER_NONE, ORDER_MOVE, ORDER_HARVEST, ORDER_RETURN, ORDER_ATTACK, ORDER_ATTACK_MOVE = 0, 1, 2, 3, 4, 5

local REGENERATE_SPEED = 2

-- MANAGEMENT --- --- --- --- --- --- --- --- --- --- ---
--    MANAGEMENT
--       MANAGEMENT
--          MANAGEMENT
--             MANAGEMENT
--                MANAGEMENT
-- MANAGEMENT
--    MANAGEMENT
--       MANAGEMENT
--          MANAGEMENT
--             MANAGEMENT
--                MANAGEMENT --- --- --- --- --- --- --- --- --- --- ---

local prevRandomSound = 1
local function getNewSoundNumber(max)
	prevRandomSound = prevRandomSound + 1
	return (prevRandomSound % max) + 1
end

local function playPositionSound(x, y, soundName, numSounds)

	if not gameInfo.normalMode then
		-- no sounds in minimap
		return
	end
	
	local dx,dy = viewPos.x + 320 - x, viewPos.y + 240 - y
	local volume = 1.0
	local panning = 0
	
	if math.abs(dx) > 320 or math.abs(dy) > 240 then
		local dist = math.sqrt(dx*dx + dy*dy)
		volume = 1 - (dist - 320) / 1000
		if volume > 1 then
			volume = 1
		end
		
		if dx < 0 then
			panning = -1 + volume
		else
			panning = 1 - volume
		end
	end
	
	if volume > 0 then
		daisy.playSound(soundName .. getNewSoundNumber(numSounds) .. ".ogg", volume, panning, 1.0)
	end

end

local function setObjectDefaults(obj)

	obj.vx = obj.vx or 0
	obj.vy = obj.vy or 0
	obj.tx = obj.tx or obj.x
	obj.ty = obj.ty or obj.y
	obj.owner = obj.owner or -1
	obj.order = obj.order or ORDER_NONE
	obj.health = obj.health or obj.type.health or 0
	obj.id = obj.id or -1

end

local function createTestObject(x, y)

	local obj = { sprite = circleSprite, x = x, y = y }
	setObjectDefaults(obj)

	table.insert(gameObjects, obj )
	
end

local function createShip(x, y, owner, type, mirrored)

	local obj = { sprite = type.sprite, x = x, y = y, type = type, owner = owner, mirrored = mirrored }

	obj.id = nextObjectId
	nextObjectId = nextObjectId + 1
	obj.isCloaked = type.isCloaked
	
	setObjectDefaults(obj)

	table.insert(gameObjects, obj )
	
	return obj
end

local function createParticleBase(x, y, spriteName)

	local obj = { x = x, y = y, type = objectTypes.spriteParticle }
	if spriteName then
		obj.sprite = daisy.createSpriteState(spriteName, "IngameGfx.dat")
	else
		obj.sprite = nil
	end
	setObjectDefaults(obj)

	return obj
end


local function createParticle(x, y, spriteName)
	local obj = createParticleBase(x, y, spriteName)
	table.insert(particleObjects, obj)
	return obj
end

local function createBackgroundParticle(x, y, spriteName)
	local obj = createParticleBase(x, y, spriteName)
	table.insert(backgroundObjects, obj)
	return obj
end

local function createExplosionChain(x, y, w, h, list, count)
	local obj = {type = objectTypes.chainExplosion, x = x, y = y, w = w, h = h, explosionDelay = 0, explosionCount = count, particleList = list}
	table.insert(backgroundObjects, obj)
	return obj
end

local function createStaticSprite(x, y, sprite, mirrored)
	
	local obj = { x = x, y = y, type = objectTypes.staticSprite, sprite = sprite }
	setObjectDefaults(obj)
	table.insert(backgroundObjects, obj)

	return obj
end

local function createProjectile(shooter, target)

	local type = shooter.type.projectileType
	if shooter.type.weaponTechnology and playerStats[shooter.owner].technologies[shooter.type.weaponTechnology] and shooter.type.projectileType2 then
		type = shooter.type.projectileType2
	end
	local x, y = shooter.x + shooter.type.muzzleOffset.x, shooter.y + shooter.type.muzzleOffset.y
	if shooter.mirrored then
		x = shooter.x + shooter.type.w - shooter.type.muzzleOffset.x
	end
	local sprite = type.sprite
	if type.animatedSprite then
		sprite = daisy.createSpriteState(type.animatedSprite, "IngameGfx.dat")
	end
	local obj = { sprite = sprite, x = x, y = y, type = type, owner = shooter.owner, mirrored = false, shooter = shooter, target = target }
	setObjectDefaults(obj)
	table.insert(particleObjects, obj)
	
	if type.muzzleSprite then
		createParticle(x, y, type.muzzleSprite)
	end
	if type.muzzleSound then
		playPositionSound(obj.x, obj.y, type.muzzleSound, type.numMuzzleSounds)
	end
	
	if type.isTracking and type.initialVelocity then
		obj.currentAngle = 0
		obj.velocity = type.initialVelocity
		
		if not shooter.mirrored then
			obj.currentAngle = math.pi
		end
	end
	
	return obj
end

local function createMoveMarker(x, y)
	local obj = createParticle(x, y, "MouseMove")
	obj.color = { r = 0, g = 255, b = 0 }
	return obj
end
local function createTargetMarker(target)
	local obj = createParticle(target.x + target.type.w / 2, target.y + target.type.h / 2, "MouseTarget")
	obj.target = target
	if target.owner <= 0 then
		obj.color = { r = 255, g = 255, b = 0 }
	elseif target.owner == localPlayer then
		obj.color = { r = 0, g = 255, b = 0 }
	else
		obj.color = { r = 255, g = 0, b = 0 }
	end
	return obj
end

local function createResource(x, y, size)
	
	local type
	local subType = math.random(1,4)
	if size == 1 then
		if subType == 1 then
			type = objectTypes.resourceSmall1
		elseif subType == 2 then
			type = objectTypes.resourceSmall2
		elseif subType == 2 then
			type = objectTypes.resourceSmall3
		else
			type = objectTypes.resourceSmall4
		end
	elseif size == 2 then
		if subType == 1 then
			type = objectTypes.resourceMedium1
		elseif subType == 2 then
			type = objectTypes.resourceMedium2
		elseif subType == 2 then
			type = objectTypes.resourceMedium3
		else
			type = objectTypes.resourceMedium4
		end
	else
		if subType == 1 then
			type = objectTypes.resourceLarge1
		elseif subType == 2 then
			type = objectTypes.resourceLarge2
		elseif subType == 2 then
			type = objectTypes.resourceLarge3
		else
			type = objectTypes.resourceLarge4
		end
	end
	
	if x < (FIELD_LEFT + 10) then
		x = (FIELD_LEFT + 10)
	elseif x > (FIELD_RIGHT - 10) then
		x = (FIELD_RIGHT - 10)
	end
	if y < (FIELD_TOP + 10) then
		y = (FIELD_TOP + 10)
	elseif y > (FIELD_BOTTOM - 10) then
		y = (FIELD_BOTTOM - 10)
	end
		
	local obj = { sprite = type.sprite, type = type, x = x, y = y, owner = -1 }
	obj.id = nextObjectId
	nextObjectId = nextObjectId + 1

	setObjectDefaults(obj)

	table.insert(gameObjects, obj)
end

local function realizeBuyShip(owner, buildItemType)
	local buildItem = constructionTypes.catcher

	for index,type in pairs(constructionTypes) do
		if type.buildId == buildItemType then
			buildItem = type
			break
		end
	end
	
	if buildItem.requirement then
		for i=1,#(buildItem.requirement) do
			if not playerStats[owner].technologies[ buildItem.requirement[i] ] then
				return false
			end
		end
	end
	
	-- if it's a research option, make sure it isn't already listed in the build queue (or already researched)
	if buildItem.isResearch then
		if playerStats[owner].researchItem then
			return false
		end
		if playerStats[owner].technologies[buildItem.buildId] then
			return false
		end
	end
	
	if playerStats[owner].resources >= buildItem.cost then
		playerStats[owner].resources = playerStats[owner].resources - buildItem.cost
		
		if buildItem.isResearch then
			playerStats[owner].researchItem = buildItem
			
			if owner == localPlayer and playerStats[localPlayer].researchTime <= 0 then
				daisy.playSound("Researching.ogg")
			end
		else
			table.insert(playerStats[owner].buildQueue, buildItem)
			
			if owner == localPlayer and playerStats[localPlayer].buildTime <= 0 then
				daisy.playSound("ConstructionStart.ogg")
			end
		end
		
		return true
	end
	if owner == localPlayer and gameInfo.voiceDelay <= 0 then
		daisy.playSound("YouNeedCash.ogg")
		gameInfo.voiceDelay = 2
	end
	return false
end

local function buyShip(owner, buildItemType)
	if isMultiplayer then
		-- create a packet
		local packet = {2, buildItemType}
		multiplayer.addTurnCommand(packet)
	else
		return realizeBuyShip(owner, buildItemType)
	end
end

local function realizeCancelPurchase(owner)

	local count =  #(playerStats[owner].buildQueue)
	if count > 0 then
		local itemType = playerStats[owner].buildQueue[count]
		playerStats[owner].resources = playerStats[owner].resources + itemType.cost
		
		table.remove(playerStats[owner].buildQueue, count)
		
		if count == 1 then
			playerStats[owner].buildTime = 0
		end
	end

end

local function cancelPurchase(owner)
	if isMultiplayer then
		-- create a packet
		local packet = {3}
		multiplayer.addTurnCommand(packet)
	else
		return realizeCancelPurchase(owner)
	end
end

-- UPDATING --- --- --- --- --- --- --- --- --- --- ---
--    UPDATING
--       UPDATING
--          UPDATING
--             UPDATING
--                UPDATING
-- UPDATING
--    UPDATING
--       UPDATING
--          UPDATING
--             UPDATING
--                UPDATING --- --- --- --- --- --- --- --- --- --- ---

local function defaultObjectSlowdown(obj, time, accel)
	obj.x = obj.x + obj.vx * time
	obj.y = obj.y + obj.vy * time
	obj.vx = obj.vx - obj.vx * accel * time
	obj.vy = obj.vy - obj.vy * accel * time
end

local function defaultObjectMove(obj, time, maxSpeed, accel)

	if obj.onHold then
		defaultObjectSlowdown(obj, time, accel * 2)
		return
	end
	if obj.webTimer and obj.webTimer > 0 then
		maxSpeed = maxSpeed * .66
	end

	obj.x = obj.x + obj.vx * time
	obj.y = obj.y + obj.vy * time * .8
	
	local dx = obj.tx - obj.x
	local dy = obj.ty - obj.y
	local length = math.sqrt(dx*dx + dy*dy)
	if length <= 10 then
		defaultObjectSlowdown(obj, time, accel * 2)
		if obj.order == ORDER_MOVE or obj.order == ORDER_ATTACK_MOVE then
			obj.order = ORDER_NONE
		end
		return
	end
	
	dx = dx / length
	dy = dy / length
	
	local multiplier = accel * time
	if dx > 0 then
		if obj.vx < 0 then
			multiplier = multiplier * 2
		end
		obj.vx = obj.vx + (maxSpeed - obj.vx) * multiplier
		
		if obj.vx > (maxSpeed * dx) then
			obj.vx = (maxSpeed * dx)
		end
	elseif dx < 0 then
		if obj.vx > 0 then
			multiplier = multiplier * 2
		end
		obj.vx = obj.vx + (-maxSpeed - obj.vx) * multiplier
		if obj.vx < (maxSpeed * dx) then
			obj.vx = (maxSpeed * dx)
		end
	end

	if dy > 0 then
		if obj.vy < 0 then
			multiplier = multiplier * 2
		end
		obj.vy = obj.vy + (maxSpeed - obj.vy) * multiplier
		if obj.vy > (maxSpeed * dy) then
			obj.vy = (maxSpeed * dy)
		end
	elseif dy < 0 then
		if obj.vy > 0 then
			multiplier = multiplier * 2
		end
		obj.vy = obj.vy + (-maxSpeed - obj.vy) * multiplier
		if obj.vy < (maxSpeed * dy) then
			obj.vy = (maxSpeed * dy)
		end
	end
	
	if obj.type.trailName and (obj.isDetected or obj.owner == localPlayer) then
		obj.trailDelay = obj.trailDelay or 0
		obj.trailDelay = obj.trailDelay - time * (math.abs(obj.vx) + math.abs(obj.vy))
		
		if obj.trailDelay <= 0 then
			local trailX = obj.x
			if not obj.mirrored then
				trailX = trailX + obj.type.w
			end
			createBackgroundParticle(trailX, obj.y + obj.type.h / 2, obj.type.trailName)
			
			obj.trailDelay = obj.trailDelay + 10
		end
	end
	
	if obj.x < (FIELD_LEFT + 5) then
		obj.x = (FIELD_LEFT + 5)
	elseif obj.x > (FIELD_RIGHT - 5) then
		obj.x = (FIELD_RIGHT - 5)
	end
	if obj.y < (FIELD_TOP + 5) then
		obj.y = (FIELD_TOP + 5)
	elseif obj.y > (FIELD_BOTTOM - 5) then
		obj.y = (FIELD_BOTTOM - 5)
	end
		
end

local function defaultObjectUpdate(obj, time)

	local maxSpeed, accel = 50, 1
	if obj.type and obj.type.maxSpeed then
		maxSpeed = obj.type.maxSpeed
	end
	if obj.type and obj.type.accel then
		accel = obj.type.accel
	end
	defaultObjectMove(obj, time, maxSpeed, accel)
end

local function updateProjectile(obj, time)

	if not obj.target or obj.target.died then
		obj.died = true
	else
	
		if obj.type.animatedSprite then
			daisy.updateSpriteState(obj.sprite, time)
		end

		local tw, th = obj.target.type.w, obj.target.type.h
		if obj.type.isTracking then
		
			local targetVelocity = obj.type.velocity
			local targetX, targetY = obj.target.x + obj.target.type.w / 2, obj.target.y + obj.target.type.h / 2
			
			--[[
			if targetX > obj.x then
				obj.vx = obj.vx + (obj.type.velocity - obj.vx) * time
			else
				obj.vx = obj.vx + (-obj.type.velocity - obj.vx) * time
			end
			if targetY > obj.y then
				obj.vy = obj.vy + (obj.type.velocity * .25 - obj.vy) * time
			else
				obj.vy = obj.vy + (-obj.type.velocity * .25 - obj.vy) * time
			end
			obj.x = obj.x + obj.vx * time
			obj.y = obj.y + obj.vy * time
			]]
			
			local angle = math.atan2(targetY - obj.y, targetX - obj.x)
			if angle < 0 then
				angle = angle + math.pi * 2
			elseif angle > (math.pi * 2) then
				print("asdf")
			end
			
			local angleDiff = angle - obj.currentAngle
			if (angleDiff > 0 and angleDiff < math.pi) or angleDiff < -math.pi then
				-- clockwise
				obj.currentAngle = obj.currentAngle + time * 6.0
				
				if obj.currentAngle > (math.pi * 2) then
					obj.currentAngle = obj.currentAngle - math.pi * 2
				end
				
				-- check if we've passed the wanted angle
				angleDiff = angle - obj.currentAngle
				if not ((angleDiff > 0 and angleDiff < math.pi) or angleDiff < -math.pi) then
					obj.currentAngle = angle
				else
					targetVelocity = targetVelocity * .5
				end
			else
				-- counter-clockwise
				obj.currentAngle = obj.currentAngle - time * 6.0
				
				if obj.currentAngle < 0 then
					obj.currentAngle = obj.currentAngle + math.pi * 2
				end
				
				angleDiff = angle - obj.currentAngle
				if (angleDiff > 0 and angleDiff < math.pi) or angleDiff < -math.pi then
					obj.currentAngle = angle
				else
					targetVelocity = targetVelocity * .5
				end
			end
			
			obj.velocity = obj.velocity + (targetVelocity - obj.velocity) * time * obj.type.accel
			
			obj.x = obj.x + obj.velocity * math.cos(obj.currentAngle) * time
			obj.y = obj.y + obj.velocity * math.sin(obj.currentAngle) * time * .75
			
			obj.mirrored = (obj.currentAngle < math.pi * .5) or (obj.currentAngle > math.pi * 1.5)
		
		else
			local targetY = obj.target.y
			if obj.y < targetY then
				targetY = targetY + 4
			elseif obj.y > (targetY + th) then
				targetY = targetY + th - 4
			else
				targetY = (obj.y + targetY + th / 2) / 2
			end
			local dx, dy = (obj.target.x + tw / 2) - obj.x, targetY - obj.y
			local angle = math.atan2(dy, dx)
			
			obj.x = obj.x + obj.type.velocity * math.cos(angle) * time
			obj.y = obj.y + obj.type.velocity * math.sin(angle) * time
		end
		
		local left,top,right,bottom = obj.target.x + 4, obj.target.y + 4, obj.target.x + tw - 4, obj.target.y + th - 4
		if obj.x >= left and obj.x <= right and obj.y >= top and obj.y <= bottom then
		
			local damage = obj.type.damage
			if obj.type.damageType ~= obj.target.type.armorType then
				damage = math.floor(damage * .5)
			end
			obj.target.health = obj.target.health - damage
			if not obj.target.damageTimer or obj.target.damageTimer < 0 then
				obj.target.damageTimer = 3
			end
			if obj.type.doWeb then
				obj.target.webTimer = 10
			end
			
			local attackerIsVisible = obj.shooter.isDetected or obj.shooter.onRadar
			if obj.target.health <= 0 then
				obj.target.health = 0
				obj.target.died = true
			elseif attackerIsVisible and (obj.target.type.isAttacker and obj.target.owner == aiPlayer and (not obj.target.ignoreDelay or obj.target.ignoreDelay <= 0)) then
				obj.target.order = ORDER_ATTACK
				obj.target.attackTarget = obj.shooter
				obj.target.ignoreDelay = 5
			end
			
			-- wake up hostiles nearby
			if attackerIsVisible then
				local wakeDistance = 200*200
				if obj.target.owner == aiPlayer and (obj.target.type.typeId == TYPE_MATERNAL or obj.target.type.typeId == TYPE_HARVESTER) then
					wakeDistance = 2000*2000
				end
				for index, wake in pairs(gameObjects) do
					if wake.owner == obj.target.owner and not wake.died and wake.type.isAttacker and (wake.order == ORDER_NONE or wake.owner == aiPlayer) then
						dx,dy = obj.target.x - wake.x, obj.target.y - wake.y
						if (dx*dx + dy*dy) < wakeDistance then
							wake.attackTarget = obj.shooter
							wake.order = ORDER_ATTACK
						end
					end
				end
			elseif obj.target.owner == aiPlayer and obj.shooter.isCloaked and aiTimers.detectionTimer <= 0 then
				-- create an attention flag!
				table.insert(aiDetectionFlags, { x = obj.x, y = obj.y })
				aiTimers.detectionTimer = 10
			end
			
			if obj.target.owner == localPlayer and gameInfo.warningDelay <= 0 then
				gameInfo.warningDelay = 7
				
				if obj.target.type.typeId == TYPE_MATERNAL then
					daisy.playSound("UnderAttack.ogg")
				end
			end
			
			if obj.type.impactSound then
				playPositionSound(obj.x, obj.y, obj.type.impactSound, obj.type.numImpactSounds)
			end
			if obj.type.impactParticle then
				createParticle(obj.x, obj.y, obj.type.impactParticle)
			end
		
			obj.died = true
		end
	
	end
end

local function updateParticle(obj, time)

	if obj.sprite then
	
		if daisy.updateSpriteState(obj.sprite, time) then
			daisy.removeSpriteState(obj.sprite)
			obj.died = true
		end
	
	else
		-- todo?
		obj.died = true
	end
end
objectTypes.spriteParticle.updateFunction = updateParticle

local function lookForAttackTarget(obj)
	-- check for enemies nearby
	local rangeDist = obj.type.attackRange * obj.type.attackRange
	local checkDist = rangeDist
	if not obj.onHold then
		checkDist = checkDist * 4
	end
	local foundWithinAttackRange = false
	local harvester = nil
	local newTargets = {}
	for index, target in pairs(gameObjects) do
		if target.owner > 0 and target.owner ~= obj.owner and (target.onRadar or target.isDetected) and not target.died then
			local dx, dy = target.x - obj.x, target.y - obj.y
			local dist = (dx*dx + dy*dy)
			if dist < checkDist then
				if target.type.isHarvester and not harvester then
					harvester = target
				elseif not target.type.isHarvester then
				
					if not foundWithinAttackRange and dist < rangeDist then
						-- this target is within fire range, so give it higher priority
						newTargets = {target}
						checkDist = rangeDist
						foundWithinAttackRange = true
					else
						table.insert(newTargets, target)
					end
				end
			end
		end
	end
	-- if we didn't find an offensive unit we can attack harvesters
	if #newTargets == 0 and harvester then
		newTargets = {harvester}
	end
	if #newTargets > 0 then
		local newTarget = newTargets[math.random(#newTargets)]
		if obj.order == ORDER_ATTACK_MOVE then
			-- save old stuff
			obj.attackMoveX = obj.tx
			obj.attackMoveY = obj.ty
		else
			obj.attackMoveX, obj.attackMoveY = nil, nil
		end
		obj.order = ORDER_ATTACK
		obj.attackTarget = newTarget
	end
end

local function getTwoAlternativeMissileTargets(obj, previousTargetId)

	local target1 = nil
	local checkDist = (obj.type.attackRange * obj.type.attackRange)
	for index, target in pairs(gameObjects) do
		if target.owner > 0 and target.owner ~= obj.owner and (target.onRadar or target.isDetected) and target.id ~= previousTargetId and not target.died then
			local dx, dy = target.x - obj.x, target.y - obj.y
			local dist = (dx*dx + dy*dy)
			if dist < checkDist then
				if target1 then
					return target1, target
				else
					target1 = target
				end
			end
		end
	end
	return target1, nil
end

local function updateAttacker(obj, time)

	local maxSpeed, accel = 50, 1
	if obj.type and obj.type.maxSpeed then
		maxSpeed = obj.type.maxSpeed
	end
	if obj.type and obj.type.accel then
		accel = obj.type.accel
	end
	
	if obj.type.speedTechnology and playerStats[obj.owner].technologies[obj.type.speedTechnology] then
		maxSpeed = maxSpeed + 30
	end
	
	if obj.attackDelay then
		obj.attackDelay = obj.attackDelay - time
	end
	if obj.warmupDelay then
		obj.warmupDelay = obj.warmupDelay - time
		
		if obj.warmupDelay > 0 then
			maxSpeed = 0
		end
		
		if obj.warmupDelay < 0 and obj.overlaySprite then
			daisy.removeSpriteState(obj.overlaySprite)
			obj.overlaySprite = nil
		end
	end
	if obj.cooldownDelay then
		obj.cooldownDelay = obj.cooldownDelay - time
		
		if obj.cooldownDelay > 0 then
			maxSpeed = 0
		end
	end
	if obj.ignoreDelay then
		obj.ignoreDelay = obj.ignoreDelay - time
	end
	
	if obj.overlaySprite then
		daisy.updateSpriteState(obj.overlaySprite, time)
		if obj.mirrored then
			obj.overlaySpritePos = {x = obj.type.w - obj.type.muzzleOffset.x, y = obj.type.muzzleOffset.y}
		else
			obj.overlaySpritePos = obj.type.muzzleOffset
		end
	end
	
	if obj.order == ORDER_ATTACK then
	
		if not obj.attackTarget or obj.attackTarget.died or not (obj.attackTarget.onRadar or obj.attackTarget.isDetected) then
			if not obj.attackTarget or obj.attackTarget.died or not obj.onRadar then
				obj.attackTarget = nil
				obj.warmupStarted = nil
				
				if obj.attackMoveX and obj.attackMoveY then
					obj.order = ORDER_ATTACK_MOVE
					obj.tx, obj.ty = obj.attackMoveX, obj.attackMoveY
					obj.attackTarget = nil
				else
					obj.order = ORDER_NONE
				end
			else
				obj.order = ORDER_ATTACK_MOVE
			end
			
			lookForAttackTarget(obj)
		else
		
			-- check if we're within range of our target
			local dx, dy = 0, 0
			
			-- muzzle position
			local testY = obj.y + obj.type.h / 2
			local testX = obj.x
			if obj.mirrored then
				testX = obj.x + obj.type.w
			end
			
			local dx, dy = obj.attackTarget.x - testX, obj.attackTarget.y - testY
			
			-- are we to the left or to the right of the target?
			if testY > obj.attackTarget.y and testY < (obj.attackTarget.y + obj.attackTarget.type.h) then
				dy = 0
			elseif testY > obj.attackTarget.y then
				dy = dy + obj.attackTarget.type.h
			end
			-- are we above or below the target?
			if testX > obj.attackTarget.x and testX < (obj.attackTarget.x + obj.attackTarget.type.w) then
				dx = 0
			elseif testX > obj.attackTarget.x then
				dx = dx + obj.attackTarget.type.w
			end
			local dist = (dx*dx + dy*dy)
			
			-- check if we have a valid angle
			local angle = math.atan2(dy, dx)
			if angle < 0 then
				angle = angle + math.pi * 2
			end
			
			
			local validAngle = angle < obj.type.attackAngle or angle > (math.pi * 2 - obj.type.attackAngle) or
				((angle - math.pi) > -obj.type.attackAngle and (angle - math.pi) < obj.type.attackAngle)
			local validRange = obj.type.attackRange * obj.type.attackRange
			-- if the attack already has started we allow slightly farther range
			if obj.warmupStarted == obj.attackTarget.id then
				validRange = validRange + (60*60)
			end
			if obj.type.rangeTechnology and playerStats[obj.owner].technologies[obj.type.rangeTechnology] then
				validRange = validRange * 2.25
			end
			
			if dist <= validRange and validAngle then
			
				if (obj.warmupStarted == obj.attackTarget.id and obj.warmupDelay and obj.warmupDelay <= 0) or obj.type.attackWarmup <= 0 then
					if (not obj.attackDelay or obj.attackDelay <= 0) then
						
						-- fire!
						obj.attackDelay = obj.type.attackRate
						if dx == 0 then
							obj.mirrored = (obj.attackTarget.x + obj.attackTarget.type.w / 2) > (obj.x + obj.type.w / 2)
						else
							obj.mirrored = dx > 0
						end
						if not obj.type.multiMissile then
							createProjectile(obj, obj.attackTarget)
						else
							local shot
							shot = createProjectile(obj, obj.attackTarget)
							local t1, t2 = getTwoAlternativeMissileTargets(obj, obj.attackTarget.id)
							shot = createProjectile(obj, t1 or obj.attackTarget)
							shot.currentAngle = shot.currentAngle + math.pi * .25
							shot = createProjectile(obj, t2 or obj.attackTarget)
							shot.currentAngle = shot.currentAngle - math.pi * .25
						end
						obj.warmupStarted = nil
						
						if obj.type.attackCooldown and obj.type.attackCooldown > 0 then
							obj.cooldownDelay = obj.type.attackCooldown
						end
					end
				elseif not obj.warmupStarted and (not obj.attackDelay or obj.attackDelay <= 0) then
					obj.warmupStarted = obj.attackTarget.id
					obj.warmupDelay = obj.type.attackWarmup
					
					if obj.type.warmupSprite then
						if obj.overlaySprite then
							daisy.removeSpriteState(obj.overlaySprite)
						end
						obj.overlaySprite = daisy.createSpriteState(obj.type.warmupSprite, "IngameGfx.dat")
						obj.overlaySpritePos = obj.type.muzzleOffset
					end
					if obj.type.warmupSound then
						playPositionSound(obj.x, obj.y, obj.type.warmupSound, obj.type.numWarmupSounds)
					end
				end
				defaultObjectSlowdown(obj, time, accel)
			
			else
				if dist > (obj.type.attackRange * obj.type.attackRange) then
					obj.tx, obj.ty = obj.attackTarget.x + obj.attackTarget.type.w / 2, obj.y + dy
				elseif not validAngle and (not obj.attackDelay or obj.attackDelay <= 0) then
					-- select a new position nearby the target
					obj.tx, obj.ty = obj.attackTarget.x + obj.attackTarget.type.w / 2, obj.attackTarget.y + obj.attackTarget.type.h / 2
					angle = -obj.type.attackAngle + math.random() * obj.type.attackAngle * 2
					if obj.x < obj.tx then
						angle = angle + math.pi
						obj.tx = obj.tx - obj.attackTarget.type.w / 2
					else
						obj.tx = obj.tx + obj.attackTarget.type.w / 2
					end
					dist = math.random(20, obj.type.attackRange)
					obj.tx = obj.tx + math.cos(angle) * dist
					obj.ty = obj.ty + math.sin(angle) * dist
					
					-- check again later
					obj.attackDelay = 1.0
				end
				
				obj.warmupStarted = nil
				
				defaultObjectMove(obj, time, maxSpeed, accel)
				--obj.mirrored = (obj.attackTarget.x + obj.attackTarget.type.w / 2) > (obj.x + obj.type.w / 2)
				
				if math.abs(obj.vx) > 10 then
					if obj.x < obj.tx then
						obj.mirrored = true
					elseif obj.x > obj.tx then
						obj.mirrored = false
					end
				end
			end
		
		end
	
	elseif obj.order == ORDER_NONE then
		defaultObjectSlowdown(obj, time, accel)
	else
		if obj.order == ORDER_ATTACK_MOVE then
			if obj.attackTarget and obj.attackTarget.died then
				obj.attackTarget = nil
			elseif obj.attackTarget and (obj.onRadar or obj.isDetected) then
				obj.tx, obj.ty = obj.attackTarget.x, obj.attackTarget.y
			end
		end
		defaultObjectMove(obj, time, maxSpeed, accel)
	end
	
	if obj.order == ORDER_NONE or obj.order == ORDER_ATTACK_MOVE then
		if not obj.scanTimer then
			obj.scanTimer = 1
		else
			obj.scanTimer = obj.scanTimer - time
			if obj.scanTimer <= 0 then
				obj.scanTimer = math.random() + .25
				
				lookForAttackTarget(obj)
			end
		end
	end

end

local function handleAttackUnitTarget(obj, target)

	if obj.owner ~= target.owner and target.owner > 0 and not target.died then
		obj.order = ORDER_ATTACK
		obj.attackTarget = target
		obj.onHold = false
	end

end

local function lookForClosestResource(harvester)

	local closest = nil
	local closeDistance = 2000 * 2000
	local x,y = harvester.x, harvester.y
	if harvester.lastPickupPosition then
		x,y = harvester.lastPickupPosition.x, harvester.lastPickupPosition.y
	end
	for index, obj in pairs(gameObjects) do
		if obj.type.isResource and not obj.died and not obj.carrier then
		
			local dx, dy = obj.x - x, obj.y - y
			if (dx*dx + dy*dy) < closeDistance then
				closeDistance = (dx*dx + dy*dy)
				closest = obj
			end
		end
	end
	
	if closest then
		harvester.harvestTarget = closest
		harvester.order = ORDER_HARVEST
	else
		harvester.order = ORDER_NONE
	end
end

local function updateHarvester(obj, time)

	local maxSpeed, accel = 50, 1
	if obj.type and obj.type.maxSpeed then
		maxSpeed = obj.type.maxSpeed
	end
	if obj.type and obj.type.accel then
		accel = obj.type.accel
	end

	if obj.order == ORDER_NONE then
		defaultObjectSlowdown(obj, time, accel)
		obj.lastPickupPosition = nil
		lookForClosestResource(obj)
	elseif obj.order == ORDER_MOVE then
		defaultObjectMove(obj, time, maxSpeed, accel)
	elseif obj.order == ORDER_HARVEST then
	
		if obj.harvestCarry then
			obj.order = ORDER_RETURN
		else
			if not obj.harvestTarget or obj.harvestTarget.carrier then
				-- look for another resource instead
				obj.harvestTarget = nil
				obj.lastPickupPosition = nil
				lookForClosestResource(obj)
			else
				obj.tx, obj.ty = obj.harvestTarget.x, obj.harvestTarget.y
				defaultObjectMove(obj, time, maxSpeed, accel)
				obj.mirrored = (obj.tx > obj.x)

				if (obj.y + obj.type.h) > (obj.harvestTarget.y) and (obj.y) < (obj.harvestTarget.y + obj.harvestTarget.type.h) and (obj.x + obj.type.w) > (obj.harvestTarget.x) and (obj.x) < (obj.harvestTarget.x + obj.harvestTarget.type.w) then
					obj.harvestCarry = obj.harvestTarget
					obj.harvestTarget.carrier = obj
					obj.lastPickupPosition = { x = obj.x, y = obj.y }
				end
			end
		end
	elseif obj.order == ORDER_RETURN and obj.harvestCarry then
		local command = gameInfo.commandShips[obj.owner]
		obj.tx, obj.ty = command.x + command.type.w / 2, command.y + command.type.h / 2
		obj.mirrored = (obj.tx > obj.x)
		
		defaultObjectMove(obj, time, maxSpeed, accel)
		
		local dx, dy = obj.tx - obj.x, obj.ty - obj.y
		if math.abs(dx) < 20 and math.abs(dy) < 16 then
			playerStats[obj.owner].resources = playerStats[obj.owner].resources + obj.harvestCarry.type.resourceValue
			obj.harvestCarry.died = true
			obj.harvestCarry = nil
			obj.harvestTarget = nil
			lookForClosestResource(obj)
		end
	end
	
	if obj.harvestCarry then
		-- move the resource
		local rx, ry = obj.x - obj.harvestCarry.type.w - 6, obj.y + (obj.type.h - obj.harvestCarry.type.h) / 2
		if obj.mirrored then
			rx = obj.x + obj.type.w + 6
		end
		obj.harvestCarry.x = obj.harvestCarry.x + (rx - obj.harvestCarry.x) * time * 4
		obj.harvestCarry.y = obj.harvestCarry.y + (ry - obj.harvestCarry.y) * time * 4
	end

end
objectTypes.harvester.updateFunction = updateHarvester
objectTypes.harvesterT.updateFunction = updateHarvester

local function updateMaternalVessel(obj, time)

	if obj.order == ORDER_NONE then
		obj.tx, obj.ty = obj.x, obj.y
	end
	
	defaultObjectUpdate(obj, time)
	
	if not obj.shootTime or obj.shootTime <= 0 then
	
		if not obj.attackTarget or obj.attackTarget.died or not obj.attackTarget.onRadar then
			-- look for something new to shoot at
			obj.attackTarget = nil
			lookForAttackTarget(obj)
		end
		
		if obj.attackTarget then
			-- verify range
			local muzzleX, muzzleY = obj.x + obj.type.muzzleOffset.x, obj.y + obj.type.muzzleOffset.y
			if obj.mirrored then
				muzzleX = obj.x + obj.type.w - obj.type.muzzleOffset.x
			end
			local dx,dy = (obj.attackTarget.x + obj.attackTarget.type.w / 2) - muzzleX, (obj.attackTarget.y + obj.attackTarget.type.h / 2) - muzzleY
			if (dx*dx + dy*dy) <= (obj.type.attackRange * obj.type.attackRange) then
				obj.shootTime = 1.0
				
				createProjectile(obj, obj.attackTarget)
			else
				obj.shootTime = .5
				obj.attackTarget = nil
			end
		else
			obj.shootTime = .5
		end
	else
		obj.shootTime = obj.shootTime - time
	end
end
objectTypes.maternal.updateFunction = updateMaternalVessel
objectTypes.maternalT.updateFunction = updateMaternalVessel

local function updateChainExplosion(obj, time)

	obj.explosionDelay = obj.explosionDelay - time
	if obj.explosionDelay <= 0 then
		obj.explosionDelay = .1 + math.random() * .4
		
		local x,y = obj.x + math.random(obj.w), obj.y + math.random(obj.h)
		local particle = obj.particleList[math.random(#obj.particleList)]
		createParticle(x, y, particle.sprite)

		if particle.sound then
			playPositionSound(x, y, particle.sound, particle.numSounds)
		end
		
		obj.explosionCount = obj.explosionCount - 1
	end

	if obj.explosionCount <= 0 then
		obj.died = true
	end

end
objectTypes.chainExplosion.updateFunction = updateChainExplosion

local function handleHarvesterOrders(obj, target)
	
	if target.type.isResource then
		if obj.harvestCarry then
			-- already occupied, return to base
			obj.order = ORDER_RETURN
			obj.onHold = false
		else
			obj.harvestTarget = target
			obj.order = ORDER_HARVEST
			obj.onHold = false
		end
	elseif obj.harvestCarry and target == gameInfo.commandShips[obj.owner] then
		obj.order = ORDER_RETURN
		obj.onHold = false
	end
end
objectTypes.harvester.targetFunction = handleHarvesterOrders
objectTypes.harvesterT.targetFunction = handleHarvesterOrders

local function issueMoveOrder(selection, owner, targetX, targetY)

	local circleSize = 0
	local xOff, yOff = 0, 0
	local targetList = {}
	local selectionList = {}
	for index, obj in pairs(selection) do
	
		if obj.owner == owner and not obj.died then
			table.insert(targetList, { x = targetX + xOff * 32, y = targetY + yOff * 32})
			table.insert(selectionList, obj)
			
			obj.tx = targetX + xOff * 32
			obj.ty = targetY + yOff * 32
			
			if circleSize == 0 and xOff == 0 then
				circleSize, xOff = 1, 1
			elseif xOff == circleSize and yOff < circleSize and yOff > -circleSize then
				yOff = yOff + 1
			elseif xOff > -circleSize and yOff == circleSize then
				xOff = xOff - 1
			elseif xOff == -circleSize and yOff > -circleSize then
				yOff = yOff - 1
			elseif yOff == -circleSize and xOff <= circleSize then
				xOff = xOff + 1
			else
				-- full lap
				circleSize = circleSize + 1
				xOff = circleSize
				yOff = 2 - circleSize
			end
		end
	end
	
	while #selectionList > 0 do
	
		-- calculate minimax
		local highestMini, highestIndex, highestObject = 0, nil, nil
		local objectIndex, miniObject = nil, nil
		for index, target in pairs(targetList) do
		
			local currentMini = 20000*20000
			local currentObjectIndex, currentMiniObject = nil, nil
			for oi, obj in pairs(selectionList) do
			
				local dx, dy = target.x - obj.x, target.y - obj.y
				local dist = dx*dx + dy*dy
				if dist < currentMini then
					currentMini = dist
					currentObjectIndex, currentMiniObject = oi, obj
					if currentMini < highestMini then
						break
					end
				end
			end
			if currentMini > highestMini then
				highestMini = currentMini
				highestIndex = index
				highestObject = target
				objectIndex, miniObject = currentObjectIndex, currentMiniObject
			end
		end
		
		if not miniObject then
			--print("Warning: Formation move failed, " .. highestMini .. ", " .. targetX .. ", " .. targetY)
			break
		end
	
		-- we've found the longest minimized distance, so assign this unit
		--if miniObject.owner == localPlayer then
			miniObject.tx, miniObject.ty = highestObject.x - miniObject.type.w / 2, highestObject.y - miniObject.type.h / 2
			miniObject.order = ORDER_MOVE
			miniObject.onHold = false
			
			if miniObject.tx > miniObject.x then
				miniObject.mirrored = true
			else
				miniObject.mirrored = false
			end
		--end
		table.remove(selectionList, objectIndex)
		table.remove(targetList, highestIndex)
	end
end

local function lookForItemAtPosition(targetX, targetY)
	local found = nil
	local foundDist = 0
	for index, obj in pairs(gameObjects) do
	
		if targetX >= obj.x and targetY >= obj.y and targetX <= (obj.x + obj.type.w) and targetY <= (obj.y + obj.type.h) and not obj.died then
		
			if not found then
				found = obj
				
				local dx, dy = (obj.x + obj.type.w / 2) - targetX, (obj.y + obj.type.h / 2) - targetY
				foundDist = (dx*dx + dy*dy)
			else
				local dx, dy = (obj.x + obj.type.w / 2) - targetX, (obj.y + obj.type.h / 2) - targetY
				local dist = (dx*dx + dy*dy)
				
				if dist < foundDist then
					found = obj
					foundDist = dist
				end
			end
		end
	end
	return found
end

local function playUnitOrderSound(selection, owner)
	if #selection > 0 and owner == localPlayer then
		local first = selection[1]
		if gameInfo.voiceDelay <= 0 and first.type.orderSounds and first.owner == localPlayer then
			local soundName = first.type.soundName
			if first.type.specialSoundCase then
				soundName = soundName .. getNewSoundNumber(first.type.orderSounds) .. ".ogg"
			else
				 soundName = soundName .. "Order" .. getNewSoundNumber(first.type.orderSounds) .. ".ogg"
			end
			daisy.playSound(soundName)
			gameInfo.voiceDelay = 2
		end
	end
end

local function realizeCommandOrderOnTarget(selection, owner, target)

	-- always issue move order first, then adjust depending on what the user clicked on
	issueMoveOrder(selection, owner, target.x, target.y)

	for index,obj in pairs(selection) do
		if not obj.died then
			if obj.owner == owner and obj.type.targetFunction then
				obj.type.targetFunction(obj, target)
			elseif obj.owner == owner and obj.type.isAttacker then
				handleAttackUnitTarget(obj, target)
			end
		end
	end
	if owner == localPlayer then
		createTargetMarker(target)
	end
end

local function realizeCommandOrder(selection, owner, targetX, targetY)
	-- look for target
	local found = lookForItemAtPosition(targetX, targetY)
	
	if found then
		realizeCommandOrderOnTarget(selection, owner, found)
	else
		-- always issue move order first, then adjust depending on what the user clicked on
		issueMoveOrder(selection, owner, targetX, targetY)
		
		if owner == localPlayer then
			createMoveMarker(targetX, targetY)
		end
	end
	
	playUnitOrderSound(selection, owner)
end

local function clearSelectionOfDiedUnits(selection)
	-- clear selection of old items
	local somethingDied = true
	while somethingDied do
		somethingDied = false
		for index,obj in pairs(selection) do
			if obj.died then
				table.remove(selection, index)
				somethingDied = true
				break
			end
		end
		if #selection == 0 then
			return
		end
	end
end

local function issueCommandOrder(selection, owner, targetX, targetY)

	clearSelectionOfDiedUnits(selection)

	if isMultiplayer and #selection > 0 then
	
		local found = lookForItemAtPosition(targetX, targetY)
		
		-- create a packet
		if found then
			local packet = {4, found.id, #selection}
			for index, obj in pairs(selection) do
				table.insert(packet, obj.id)
			end
			multiplayer.addTurnCommand(packet)
		else
			local packet = {1, math.floor(targetX), math.floor(targetY), #selection}
			for index, obj in pairs(selection) do
				table.insert(packet, obj.id)
			end
			multiplayer.addTurnCommand(packet)
		end
	else
		realizeCommandOrder(selection, owner, targetX, targetY)
	end
end

local function realizeStopUnits(selection, owner)
	for index,obj in pairs(selection) do
		if not obj.died and obj.owner == owner then
			obj.order = ORDER_NONE
			obj.onHold = false
		end
	end
end

local function stopUnits(selection, owner)

	clearSelectionOfDiedUnits(selection)

	if isMultiplayer and #selection > 0 then
		-- create a packet
		local packet = {5, #selection}
		for index, obj in pairs(selection) do
			table.insert(packet, obj.id)
		end
		multiplayer.addTurnCommand(packet)
	else
		realizeStopUnits(selection, owner)
	end
end

local function realizeHoldUnits(selection, owner)
	for index,obj in pairs(selection) do
		if not obj.died and obj.owner == owner then
			obj.onHold = true
		end
	end
end

local function holdUnits(selection, owner)

	clearSelectionOfDiedUnits(selection)

	if isMultiplayer and #selection > 0 then
		-- create a packet
		local packet = {6, #selection}
		for index, obj in pairs(selection) do
			table.insert(packet, obj.id)
		end
		multiplayer.addTurnCommand(packet)
	else
		realizeHoldUnits(selection, owner)
	end
end

local function addChatLine(text, owner)

	local r,g,b = 180, 200, 238
	
	if owner > 1 then
		r,g,b = 240, 235, 60
	end

	table.insert(chatLines, 1, { text = text, life = 10, r = r, g = g, b = b })
end

local function sendChatLine()
	if isMultiplayer then
		multiplayer.addTurnCommand({7, gameInfo.chatLine})
	else
		addChatLine("You > " .. gameInfo.chatLine, localPlayer)
	end
end


multiplayer.unpackPacket = function(packetId, values, position)

	--print("Packed ID: " .. packetId)
	if packetId == 1 then
		-- get x, y and unit count
		local packet = {packetId, values[position + 1], values[position + 2], values[position + 3]}
		-- get number of units
		local units = values[position + 3]
		
		--print("px " .. values[position + 1] .. ", py " .. values[position + 2])
		for i=1,units do
			table.insert(packet, values[position + 3 + i])
		end
		
		--print("Count: " .. units)
		return packet, units + 4
	elseif packetId == 4 then
		-- get target id and unit count
		local packet = {packetId, values[position + 1], values[position + 2]}
		-- get number of units
		local units = values[position + 2]
		
		for i=1,units do
			table.insert(packet, values[position + 2 + i])
		end
		
		return packet, units + 3
	elseif packetId == 5 then
		-- get unit count
		local packet = {packetId, values[position + 1]}
		-- get number of units
		local units = values[position + 1]
		
		for i=1,units do
			table.insert(packet, values[position + 1 + i])
		end
		
		return packet, units + 2
	elseif packetId == 6 then
		local packet = {packetId, values[position + 1]}
		local units = values[position + 1]
		for i=1,units do
			table.insert(packet, values[position + 1 + i])
		end
		return packet, units + 2
	elseif packetId == 2 then
		-- get type
		local packet = {packetId, values[position + 1] }
		return packet, 2
	elseif packetId == 3 then
		return {packetId}, 1
	elseif packetId == 100 then
		return {packetId, values[position + 1]}, 2
	elseif packetId == 7 then
		-- get text
		local packet = {packetId, values[position + 1] }
		return packet, 2
	end
end

multiplayer.parseCommand = function(playerNetworkId, command)

	local owner = 2
	if playerNetworkId > 10 then
		owner = 1
	end
	
	--print("parse " .. playerNetworkId .. ", " .. command[1])
	if command[1] == 1 then
		local x, y = command[2], command[3]
		
		local selection = {}
		for i=5,#command do
			local unitId = command[i]
			
			-- locate this unit
			local found = false
			for index,obj in pairs(gameObjects) do
				if obj.id == unitId then
					table.insert(selection, obj)
					found = true
					break
				end
			end
			
			if not found then
				print("Error, not found: " .. unitId)
			end
		end
		
		if #selection > 0 then
			realizeCommandOrder(selection, owner, x, y)
		end
	elseif command[1] == 4 then
		local targetId = command[2]
		
		-- locate this unit
		local found = nil
		for index,obj in pairs(gameObjects) do
			if obj.id == targetId then
				found = obj
				break
			end
		end
		
		if found then
			local selection = {}
			for i=4,#command do
				local unitId = command[i]
				
				-- locate this unit
				for index,obj in pairs(gameObjects) do
					if obj.id == unitId then
						table.insert(selection, obj)
						break
					end
				end
			end
			--print("id " .. command[1] .. ", target " .. targetId .. ", x " .. x .. ", y " .. y)
			if #selection > 0 then
				realizeCommandOrderOnTarget(selection, owner, found)
			end
		end
	elseif command[1] == 5 then
		local selection = {}
		for i=3,#command do
			local unitId = command[i]
			
			-- locate this unit
			for index,obj in pairs(gameObjects) do
				if obj.id == unitId then
					table.insert(selection, obj)
					break
				end
			end
		end
		if #selection > 0 then
			realizeStopUnits(selection, owner)
		end
	elseif command[1] == 6 then
		local selection = {}
		for i=3,#command do
			local unitId = command[i]
			
			-- locate this unit
			for index,obj in pairs(gameObjects) do
				if obj.id == unitId then
					table.insert(selection, obj)
					break
				end
			end
		end
		if #selection > 0 then
			realizeHoldUnits(selection, owner)
		end
	elseif command[1] == 2 then
		realizeBuyShip(owner, command[2])
	elseif command[1] == 3 then
		realizeCancelPurchase(owner)
	elseif command[1] == 100 then
		multiplayer.setLatencyLength(command[2])
	elseif command[1] == 7 then
		local text = command[2]
		local playerName = playerInfo.getPlayer(playerNetworkId).name or "NoName"
		text = playerName .. " > " .. text
		addChatLine(text, owner)
	end

end

local function clampViewPos()
	if viewPos.x < FIELD_LEFT then
		viewPos.x = FIELD_LEFT
	end
	if viewPos.x > (FIELD_RIGHT - 640) then
		viewPos.x = (FIELD_RIGHT - 640)
	end
	if viewPos.y < FIELD_TOP then
		viewPos.y = FIELD_TOP
	end
	if viewPos.y > (FIELD_BOTTOM - 480) then
		viewPos.y = (FIELD_BOTTOM - 480)
	end
end

local function playSelectionSound()
	if #gameInfo.selectedObjects > 0 then
		
		local first = gameInfo.selectedObjects[1]
		if gameInfo.voiceDelay <= 0 and first.type.selectSounds and first.owner == localPlayer then
			local soundName = first.type.soundName
			if first.type.specialSoundCase then
				soundName = soundName .. getNewSoundNumber(first.type.orderSounds) .. ".ogg"
			else
				soundName = soundName .. "Select" .. getNewSoundNumber(first.type.orderSounds) .. ".ogg"
			end
			daisy.playSound(soundName)
			--print(soundName)
			gameInfo.voiceDelay = 2
		end
		
	end
end

local function handleSelection(left, top, right, bottom, allowCtrlSelect)
	local newSelection = {}
			
	if left > right then
		local t = left
		left = right
		right = t
	end
	if top > bottom then
		local t = top
		top = bottom
		bottom = t
	end
	
	
	local friendSelected = false
	local enemySelected = false
	for index, obj in pairs(gameObjects) do
		if obj.type and
			(obj.y + obj.type.h) > top and
			obj.y < bottom and
			(obj.x + obj.type.w) > left and
			obj.x < right then
			
			if obj.owner == localPlayer then
				friendSelected = true
				--if #newSelection == 1 and newSelection[1].owner ~= localPlayer then
				--	newSelection = {} -- remove non-player unit
				--end
				
				table.insert(newSelection, obj)
			--elseif #newSelection == 0 then
			else
				enemySelected = true
				table.insert(newSelection, obj)
			end
		end
	end
	
	-- if the player made a really small square, then attempt to select a single unit (the "best one")
	if #newSelection > 1 and (right - left) <= 5 and (bottom - top) <= 5 then
		
		local cx, cy = (left + (right - left) / 2), (top + (bottom - top) / 2)
		
		-- prioritize small units over large ones
		-- prioritize the unit closest to the click area
		local selected = newSelection[1]
		local dx,dy = (selected.x + selected.type.w / 2) - cx, (selected.y + selected.type.h / 2) - cx
		local selDist = (dx*dx + dy*dy)
		
		for i=2,#newSelection do
			local next = newSelection[i]
			
			local replace = false
			if next.type ~= selected.type and (next.type.w < selected.type.w or next.type.h < selected.type.h) then
				replace = true
			else
				dx,dy = (selected.x + selected.type.w / 2) - cx, (selected.y + selected.type.h / 2) - cx
				
				if selDist > (dx*dx + dy*dy) then
					replace = true
				end
			end
			
			if replace then
				selected = next
				dx,dy = (selected.x + selected.type.w / 2) - cx, (selected.y + selected.type.h / 2) - cx
				selDist = (dx*dx + dy*dy)
			end
		end
		
		newSelection = { selected }
	end
	
	if friendSelected and enemySelected and #newSelection > 1 then
	
		local replacement = {}
		for i=1,#newSelection do
			if newSelection[i].owner == localPlayer then
				table.insert(replacement, newSelection[i])
			end
		end
		newSelection = replacement
	elseif not friendSelected and enemySelected and #newSelection > 1 then
		newSelection = { newSelection[1] }
	end
	
	if #newSelection > 0 then
	
		-- check for double-clicked unit
		local ctrlPressed = daisy.isKeyPressed(17)
		if allowCtrlSelect and #newSelection == 1 and ((mouse.selectionTimer > 0 and newSelection[1] == mouse.selectionObject) or ctrlPressed) then

			-- double-click on a single, friendly unit, select all which are on-screen
			local selectionType = newSelection[1].type
			newSelection = {}
			mouse.selectionTimer = 0
			
			for index, obj in pairs(gameObjects) do
				if obj.type == selectionType and obj.owner == localPlayer and
					(obj.y + obj.type.h) > viewPos.y and
					obj.y < (viewPos.y + 480) and
					(obj.x + obj.type.w) > viewPos.x and
					obj.x < (viewPos.x + 640) then
				
					table.insert(newSelection, obj)
				end
			end
		else
			mouse.selectionTimer = .25
		end
		
		local shiftPressed = daisy.isKeyPressed(16)
		
		-- clear previous selection if not holding shift
		if not shiftPressed or (#(gameInfo.selectedObjects) == 1 and gameInfo.selectedObjects[1].owner ~= localPlayer) then
			for index, obj in pairs(gameInfo.selectedObjects) do
				obj.selected = false
			end
			gameInfo.selectedObjects = {}
		end
		
		-- if holding shift and selecting a single unit which is already selected, it should be de-selected
		-- if holding control, then select only those units of the same type
		if #(gameInfo.selectedObjects) > 1 and #newSelection == 1 and newSelection[1].selected then
			
			--print("asdf")
			if shiftPressed then
			
				for index, obj in pairs(gameInfo.selectedObjects) do
					if obj == newSelection[1] then
						obj.selected = false
						table.remove(gameInfo.selectedObjects, index)
					end
				end
				newSelection = {}
			end
		end
		
		-- replace selection
		if not shiftPressed then
		
			-- check if we are selecting multiple items, and some of them are attackers...
			-- in that case, only select attackers
			if #newSelection > 1 then
				local foundAttacker = false
				for index, obj in pairs(newSelection) do
					if obj.type.isAttacker then
						foundAttacker = true
						break
					end
				end
				if foundAttacker then
					local replacementSelection = {}
					
					for index, obj in pairs(newSelection) do
						if obj.type.isAttacker then
							table.insert(replacementSelection, obj)
						end
					end
					newSelection = replacementSelection
				end
			end
		
			gameInfo.selectedObjects = newSelection
		else
			for index, obj in pairs(newSelection) do
				if not obj.selected then
					table.insert(gameInfo.selectedObjects, obj)
				end
			end
		end
		for index, obj in pairs(newSelection) do
			obj.selected = true
		end
		if #newSelection > 0 and newSelection[1].owner == localPlayer then
			mouse.selectionObject = newSelection[1]
		end
		
		playSelectionSound()
		
	end
end

-- NORMAL GUI
-- NORMAL GUI
-- NORMAL GUI
local function updateNormalGui(time)

	if mouse.selecting then
		if not mouse.left then
			-- make selection
			mouse.selecting = false
			
			local left,top,right,bottom = mouse.selX + viewPos.x, mouse.selY + viewPos.y, mouse.curX + viewPos.x, mouse.curY + viewPos.y
			handleSelection(left, top, right, bottom, true)
		end
	elseif mouse.left and not mouse.prevLeft then
		-- start selection square
		mouse.selecting = true
		mouse.selX, mouse.selY = mouse.curX, mouse.curY
	end
	
	if mouse.right and not mouse.prevRight and gameInfo.selectedObjects then
	
		local targetX, targetY = mouse.curX + viewPos.x, mouse.curY + viewPos.y
		issueCommandOrder(gameInfo.selectedObjects, localPlayer, targetX, targetY)
		
	end
	
	-- scroll screen
	if daisy.isKeyPressed(37) or mouse.curX <= 0 then
		viewPos.x = viewPos.x - 400 * time
	end
	if daisy.isKeyPressed(39) or mouse.curX >= 639 then
		viewPos.x = viewPos.x + 400 * time
	end
	if daisy.isKeyPressed(38) or mouse.curY <= 0 then
		viewPos.y = viewPos.y - 400 * time
	end
	if daisy.isKeyPressed(40) or mouse.curY >= 479 then
		viewPos.y = viewPos.y + 400 * time
	end
	clampViewPos()

end

-- MINIMAP GUI
-- MINIMAP GUI
-- MINIMAP GUI
-- MINIMAP GUI
local function updateMinimapGui(time)


	if not mouse.prevRight and mouse.right and #(gameInfo.selectedObjects) > 0 then
		local tx, ty = mouse.curX * (FIELD_WIDTH / 640) + FIELD_LEFT, mouse.curY * (FIELD_HEIGHT / 480) + FIELD_TOP
		issueCommandOrder(gameInfo.selectedObjects, localPlayer, tx, ty)
	end
	
	if not mouse.prevLeft and mouse.left then
		if mouse.selectionTimer > 0 then
			viewPos.x, viewPos.y = mouse.curX * (FIELD_WIDTH / 640) + FIELD_LEFT - 320, mouse.curY * (FIELD_HEIGHT / 480) + FIELD_TOP - 240
			gameInfo.normalMode = true
		else
			mouse.selectionTimer = .25
		end
	end
	if not mouse.prevLeft and mouse.left then
		mouse.selecting = true
		mouse.selX, mouse.selY = mouse.curX, mouse.curY
	elseif mouse.prevLeft and not mouse.left and mouse.selecting then
	
		if mouse.selecting then
			local left, top, right, bottom = mouse.selX * (FIELD_WIDTH / 640) + FIELD_LEFT, mouse.selY * (FIELD_HEIGHT / 480) + FIELD_TOP, mouse.curX * (FIELD_WIDTH / 640) + FIELD_LEFT, mouse.curY * (FIELD_HEIGHT / 480) + FIELD_TOP
			handleSelection(left, top, right, bottom, false)
		end
	
		mouse.selecting = false
	end
end

-- BUILD GUI
local function updateBuildGui(time)

	gameInfo.highlightBuildOption = nil
	for index, conGui in pairs(constructionTypeGui) do
		if mouse.curX >= (conGui.x - 22) and mouse.curX <= (conGui.x + 22) and mouse.curY >= (conGui.y - 22) and mouse.curY <= (conGui.y + 22) then
			gameInfo.highlightBuildOption = conGui
			break
		end
	end
	
	if mouse.left and not mouse.prevLeft then
		if gameInfo.highlightBuildOption then
			buyShip(localPlayer, gameInfo.highlightBuildOption.type.buildId)
		elseif mouse.curX >= (320 - 22) and mouse.curX <= (320 + 22) and mouse.curY >= 440 then
			gameInfo.buildMode = false
		end
	end
end

local function updateObjectList(objectList, time)
	local somethingDied = false
	for index, obj in pairs(objectList) do
		if obj.type and obj.type.updateFunction then
			obj.type.updateFunction(obj, time)
		elseif obj.type and obj.type.isAttacker then
			updateAttacker(obj, time)
		elseif obj.type and obj.type.isProjectile then
			updateProjectile(obj, time)
		else
			defaultObjectUpdate(obj, time)
		end
		if obj.died then
			somethingDied = true
		elseif obj.type.regenerates and obj.health < obj.type.health then
			obj.health = obj.health + time * REGENERATE_SPEED
			if obj.health > obj.type.health then
				obj.health = obj.type.health
			end
		end
		if obj.damageTimer then
			obj.damageTimer = obj.damageTimer - time
		end
		if obj.webTimer then
			obj.webTimer = obj.webTimer - time
		end
	end
	
	while somethingDied do
		somethingDied = false
		for index, obj in pairs(objectList) do
			if obj.died then
				if obj.type.wreckSprite then
					createStaticSprite(obj.x, obj.y, obj.type.wreckSprite, obj.mirrored)
				end
				if obj.type.deathParticle then
					createParticle(obj.x + obj.type.w / 2, obj.y + obj.type.h / 2, obj.type.deathParticle)
				end
				if obj.type.deathSound then
					playPositionSound(obj.x, obj.y, obj.type.deathSound, obj.type.numDeathSounds)
				end
				if obj.type.deathChain then
					createExplosionChain(obj.x, obj.y, obj.type.w, obj.type.h, obj.type.deathChain, obj.type.deathChainCount)
				end
				if obj.type.animatedSprite then
					daisy.removeSpriteState(obj.sprite)
				end
				
				table.remove(objectList, index)
				somethingDied = true
				break
			end
		end
	end
end

-- AI AI AI AI
-- AI AI AI AI
-- AI AI AI AI
-- AI AI AI AI
local function updateAi(time)

	aiTimers.detectionTimer = aiTimers.detectionTimer - time
	if #aiDetectionFlags > 0 then
	
		-- send up to 3 idle inspectors to the detection flag
		local found = false
		local foundList = {}
		for index,obj in pairs(gameObjects) do
			if obj.owner == aiPlayer and obj.type.typeId == TYPE_INSPECTOR and obj.order == ORDER_NONE then
				found = true
				table.insert(foundList, obj)
				
				if #foundList == 3 then
					break
				end
			end
		end
		
		if found then
			issueMoveOrder(foundList, aiPlayer, aiDetectionFlags[1].x, aiDetectionFlags[1].y)
			table.remove(aiDetectionFlags, 1)
		end
		
		if not found and (#aiBuildQueue == 0 or aiBuildQueue[1].buildId ~= TYPE_INSPECTOR) then
			table.insert(aiBuildQueue, 1, constructionTypes.inspector)
		end
	end

	aiTimers.nextUnitCommand = aiTimers.nextUnitCommand - time
	if aiTimers.nextUnitCommand <= 0 then
		aiTimers.nextUnitCommand = aiTimers.nextUnitCommand + math.random() * 3 + .25
		
		local harassForce = {}
		local defenseForce = {}
		local left, top, right, bottom = FIELD_RIGHT, FIELD_BOTTOM, FIELD_LEFT, FIELD_TOP
		
		-- verify orders of all units
		for index,obj in pairs(gameObjects) do
			if obj.owner == aiPlayer then
			
				if obj.type.isHarvester then
				
					if obj.order ~= ORDER_HARVEST and obj.order ~= ORDER_RETURN then
						obj.order = ORDER_HARVEST
					end
					
					if obj.x < left then
						left = obj.x
					end
					if obj.x > right then
						right = obj.x
					end
					if obj.y < top then
						top = obj.y
					end
					if obj.y > bottom then
						bottom = obj.y
					end
				
				elseif obj.type.isAttacker and (obj.order == ORDER_NONE or obj.order == ORDER_MOVE) then
				
					if obj.type.typeId == TYPE_BLASTER or obj.type.typeId == TYPE_CLOAKING then
						table.insert(harassForce, obj)
					elseif obj.type.typeId == TYPE_CATCHER or obj.type.typeId == TYPE_FRIGATE then
						table.insert(defenseForce, obj)
					elseif obj.type.typeId == TYPE_PLASMAGUN then
						for i2,o2 in pairs(gameObjects) do
							if o2.owner == localPlayer and not o2.died and o2.type.typeId == TYPE_FRIGATE then
								obj.order = ORDER_ATTACK_MOVE
								obj.attackTarget = o2
								break
							end
						end
						if not obj.attackTarget then
							obj.order = ORDER_ATTACK_MOVE
							obj.attackTarget = gameInfo.commandShips[localPlayer]
						end
					end
				
				end
			
			end
		end
		
		if #harassForce >= 5 then
			-- locate the hostile harvester which is closest to the mothership
			local closestDistance = 4000*4000
			local closestHarvester = gameInfo.commandShips[localPlayer]
			
			--print("attacking!")
			
			for index, obj in pairs(gameObjects) do
				if obj.owner == localPlayer and (obj.type.isHarvester or obj.type.typeId == TYPE_PLASMAGUN) and not obj.died then
					local dx, dy = obj.x - gameInfo.commandShips[2].x, obj.y - gameInfo.commandShips[2].y
					if (dx*dx + dy*dy) < closestDistance then
						closestDistance = (dx*dx + dy*dy)
						closestHarvester = obj
					end
				end
			end
			
			if closestHarvester then
				for i=1,#harassForce do
					harassForce[i].order = ORDER_ATTACK_MOVE
					harassForce[i].attackTarget = closestHarvester
				end
			end
		elseif left < right and top < bottom then
			issueMoveOrder(harassForce, aiPlayer, math.random(left, right), math.random(top, bottom))
		end
		if #defenseForce >= 10 then
			-- locate the hostile bombers which is closest to the mothership
			local closestDistance = 4000*4000
			local closestBomber = nil
			
			for index, obj in pairs(gameObjects) do
				if obj.owner == localPlayer and (obj.type.typeId == TYPE_BLASTER or obj.type.typeId == TYPE_CLOAKING) and not obj.died then
					local dx, dy = obj.x - gameInfo.commandShips[2].x, obj.y - gameInfo.commandShips[2].y
					if (dx*dx + dy*dy) < closestDistance then
						closestDistance = (dx*dx + dy*dy)
						closestBomber = obj
					end
				end
			end
			
			if closestBomber then
				for i=1,10 do
					defenseForce[i].order = ORDER_ATTACK_MOVE
					defenseForce[i].attackTarget = closestBomber
				end
			end
		elseif left < right and top < bottom then
			issueMoveOrder(defenseForce, aiPlayer, math.random(left, right), math.random(top, bottom))
		end
	end
	
	if gameInfo.commandShips[aiPlayer].order == ORDER_NONE and #aiAsteroidGroup > 2 then
		-- calculate a reasonable position for the command ship
		
		shellsort(aiAsteroidGroup,
			function(g1, g2)
				local dx1,dy1 = g1.x - gameInfo.commandShips[aiPlayer].x, g1.y - gameInfo.commandShips[aiPlayer].y
				local dx2,dy2 = g2.x - gameInfo.commandShips[aiPlayer].x, g2.y - gameInfo.commandShips[aiPlayer].y
				
				return (dx1*dx1 + dy1*dy1) < (dx2*dx2 + dy2*dy2)
			end,
			#aiAsteroidGroup)
			
		-- move to the center position of the 3 closest
		gameInfo.commandShips[aiPlayer].order = ORDER_MOVE
		gameInfo.commandShips[aiPlayer].tx = (aiAsteroidGroup[1].x + aiAsteroidGroup[2].x + aiAsteroidGroup[3].x) / 3
		gameInfo.commandShips[aiPlayer].ty = (aiAsteroidGroup[1].y + aiAsteroidGroup[2].y + aiAsteroidGroup[3].y) / 3
		
		-- remove the closest one, so that it's ignored when the command ship reaches the destination
		table.remove(aiAsteroidGroup, 1)
		
		--print("ai moving " .. #aiAsteroidGroup)
	end
	
	aiTimers.nextBuildCommand = aiTimers.nextBuildCommand - time
	if aiTimers.nextBuildCommand <= 0 and playerStats[aiPlayer].buildTime <= 0 then
		aiTimers.nextBuildCommand = 3
		
		if #aiBuildQueue > 0 then
		
			if playerStats[aiPlayer].resources >= aiBuildQueue[1].cost then
			
				if buyShip(aiPlayer, aiBuildQueue[1].buildId) then
					table.remove(aiBuildQueue, 1)
				end
			
			end
		
		elseif playerStats[aiPlayer].resources >= 100 then
		
			if playerStats[aiPlayer].researchTime <= 0 then
				-- attempt to buy all research options (will fail if not enough resources or already researched)
				buyShip(aiPlayer, REST_FIGHTER)
				buyShip(aiPlayer, REST_FRIGATE)
				buyShip(aiPlayer, REST_HITECH)
				buyShip(aiPlayer, REST_CLOAKING)
				buyShip(aiPlayer, REST_SPEED)
				buyShip(aiPlayer, REST_BUILD)
				buyShip(aiPlayer, REST_SUPER)
			end
		
			-- calculate some stuff
			local numHarvesters, numResources = 0, 0
			local enemyBombers, myFighters, enemyFighters = 0, 0, 0
			local enemyHasAssault = false
			for index,obj in pairs(gameObjects) do
				if obj.owner == localPlayer then
					if obj.type.typeId == TYPE_BLASTER then
						enemyBombers = enemyBombers + 1
					elseif obj.type.typeId == TYPE_CATCHER then
						enemyFighters = enemyFighters + 1
					elseif obj.type.typeId == 4 then
						enemyHasAssault = true
					end
				elseif obj.owner == aiPlayer then
					if obj.type.isHarvester then
						numHarvesters = numHarvesters + 1
					elseif obj.type.typeId == 1 then
						myFighters = myFighters + 1
					end
				else
					if obj.type.isResource then
						numResources = numResources + obj.type.resourceValue
					end
				end
			end
			
			if (numHarvesters * 1600) < numResources then
				buyShip(aiPlayer, TYPE_HARVESTER)
			elseif myFighters < (enemyBombers * 2) or myFighters < (enemyFighters * 2) then
				buyShip(aiPlayer, TYPE_CATCHER)
			else
				if (playerStats[aiPlayer].resources >= 500 or enemyHasAssault) and playerStats[aiPlayer].technologies[REST_HITECH] and playerStats[aiPlayer].technologies[REST_FRIGATE] then
					buyShip(aiPlayer, TYPE_PLASMAGUN)
				elseif (playerStats[2].resources >= 400 and enemyFighters >= (myFighters + 5)) and playerStats[aiPlayer].technologies[REST_FRIGATE] then
					buyShip(aiPlayer, TYPE_FRIGATE)
				else
					if playerStats[aiPlayer].technologies[REST_CLOAKING] and enemyFighters >= myFighters then
						buyShip(aiPlayer, TYPE_CLOAKING)
					else
						buyShip(aiPlayer, TYPE_BLASTER)
					end
				end
			end
		end
	end
end

local function createAiBuildQueue()

	local strategy = mapSeed % 5
	--print("strategy: " .. strategy)
	
	if strategy == 0 then
		-- balanced strategy
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.fighterResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.inspector)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.hitechResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.frigateResearch)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.frigate)
		table.insert(aiBuildQueue, constructionTypes.frigate)
		table.insert(aiBuildQueue, constructionTypes.plasmagun)
	elseif strategy == 1 then
		-- macro strategy
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.fighterResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.frigateResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.frigate)
		table.insert(aiBuildQueue, constructionTypes.hitechResearch)
		table.insert(aiBuildQueue, constructionTypes.frigate)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.plasmagun)
		table.insert(aiBuildQueue, constructionTypes.buildResearch)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.frigate)
		table.insert(aiBuildQueue, constructionTypes.plasmagun)
		table.insert(aiBuildQueue, constructionTypes.plasmagun)
		table.insert(aiBuildQueue, constructionTypes.frigate)
		table.insert(aiBuildQueue, constructionTypes.speedResearch)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
	elseif strategy == 2 then
		-- blaster rush strategy
		table.insert(aiBuildQueue, constructionTypes.fighterResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.inspector)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.hitechResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.blaster)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.frigateResearch)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
	elseif strategy == 3 then
		-- catcher dominance strategy
		table.insert(aiBuildQueue, constructionTypes.fighterResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.inspector)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.speedResearch)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.hitechResearch)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
	else
		-- cloak rush strategy
		table.insert(aiBuildQueue, constructionTypes.fighterResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.inspector)
		table.insert(aiBuildQueue, constructionTypes.hitechResearch)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.cloakingResearch)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.cloaking)
		table.insert(aiBuildQueue, constructionTypes.cloaking)
		table.insert(aiBuildQueue, constructionTypes.cloaking)
		table.insert(aiBuildQueue, constructionTypes.cloaking)
		table.insert(aiBuildQueue, constructionTypes.cloaking)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.harvester)
		table.insert(aiBuildQueue, constructionTypes.speedResearch)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
		table.insert(aiBuildQueue, constructionTypes.catcher)
	end

end

local function runFrameFunction(time)
	
	if aiPlayer > 0 then
		updateAi(time)
	end
	
	for i=1,2 do
		if not gameInfo.commandShips[i].died and #(playerStats[i].buildQueue) > 0 then
			
			if playerStats[i].buildTime <= 0 then
				playerStats[i].buildTime = playerStats[i].buildQueue[1].buildTime
			else
				local decrease = time
				if playerStats[i].technologies[REST_BUILD] then
					decrease = decrease * 1.5
				end
				playerStats[i].buildTime = playerStats[i].buildTime - decrease
				if playerStats[i].buildTime <= 0 then
				
					-- spawn unit
					local cs = gameInfo.commandShips[i]
					local newType = playerStats[i].buildQueue[1].result[i]
					local x,y = cs.x + (cs.type.w - newType.w) / 2, cs.y + (cs.type.h - newType.h) / 2
					local obj = createShip(x, y, i, newType)
					
					issueMoveOrder({obj}, i, x + 200, y + 50)
					
					if i == localPlayer then
						daisy.playSound("ConstructionComplete.ogg")
					end
					table.remove(playerStats[i].buildQueue, 1)
				end
			end
		end
		if not gameInfo.commandShips[i].died and playerStats[i].researchItem then
			
			if playerStats[i].researchTime <= 0 then
				playerStats[i].researchTime = playerStats[i].researchItem.buildTime
			else
				playerStats[i].researchTime = playerStats[i].researchTime - time
				if playerStats[i].researchTime <= 0 then
				
					local techId = playerStats[i].researchItem.buildId
					playerStats[i].technologies[techId] = true
					
					if i == localPlayer then
						daisy.playSound("NewTech.ogg")
					end
					playerStats[i].researchItem = nil
				end
			end
		end
	end

	updateObjectList(gameObjects, time)
	updateObjectList(particleObjects, time)
	updateObjectList(backgroundObjects, time)
	
	-- update "line of sight"
	gameInfo.nextSightUpdate = gameInfo.nextSightUpdate - time
	if gameInfo.nextSightUpdate <= 0 then
		gameInfo.nextSightUpdate = gameInfo.nextSightUpdate + .5
		
		for i=1,#gameObjects do
			if gameObjects[i].owner <= 0 then
				gameObjects[i].isDetected = true
				gameObjects[i].onRadar = true
			else
				gameObjects[i].isDetected = false
				gameObjects[i].onRadar = false
				gameObjects[i].isCloaked = gameObjects[i].type.isCloaked or (gameObjects[i].type.cloakTechnology and playerStats[gameObjects[i].owner].technologies[gameObjects[i].type.cloakTechnology])
			end
		end
		for i=1,#gameObjects do
			local obj = gameObjects[i]
			
			if obj.owner > 0 then
				for j=1,#gameObjects do
				
					local obj2 = gameObjects[j]
				
					if obj2.owner > 0 and obj2.owner ~= obj.owner then
						
						local dx,dy = (obj2.x + obj2.type.w / 2) - (obj.x + obj.type.w / 2), (obj2.y + obj2.type.h / 2) - (obj.y + obj.type.h / 2)
						local dist = (dx*dx + dy*dy)
						
						if dist <= obj.type.radarRange then
							if dist <= obj.type.sightRange and (not obj2.isCloaked or obj.type.isDetector) then
								obj2.onRadar = true
								obj2.isDetected = true
							elseif not obj2.isCloaked then
								obj2.onRadar = true
							end
						end
						if dist <= obj2.type.radarRange then
							if dist <= obj2.type.sightRange and (not obj.isCloaked or obj2.type.isDetector) then
								obj.onRadar = true
								obj.isDetected = true
							elseif not obj.isCloaked then
								obj.onRadar = true
							end
						end
					end
				end
			end
		end
	end
	
	daisy.updateSpriteState(unknownShipSprite, time)
	daisy.updateSpriteState(webSprite, time)
end

local function centerOnSelection()
	-- center on selection
	local x, y, count = 0, 0, 0
	
	for i=1,#(gameInfo.selectedObjects) do
		count = count + 1
		x = x + gameInfo.selectedObjects[i].x + gameInfo.selectedObjects[i].type.w / 2
		y = y + gameInfo.selectedObjects[i].y + gameInfo.selectedObjects[i].type.h / 2
	end
	
	if count > 0 then
		viewPos.x, viewPos.y = (x / count) - 320, (y / count) - 240
		clampViewPos()
	end
end

local function frameIndependentUpdate(time)
	if time > .06 then
		time = .06
	end

	-- Update mouse info
	mouse.prevLeft = mouse.left
	mouse.prevRight = mouse.right
	mouse.left = daisy.isMouseButtonPressed(0)
	mouse.right = daisy.isMouseButtonPressed(1)
	mouse.curX, mouse.curY = daisy.getMousePosition()
	-- stupid hack to prevent start-up scrolling
	if mouse.curX == 0 and mouse.curY == 0 and not mouse.activated then
		mouse.curX, mouse.curY = 1, 1
	else
		mouse.activated = true
	end
	mouse.selectionTimer = mouse.selectionTimer - time
	
	gameInfo.voiceDelay = gameInfo.voiceDelay - time
	gameInfo.warningDelay = gameInfo.warningDelay - time
	gameInfo.commandGroupTimer = gameInfo.commandGroupTimer - time
	
	if gameInfo.buildMode then
		updateBuildGui(time)
	else
		if gameInfo.normalMode then
			updateNormalGui(time)
		else
			updateMinimapGui(time)
		end
	end
	
	gameInfo.infoTimer = gameInfo.infoTimer - time
	
	-- update chat lines
	for index, line in pairs(chatLines) do
		line.life = line.life - time
		if line.life <= 0 then
			table.remove(chatLines, index)
			break
		end
	end

end

local keepTime = 0
local function gameRun(time)

	frameIndependentUpdate(time)
	
	if isMultiplayer then
		multiplayer.frameUpdate(time, runFrameFunction)
		
		if gameInfo.commandShips[localPlayer].died or gameInfo.commandShips[externalPlayer].died then
			multiplayer.saveReplayFile()
		end
		
	else
		if time > .06 then
			time = .06
		end
		time = time + keepTime
		while time > 0.00625 do
			runFrameFunction(0.00625)
			time = time - 0.00625
		end
		keepTime = time
	end
	
	--[[
	for i=1,255 do
		if daisy.isKeyPressed(i) then
			print("key " .. i)
		end
	end
	]]
	
	if musicInfo.introTimer > 0 then
		musicInfo.introTimer = musicInfo.introTimer - time * .15
		if musicInfo.introTimer <= 0 then
			daisy.stopMusic("AdagioForStringsShort.ogg")
		else
			daisy.updateMusic("AdagioForStringsShort.ogg", musicInfo.introTimer)
		end
	end
	if _settings.enableMusic and (not musicInfo.currentMusic or not daisy.updateMusic(musicInfo.currentMusic, .6)) then
	
		-- use the length of the object list since it's fairly randomized to select music
		local len = #gameObjects
		if math.cos(len) > 0 then
			musicInfo.currentMusic = "ambient1.mp3"
		else
			musicInfo.currentMusic = "ambient2.mp3"
		end
		--print("len " .. len .. ", " .. musicInfo.currentMusic)
		daisy.playMusic(musicInfo.currentMusic, 0.6, false)
	end
	
end

-- RENDERING --- --- --- --- --- --- --- --- --- --- ---
--    RENDERING
--       RENDERING
--          RENDERING
--             RENDERING
--                RENDERING
-- RENDERING
--    RENDERING
--       RENDERING
--          RENDERING
--             RENDERING
--                RENDERING --- --- --- --- --- --- --- --- --- --- ---

local function defaultObjectRender(obj)

	local screenX, screenY = math.floor((obj.x - viewPos.x) / 2) * 2, math.floor((obj.y - viewPos.y) / 2) * 2
	
	local a,r,g,b = 255,255,255,255
	if obj.color then
		r,g,b = obj.color.r, obj.color.g, obj.color.b
	end
	if obj.isCloaked then
		a = 160
	end
	
	if obj.sprite then
		if obj.mirrored and obj.type then
			if obj.type.animatedSprite then
				local w,h = daisy.getSpriteStateSize(obj.sprite)
				daisy.renderSpriteStateFreeShape(obj.sprite,
					screenX + w, screenY,
					screenX, screenY,
					screenX + w, screenY + h,
					screenX, screenY + h, a, r, g, b)
			else
				daisy.renderSpriteStateFreeShape(obj.sprite,
					screenX + obj.type.w, screenY,
					screenX, screenY,
					screenX + obj.type.w, screenY + obj.type.h,
					screenX, screenY + obj.type.h, a, r, g, b)
			end
		else
			daisy.renderSpriteStateFixed(obj.sprite, screenX, screenY, a, r, g, b)
		end
		
		if obj.overlaySprite then
			daisy.renderSpriteStateFixed(obj.overlaySprite, screenX + obj.overlaySpritePos.x, screenY + obj.overlaySpritePos.y)
		end
		
		if obj.webTimer and obj.webTimer > 0 then
			daisy.renderSpriteStateFixed(webSprite, screenX + obj.type.w / 2, screenY + obj.type.h / 2)
		end
	end
	
	if obj.selected then
		local healthWidth, healthOff = 16, 16
		if obj.type then
			healthWidth, healthOff = obj.type.w, obj.type.h
		end
		
		local hx1, hx2, hy = math.floor((screenX + 2) / 2) * 2, math.floor((screenX + healthWidth - 2) / 2) * 2, math.floor((screenY + healthOff + 4) / 2) * 2
		
		if obj.type.health and obj.health < obj.type.health then
			local progress = obj.health / obj.type.health
			local hx3 = math.floor((hx1 + (hx2 - hx1) * progress) / 2) * 2
			
			if progress < .3 then
				daisy.renderRectangle(hx1, hy, hx2 - hx1, 2, 255, 128, 0, 0)
				daisy.renderRectangle(hx1, hy, hx3 - hx1, 2, 255, 255, 0, 0)
			elseif progress < .6 then
				daisy.renderRectangle(hx1, hy, hx2 - hx1, 2, 255, 128, 128, 0)
				daisy.renderRectangle(hx1, hy, hx3 - hx1, 2, 255, 255, 255, 0)
			else
				daisy.renderRectangle(hx1, hy, hx2 - hx1, 2, 255, 0, 128, 0)
				daisy.renderRectangle(hx1, hy, hx3 - hx1, 2, 255, 0, 255, 0)
			end			
			
		else
			daisy.renderRectangle(hx1, hy, hx2 - hx1, 2, 255, 0, 255, 0)
		end
		
	end	
	
	--daisy.renderText("" .. obj.id, screenX, screenY)
end

local function hiddenObjectRender(obj)
	local screenX, screenY = math.floor((obj.x - viewPos.x) / 2) * 2, math.floor((obj.y - viewPos.y) / 2) * 2
	
	daisy.renderSpriteStateFixed(unknownShipSprite, screenX + obj.type.w / 2, screenY + obj.type.h / 2, 255, 255, 0, 0)
end

local function defaultObjectRenderMini(obj)

	local screenX, screenY = math.floor(((obj.x - FIELD_LEFT) * (640 / FIELD_WIDTH)) / 2) * 2, math.floor(((obj.y - FIELD_TOP)  * (480 / FIELD_HEIGHT)) / 2) * 2

	local r,g,b = 255, 255, 255
	if obj.selected then
		r,g,b = 255, 255, 255
	elseif obj.owner == localPlayer then
		r,g,b = 0, 255, 0
	elseif obj.owner == externalPlayer then
		r,g,b = 255, 0, 0
	else
		r,g,b = 160, 100, 40
	end
	
	if obj.damageTimer and obj.damageTimer > 0 then
		if math.cos(obj.damageTimer * math.pi * 6) > 0 then
			-- don't render
			return
		end
	end

	if obj.type.miniSprite and (obj.isDetected or obj.owner == localPlayer) then
		daisy.renderSpriteStateFixed(obj.type.miniSprite, screenX, screenY, 255, r, g, b)
	elseif (obj.isDetected or obj.owner == localPlayer) then
		daisy.renderRectangle(screenX, screenY, 4, 4, 255, r, g, b)
	else
		daisy.renderRectangle(screenX, screenY, 4, 4, 255, r / 2, g / 2, b / 2)
	end

end

local function gameRender()


	if gameInfo.normalMode then
	
		do
			-- calculate background position
			--local screenSpanX, screenSpanY = 1024 - 640, 1024 - 480
			--local worldSpanX, worldSpanY = FIELD_WIDTH - 640, FIELD_HEIGHT - 480
			--local x = math.floor((((viewPos.x - FIELD_LEFT) / worldSpanX) * screenSpanX) / 2) * 2
			--local y = math.floor((((viewPos.y - FIELD_TOP) / worldSpanY) * screenSpanY) / 2) * 2
			
			daisy.renderSpriteStateFixed(bgSprite, bgPosition.x, bgPosition.y)
		end
	
		-- render stars
		for i=1,#stars do
			local star = stars[i]
			--local color = 96 + star.z * 64
			
			local x,y = star.x - viewPos.x, star.y - viewPos.y
			if star.z == 0 then
				x,y = x / 8, y / 8
			elseif star.z == 1 then
				x,y = x / 4, y / 4
			else
				x,y = x / 2, y / 2
			end
			x = math.floor(x / 2) * 2
			y = math.floor(y / 2) * 2
			
			--if x > 0 and x < 640 and y > 0 and y < 480 then
			--	daisy.renderRectangle(x, y, 2, 2, 255, color, color, color)
			--end
			daisy.renderSpriteStateFixed(star.sprite, x, y)
		end
		
		--if daisy.isKeyPressed(68) then
		--	daisy.renderRectangle(0, 0, 640, 480, 64, 0, 0, 0)
		--end

		for index, obj in pairs(backgroundObjects) do
			if obj.type and obj.type.renderFunction then
				obj.type.renderFunction(obj)
			else
				defaultObjectRender(obj)
			end
		end
		
		for index, obj in pairs(gameObjects) do
			if obj.onRadar or (obj.owner == localPlayer) then
				if obj.type and obj.type.renderFunction then
					obj.type.renderFunction(obj)
				else
					if not obj.isDetected and obj.owner ~= localPlayer then
						hiddenObjectRender(obj)
					else
						defaultObjectRender(obj)
					end
				end
			end
		end
		
		for index, obj in pairs(particleObjects) do
			if obj.type and obj.type.renderFunction then
				obj.type.renderFunction(obj)
			else
				defaultObjectRender(obj)
			end
		end
	else
	
		daisy.renderSpriteStateFixed(bgSprite, bgPosition.x, bgPosition.y)

		--[[
		-- render stars
		for i=1,#stars do
			local star = stars[i]
			local color = 96 + star.z * 64
			
			-- the full game field is 4000x2000
			local x,y = (star.x - FIELD_LEFT * 2) * (640 / (FIELD_WIDTH * 2)), (star.y - FIELD_TOP * 2) * (480 / (FIELD_HEIGHT * 2))
			x = math.floor(x / 2) * 2
			y = math.floor(y / 2) * 2
			
			daisy.renderRectangle(x, y, 2, 2, 255, color, color, color)
		end
		]]
		
		for index, obj in pairs(gameObjects) do
			if obj.onRadar or (obj.owner == localPlayer) then
				if obj.type and obj.type.renderMiniFunction then
					obj.type.renderMiniFunction(obj)
				else
					defaultObjectRenderMini(obj)
				end
			end
		end
	end

	if mouse.selecting then
		local startX, startY = math.floor(mouse.selX / 2) * 2, math.floor(mouse.selY / 2) * 2
		local endX, endY = math.floor(mouse.curX / 2) * 2, math.floor(mouse.curY / 2) * 2
		
		-- top
		daisy.renderLine(startX, startY, endX + 1, startY, 255, 0, 255, 0)
		daisy.renderLine(startX, startY + 1, endX + 1, startY + 1, 255, 0, 255, 0)
		
		-- left
		daisy.renderLine(startX, startY, startX, endY + 1, 255, 0, 255, 0)
		daisy.renderLine(startX + 1, startY, startX + 1, endY + 1, 255, 0, 255, 0)
		
		-- bottom
		daisy.renderLine(startX, endY, endX + 1, endY, 255, 0, 255, 0)
		daisy.renderLine(startX, endY + 1, endX + 1, endY + 1, 255, 0, 255, 0)
		
		-- right
		daisy.renderLine(endX, startY, endX, endY + 1, 255, 0, 255, 0)
		daisy.renderLine(endX + 1, startY, endX + 1, endY + 1, 255, 0, 255, 0)
	end
	
	if gameInfo.buildMode then
	
		daisy.renderRectangle(0, 0, 640, 480, 128, 0, 0, 0)
		
		daisy.renderSpriteState(buildBackground, 320, 460)
	
		daisy.renderText("Resources: " .. playerStats[localPlayer].resources, 630, 6, 2, "double.fnt")
		if playerStats[localPlayer].researchItem then
			local progress = math.floor(100 * (1 - (playerStats[localPlayer].researchTime / playerStats[localPlayer].researchItem.buildTime)))
			local string = progress .. " %"

			daisy.renderText(string, 630 - 50, 26 + 18, 2, "double.fnt")
			
			daisy.renderSpriteStateFixed(playerStats[localPlayer].researchItem.guiSprite, 630 - 22, 26 + 32)
		end
		if #(playerStats[localPlayer].buildQueue) > 0 then
		
			local relY = 26 + 44
			local step = 44
			
			if #(playerStats[localPlayer].buildQueue) > 8 then
				step = 8*44 / #(playerStats[localPlayer].buildQueue)
			end
			
			for i=1,#(playerStats[localPlayer].buildQueue) do
				if i == 1 then
					local progress = math.floor(100 * (1 - (playerStats[localPlayer].buildTime / playerStats[localPlayer].buildQueue[1].buildTime)))
					local string = progress .. " %"
					
					daisy.renderText(string, 630 - 50, relY + 18, 2, "double.fnt")
				end
				
				daisy.renderSpriteStateFixed(playerStats[localPlayer].buildQueue[i].guiSprite, 630 - 22, relY + 32)
				
				relY = relY + step
			end
			daisy.renderText(string, 630, 26, 2, "double.fnt")
		end
		--[[
		if #(playerStats[localPlayer].buildQueue) > 0 then
			local string = "Build queue: "
			for i=1,#(playerStats[localPlayer].buildQueue) do
				if i > 1 then
					string = string .. ", " .. playerStats[localPlayer].buildQueue[i].name
				else
					local progress = math.floor(100 * (1 - (playerStats[localPlayer].buildTime / playerStats[localPlayer].buildQueue[1].buildTime)))
					string = string .. playerStats[localPlayer].buildQueue[i].name .. " (" .. progress .. " %)"
				end
			end
			daisy.renderText(string, 630, 26, 2, "double.fnt")
		else
			daisy.renderText("Build queue is empty", 630, 26, 2, "double.fnt")
		end
		if playerStats[localPlayer].researchItem then
			local string = "Researching: " .. playerStats[localPlayer].researchItem.name
			local progress = math.floor(100 * (1 - (playerStats[localPlayer].researchTime / playerStats[localPlayer].researchItem.buildTime)))
			string = string .. " (" .. progress .. " %)"

			daisy.renderText(string, 630, 46, 2, "double.fnt")
		else
			daisy.renderText("Not researching", 630, 46, 2, "double.fnt")
		end
		]]

		for index,conItem in ipairs(constructionTypeGui) do
		
			if conItem.lines then
				if conItem.type.isResearch then
					local lineType = 2
					if playerStats[localPlayer].technologies[conItem.type.buildId] then
						lineType = 1
					end
					for index,line in pairs(conItem.lines) do
						daisy.renderSpriteStateFixed(constructionTypeLines[line.type][lineType], line.x, line.y)
					end
				end
			end
			
		end
		for index,conItem in ipairs(constructionTypeGui) do
		
			local item = conItem.type
			local sprite = conItem.normal
			if item.isResearch then
				if playerStats[localPlayer].technologies[ item.buildId ] or (playerStats[localPlayer].researchItem == item and math.cos(playerStats[localPlayer].researchTime * math.pi * 4) > 0) then
					sprite = conItem.highlighted
				else
					local reqs = not playerStats[localPlayer].researchItem or playerStats[localPlayer].researchItem == item
					if item.requirement then
						for i=1,#(item.requirement) do
							if not playerStats[localPlayer].technologies[ item.requirement[i] ] then
								reqs = false
								break
							end
						end
					end
					if reqs then
						sprite = conItem.normal
					else
						sprite = conItem.disabled
					end
				end
			else
				local reqs = true
				if item.requirement then
					for i=1,#(item.requirement) do
						if not playerStats[localPlayer].technologies[ item.requirement[i] ] then
							reqs = false
							break
						end
					end
				end
				if reqs then
					if playerStats[localPlayer].resources >= item.cost then
						sprite = conItem.highlighted
					end
				else
					sprite = conItem.disabled
				end
			end
			
			if conItem.normal and conItem.x and conItem.y then
				daisy.renderSpriteStateFixed(sprite, conItem.x, conItem.y)
			end
			
		end
		daisy.renderSpriteState(buildSprite, 320, 460)
		
		if gameInfo.highlightBuildOption then
			local item = gameInfo.highlightBuildOption.type
		
			if not item.isResearch or (not playerStats[localPlayer].technologies[item.buildId] and item ~= playerStats[localPlayer].researchItem) then
				local x, y = gameInfo.highlightBuildOption.x, gameInfo.highlightBuildOption.y
				
				local a,r,g,b = 255, 128, 255, 128
				if item.isResearch then
					g = 128
					b = 255
				end
				
				daisy.renderText(item.name, 10, 6, 0, "double.fnt", a, r, g, b)
				daisy.renderText("Cost: " .. item.cost, 10, 26, 0, "double.fnt", a, r, g, b)
				daisy.renderText("Shortcut: " .. item.keyString, 10, 46, 0, "double.fnt", a, r, g, b)
				
				if item.requirement then
				
					daisy.renderText("Requires:", 10, 86, 0, "double.fnt", a, r, g, b)
				
					local ty = 106
					for i=1,#(item.requirement) do
						for asdf, req in pairs(constructionTypes) do
							if req.buildId == item.requirement[i] then
								daisy.renderText(req.name, 10, ty, 0, "double.fnt", a, r, g, b)
								ty = ty + 20
								break
							end
						end
					end
					

				end
				
				if mouse.left then
					daisy.renderLine(x - 22, y - 22, x + 23, y - 22, 255, 255, 255, 255)
					daisy.renderLine(x - 22, y - 22, x - 22, y + 22, 255, 255, 255, 255)
					daisy.renderLine(x + 22, y - 22, x + 22, y + 22, 255, 255, 255, 255)
					daisy.renderLine(x - 22, y + 22, x + 22, y + 22, 255, 255, 255, 255)
					daisy.renderLine(x - 23, y - 23, x + 24, y - 23, 255, 255, 255, 255)
					daisy.renderLine(x - 23, y - 23, x - 23, y + 23, 255, 255, 255, 255)
					daisy.renderLine(x + 23, y - 23, x + 23, y + 23, 255, 255, 255, 255)
					daisy.renderLine(x - 23, y + 23, x + 23, y + 23, 255, 255, 255, 255)
				end
			end
		end
		
		--[[	
		local y = 6
		-- ships
		for index,conItem in ipairs(constructionTypeGui) do
		
			local item = conItem.type
		
			-- check requirements
			local reqs = true
			
			if item.isResearch then
				reqs = false
			end
			
			if item.requirement and reqs then
				for i=1,#(item.requirement) do
					if not playerStats[localPlayer].technologies[ item.requirement[i] ] then
						reqs = false
					end
				end
			end
			
			if reqs then
			
				local a = 255
				if item.cost > playerStats[localPlayer].resources then
					a = 128
				end
				
				daisy.renderText(item.keyString .. " - " .. item.name .. " (" .. item.cost .. " ru)", 10, y, 0, "double.fnt", a, 128, 255, 128)
			
				y = y + 20
			end
		end
		
		-- research
		y = y + 20
		if not playerStats[localPlayer].researchItem then
			for index,conItem in ipairs(constructionTypeGui) do
			
				local item = conItem.type
			
				-- check requirements
				local reqs = true
				
				if not item.isResearch or playerStats[localPlayer].technologies[item.buildId] then
					reqs = false
				end
				
				if item.requirement and reqs then
					for i=1,#(item.requirement) do
						if not playerStats[localPlayer].technologies[ item.requirement[i] ] then
							reqs = false
						end
					end
				end
				
				if reqs then
				
					local a = 255
					if item.cost > playerStats[localPlayer].resources then
						a = 128
					end
					
					daisy.renderText(item.keyString .. " - " .. item.name .. " (" .. item.cost .. " ru)", 10, y, 0, "double.fnt", a, 128, 128, 255)
				
					y = y + 20
				end
			end
		end
		]]
	else
	
		local string = playerStats[localPlayer].resources .. " / "
		if #(playerStats[localPlayer].buildQueue) > 0 then
			local progress = math.floor(100 * (1 - (playerStats[localPlayer].buildTime / playerStats[localPlayer].buildQueue[1].buildTime)))
			string = string .. progress .. " % / "
		else
			string = string .. ". / "
		end
		if playerStats[localPlayer].researchItem then
			local progress = math.floor(100 * (1 - (playerStats[localPlayer].researchTime / playerStats[localPlayer].researchItem.buildTime)))
			string = string .. progress .. " %"
		else
			string = string .. "."
		end
		daisy.renderText(string, 630, 10, 2, "double.fnt")
	end

	--[[
	daisy.renderText("Resources: " .. playerStats[externalPlayer].resources, 10, 6, 0, "double.fnt")
	if #(playerStats[externalPlayer].buildQueue) > 0 then
		local string = "Build queue: "
		for i=1,#(playerStats[externalPlayer].buildQueue) do
			if i > 1 then
				string = string .. ", " .. playerStats[externalPlayer].buildQueue[i].name
			else
				local progress = math.floor(100 * (1 - (playerStats[externalPlayer].buildTime / playerStats[externalPlayer].buildQueue[1].buildTime)))
				string = string .. playerStats[externalPlayer].buildQueue[i].name .. " (" .. progress .. " %)"
			end
		end
		daisy.renderText(string, 10, 26, 0, "double.fnt")
	else
		daisy.renderText("Build queue is empty", 10, 26, 0, "double.fnt")
	end
	if playerStats[externalPlayer].researchItem then
		local string = "Researching: " .. playerStats[externalPlayer].researchItem.name
		local progress = math.floor(100 * (1 - (playerStats[externalPlayer].researchTime / playerStats[externalPlayer].researchItem.buildTime)))
		string = string .. " (" .. progress .. " %)"

		daisy.renderText(string, 10, 46, 2, "double.fnt")
	else
		daisy.renderText("Not researching", 10, 46, 2, "double.fnt")
	end
	]]
	
	if gameInfo.infoTimer > 0 then
		if isMultiplayer and multiplayer.isReplay then
			daisy.renderText("Press TAB to toggle player view!", 320, 420, 1, "double.fnt")
		else
			daisy.renderText("Press B or R for build mode, or SPACE for strategical view", 320, 420, 1, "double.fnt")
		end
	end
	
	if gameInfo.commandShips[localPlayer].died then
		local shortKey = "Ctrl"
		if daisy.isMac() then
			shortKey = "Cmd"
		end
		daisy.renderText("DEFEAT!", 320, 220, 1, "double.fnt")
		daisy.renderText("Press " .. shortKey .. "+R to Restart", 320, 240, 1, "double.fnt")
	elseif gameInfo.commandShips[externalPlayer].died then
		local shortKey = "Ctrl"
		if daisy.isMac() then
			shortKey = "Cmd"
		end
		daisy.renderText("VICTORY!", 320, 220, 1, "double.fnt")
		daisy.renderText("Press " .. shortKey .. "+R to Restart", 320, 240, 1, "double.fnt")
	end
	
	if gameInfo.chatMode and gameInfo.chatLine then
		local text = "> " .. gameInfo.chatLine .. "_"
		
		daisy.renderText(text, 320, 360, 1, "double.fnt")
	end

	do
		local textY = 430
		for index, line in pairs(chatLines) do
			daisy.renderText(line.text, 20, textY, 0, "double.fnt", 255, line.r, line.g, line.b)
			textY = textY - 20
		end
	end

end


local function keyPressed(key)

	--print("key " .. key)
	
	if key == 27 then
		-- ESCAPE key
		
		if gameInfo.chatMode then
			gameInfo.chatMode = false
			return
		end
		
		-- deselect units
		for ind, obj in pairs(gameInfo.selectedObjects) do
			obj.selected = false
		end
		gameInfo.selectedObjects = {}
		return
	end
	
	if key == 13 then
		-- ENTER key
		
		if gameInfo.chatMode then
			if gameInfo.chatLine and string.len(gameInfo.chatLine) > 0 then
				-- send chat message
				sendChatLine()
			end
			gameInfo.chatMode = false
		else
			gameInfo.chatMode = true
			gameInfo.chatLine = ""
		end
		
		return
	end
	
	if key == 8 and gameInfo.chatMode and gameInfo.chatLine then
		-- subtract key
		gameInfo.chatLine = string.sub(gameInfo.chatLine, 1, -2)
	end
	
	
	if gameInfo.chatMode then
		-- kill key presses while chatting
		return
	end
	
	
	if key == 66 or key == 82 then
		gameInfo.buildMode = not gameInfo.buildMode
	elseif key == 32 then
		gameInfo.normalMode = not gameInfo.normalMode
		
		if gameInfo.normalMode then
			centerOnSelection()
		end
	elseif key == 8 then
		cancelPurchase(localPlayer)
	elseif key == 9 and isMultiplayer and multiplayer.isReplay then
		local temp = localPlayer
		localPlayer = externalPlayer
		externalPlayer = temp
	elseif gameInfo.buildMode then
	
		local buildItem = nil
		
		for index,item in pairs(constructionTypes) do
		
			if item.key == key and (not item.isResearch or not playerStats[localPlayer].technologies[item.buildId]) then
				-- check requirements
				local reqs = true
				if item.requirement then
					for i=1,#(item.requirement) do
						if not playerStats[localPlayer].technologies[ item.requirement[i] ] then
							reqs = false
						end
					end
				end
				
				if reqs then
					buildItem = item.buildId
					break
				end
			end
		end
	
		if buildItem then
			buyShip(localPlayer, buildItem)
		end
		
		return
	end
	
	if key == 83 then
		-- S
		stopUnits(gameInfo.selectedObjects, localPlayer)
	elseif key == 72 then
		-- H
		holdUnits(gameInfo.selectedObjects, localPlayer)
	end
	
	if key >= 48 and key <= 57 then
		if daisy.isKeyPressed(17) and #gameInfo.selectedObjects > 0 then
			-- create command group
			gameInfo.commandGroups[key] = {}
			for ind, obj in pairs(gameInfo.selectedObjects) do
				if not obj.died and obj.owner == localPlayer then
					table.insert(gameInfo.commandGroups[key], obj)
				end
			end
		elseif not daisy.isKeyPressed(17) and gameInfo.commandGroups[key] then
			-- select command group
			for ind, obj in pairs(gameInfo.selectedObjects) do
				obj.selected = false
			end
			gameInfo.selectedObjects = {}
			for ind, obj in pairs(gameInfo.commandGroups[key]) do
				if not obj.died and obj.owner == localPlayer then
					obj.selected = true
					table.insert(gameInfo.selectedObjects, obj)
				end
			end
			if gameInfo.commandGroupTimer > 0 and gameInfo.commandGroupTimerId == key then
				-- center on group
				centerOnSelection()
				gameInfo.normalMode = true
 			else
				gameInfo.commandGroupTimer = .25
				gameInfo.commandGroupTimerId = key
			end
			playSelectionSound()
		end
	end
end

local function charEntered(key)

	if gameInfo.chatMode and gameInfo.chatLine and key >= 32 then
		gameInfo.chatLine = gameInfo.chatLine .. string.char(key)
	end

end

function registerGameState()
	hook.add("frameUpdate", gameRun)
	hook.add("frameRender", gameRender)
	hook.add("keyPress", keyPressed)
	hook.add("keyCharacter", charEntered)
	
	isMultiplayer, localPlayer, externalPlayer, aiPlayer = _isMultiplayer, _localPlayer, _externalPlayer, _aiPlayer
	mapSeed = _mapSeed
	MAP_SIZE = _mapSize
	FIELD_LEFT, FIELD_RIGHT, FIELD_TOP, FIELD_BOTTOM = -1000 * MAP_SIZE, 1000 * MAP_SIZE, -750 * MAP_SIZE, 750 * MAP_SIZE
	FIELD_WIDTH, FIELD_HEIGHT = FIELD_RIGHT - FIELD_LEFT, FIELD_BOTTOM - FIELD_TOP
	
	--daisy.stopAllMusic()
	--math.random()
	--daisy.playMusic("ambient" .. math.random(1,2) .. ".mp3", 0.6, true)
	
	objectTypes.catcher.sprite = daisy.createSpriteState("catcher", "IngameGfx.dat")
	objectTypes.blaster.sprite = daisy.createSpriteState("blaster", "IngameGfx.dat")
	objectTypes.harvester.sprite = daisy.createSpriteState("harvester", "IngameGfx.dat")
	objectTypes.frigate.sprite = daisy.createSpriteState("attackfrigate", "IngameGfx.dat")
	objectTypes.plasmagun.sprite = daisy.createSpriteState("plasmagunfrigate", "IngameGfx.dat")
	objectTypes.maternal.sprite = daisy.createSpriteState("maternalvessel", "IngameGfx.dat")
	objectTypes.inspector.sprite = daisy.createSpriteState("inspector", "IngameGfx.dat")
	objectTypes.cloaking.sprite = daisy.createSpriteState("shadow", "IngameGfx.dat")
	objectTypes.super.sprite = daisy.createSpriteState("missilehulk", "IngameGfx.dat")

	objectTypes.catcherT.sprite = daisy.createSpriteState("Tcatcher", "IngameGfx.dat")
	objectTypes.blasterT.sprite = daisy.createSpriteState("Tblaster", "IngameGfx.dat")
	objectTypes.harvesterT.sprite = daisy.createSpriteState("Tharvester", "IngameGfx.dat")
	objectTypes.frigateT.sprite = daisy.createSpriteState("Tattackfrigate", "IngameGfx.dat")
	objectTypes.plasmagunT.sprite = daisy.createSpriteState("Tplasmagunfrigate", "IngameGfx.dat")
	objectTypes.maternalT.sprite = daisy.createSpriteState("Tcommandvessel", "IngameGfx.dat")
	objectTypes.inspectorT.sprite = daisy.createSpriteState("Tinspector", "IngameGfx.dat")
	objectTypes.cloakingT.sprite = daisy.createSpriteState("Tshadow", "IngameGfx.dat")
	objectTypes.superT.sprite = daisy.createSpriteState("Tmissilehulk", "IngameGfx.dat")

	objectTypes.maternal.miniSprite = daisy.createSpriteState("symbmothership", "IngameGfx.dat")
	objectTypes.maternalT.miniSprite = daisy.createSpriteState("symbmothership", "IngameGfx.dat")
	objectTypes.frigate.miniSprite = daisy.createSpriteState("symbolfrigate", "IngameGfx.dat")
	objectTypes.plasmagun.miniSprite = daisy.createSpriteState("symbolfrigate", "IngameGfx.dat")
	objectTypes.frigateT.miniSprite = daisy.createSpriteState("symbolfrigate", "IngameGfx.dat")
	objectTypes.plasmagunT.miniSprite = daisy.createSpriteState("symbolfrigate", "IngameGfx.dat")
	objectTypes.harvester.miniSprite = daisy.createSpriteState("symbolharvester", "IngameGfx.dat")
	objectTypes.harvesterT.miniSprite = daisy.createSpriteState("symbolharvester", "IngameGfx.dat")
	objectTypes.super.miniSprite = daisy.createSpriteState("symbolhulk", "IngameGfx.dat")
	objectTypes.superT.miniSprite = daisy.createSpriteState("symbolhulk", "IngameGfx.dat")
	objectTypes.inspector.miniSprite = daisy.createSpriteState("symbolinspector", "IngameGfx.dat")
	objectTypes.inspectorT.miniSprite = daisy.createSpriteState("symbolinspector", "IngameGfx.dat")
	
	objectTypes.resourceSmall1.sprite = daisy.createSpriteState("rocksmall1", "IngameGfx.dat")
	objectTypes.resourceSmall2.sprite = daisy.createSpriteState("rocksmall2", "IngameGfx.dat")
	objectTypes.resourceSmall3.sprite = daisy.createSpriteState("rocksmall3", "IngameGfx.dat")
	objectTypes.resourceSmall4.sprite = daisy.createSpriteState("rocksmall4", "IngameGfx.dat")
	objectTypes.resourceMedium1.sprite = daisy.createSpriteState("rockmedium1", "IngameGfx.dat")
	objectTypes.resourceMedium2.sprite = daisy.createSpriteState("rockmedium2", "IngameGfx.dat")
	objectTypes.resourceMedium3.sprite = daisy.createSpriteState("rockmedium3", "IngameGfx.dat")
	objectTypes.resourceMedium4.sprite = daisy.createSpriteState("rockmedium4", "IngameGfx.dat")
	objectTypes.resourceLarge1.sprite = daisy.createSpriteState("rocklarge1", "IngameGfx.dat")
	objectTypes.resourceLarge2.sprite = daisy.createSpriteState("rocklarge2", "IngameGfx.dat")
	objectTypes.resourceLarge3.sprite = daisy.createSpriteState("rocklarge3", "IngameGfx.dat")
	objectTypes.resourceLarge4.sprite = daisy.createSpriteState("rocklarge4", "IngameGfx.dat")
	
	objectTypes.smallBullet.sprite = daisy.createSpriteState("bulletA", "IngameGfx.dat")
	objectTypes.blastBomb.sprite = daisy.createSpriteState("plasmaA", "IngameGfx.dat")
	objectTypes.plasmaRay.sprite = daisy.createSpriteState("plasmaray2", "IngameGfx.dat")
	
	objectTypes.catcher.wreckSprite = daisy.createSpriteState("catcherWreck", "IngameGfx.dat")
	objectTypes.blaster.wreckSprite = daisy.createSpriteState("blasterWreck", "IngameGfx.dat")
	objectTypes.harvester.wreckSprite = daisy.createSpriteState("harvesterWreck", "IngameGfx.dat")
	objectTypes.frigate.wreckSprite = daisy.createSpriteState("attackfrigateWreck", "IngameGfx.dat")
	objectTypes.plasmagun.wreckSprite = daisy.createSpriteState("plasmagunfrigateWreck", "IngameGfx.dat")
	objectTypes.maternal.wreckSprite = daisy.createSpriteState("maternalvesselWreck", "IngameGfx.dat")
	objectTypes.inspector.wreckSprite = daisy.createSpriteState("inspectorWreck", "IngameGfx.dat")
	objectTypes.cloaking.wreckSprite = daisy.createSpriteState("shadowWreck", "IngameGfx.dat")
	objectTypes.super.wreckSprite = daisy.createSpriteState("missilehulkWreck", "IngameGfx.dat")

	objectTypes.catcherT.wreckSprite = daisy.createSpriteState("TcatcherWreck", "IngameGfx.dat")
	objectTypes.blasterT.wreckSprite = daisy.createSpriteState("TblasterWreck", "IngameGfx.dat")
	objectTypes.harvesterT.wreckSprite = daisy.createSpriteState("TharvesterWreck", "IngameGfx.dat")
	objectTypes.frigateT.wreckSprite = daisy.createSpriteState("TattackfrigateWreck", "IngameGfx.dat")
	objectTypes.plasmagunT.wreckSprite = daisy.createSpriteState("TplasmagunfrigateWreck", "IngameGfx.dat")
	objectTypes.maternalT.wreckSprite = daisy.createSpriteState("TcommandvesselWreck", "IngameGfx.dat")
	objectTypes.inspectorT.wreckSprite = daisy.createSpriteState("TinspectorWreck", "IngameGfx.dat")
	objectTypes.cloakingT.wreckSprite = daisy.createSpriteState("TshadowWreck", "IngameGfx.dat")
	objectTypes.superT.wreckSprite = daisy.createSpriteState("TmissilehulkWreck", "IngameGfx.dat")

	unknownShipSprite = daisy.createSpriteState("undetected", "IngameGfx.dat")
	webSprite = daisy.createSpriteState("shadowEffect", "IngameGfx.dat")
	
	for index, type in pairs(objectTypes) do
		if type.sprite then
			type.w, type.h = daisy.getSpriteStateSize(type.sprite)
		else
			type.w, type.h = 16, 16
		end
	end

	for index, type in pairs(objectTypes) do
		if type.miniSprite then
			type.mw, type.mh = daisy.getSpriteStateSize(type.miniSprite)
		else
			type.mw, type.mh = 4, 4
		end
	end
	
	math.randomseed(mapSeed)
	
	local asteroidCount = 4 * MAP_SIZE
	for i=1,asteroidCount do
		local count = math.random(35, 50)
		local cx, cy = math.random(FIELD_LEFT + 100, -100), math.random(FIELD_TOP + 100, FIELD_BOTTOM - 100)
		
		for k=1,2 do
		
			table.insert(aiAsteroidGroup, {x = cx, y = cy})
		
			for j=1,count do
				local angle = math.random() * math.pi * 2
				local distance = math.random(5, 150)
				
				createResource(cx + math.cos(angle) * distance * 1.5, cy + math.sin(angle) * distance, math.ceil(3 * j / count))
			end
			-- place a mirror
			cx,cy = -cx,-cy
		end
	end
	
	local forceY = math.random(FIELD_TOP + 300, FIELD_BOTTOM - 300)
	
	local forcePos = FIELD_RIGHT - 250 * MAP_SIZE
	gameInfo.commandShips[1] = createShip(forcePos, forceY - 30 - objectTypes.maternalT.h / 2, 1, objectTypes.maternal, false)
	createShip(forcePos + 70, forceY + 0, 1, objectTypes.harvester, false)
	createShip(forcePos + 70, forceY + 40, 1, objectTypes.harvester, false)
	createShip(forcePos + 70, forceY + 80, 1, objectTypes.harvester, false)
	--createShip(forcePos - 100, forceY + 0, 1, objectTypes.plasmagun, false)
	--createShip(forcePos - 130, forceY + 30, 1, objectTypes.plasmagun, false)
	--createShip(forcePos - 160, forceY + 60, 1, objectTypes.blasterT, false)
	--createShip(forcePos - 430, forceY + 190, 2, objectTypes.catcherT, false)
	--createShip(forcePos - 410, forceY + 240, 2, objectTypes.catcherT, false)
	--createShip(forcePos - 420, forceY + 290, 2, objectTypes.catcherT, false)
	--createShip(forcePos - 430, forceY + 340, 2, objectTypes.blasterT, false)
	--createShip(forcePos - 440, forceY + 390, 2, objectTypes.catcherT, false)
	--createShip(forcePos - 450, forceY + 440, 2, objectTypes.frigateT, false)
	--createShip(forcePos - 200, forceY + 0, 2, objectTypes.plasmagunT, false)
	--createShip(forcePos - 300, forceY + 120, 1, objectTypes.plasmagun, false)
	--createShip(0, 0, 1, objectTypes.super, false)

	forceY = -forceY
	forcePos = FIELD_LEFT + 250 * MAP_SIZE
	gameInfo.commandShips[2] = createShip(forcePos, forceY + 30 - objectTypes.maternalT.h / 2, 2, objectTypes.maternalT, true)
	createShip(forcePos - 70, forceY + 0, 2, objectTypes.harvesterT, true)
	createShip(forcePos - 70, forceY + 40, 2, objectTypes.harvesterT, true)
	createShip(forcePos - 70, forceY + 80, 2, objectTypes.harvesterT, true)
	--createShip(forcePos + 100, forceY + 0, 2, objectTypes.catcherT, true)
	--createShip(forcePos + 100, forceY + 0, 2, objectTypes.cloakingT, true)
	--createShip(forcePos + 130, forceY + 30, 2, objectTypes.catcherT, true)
	--createShip(forcePos + 160, forceY + 60, 2, objectTypes.catcherT, true)
	--createShip(forcePos + 130, forceY + 90, 2, objectTypes.catcherT, true)
	--createShip(forcePos + 100, forceY + 120, 2, objectTypes.catcherT, true)
	--createShip(forcePos + 100, forceY + 120, 2, objectTypes.frigateT, true)
	
	viewPos.x = gameInfo.commandShips[localPlayer].x - 320
	viewPos.y = gameInfo.commandShips[localPlayer].y - 240 + gameInfo.commandShips[localPlayer].type.h / 2
	
	
	starSprites = {
		{
			daisy.createSpriteState("Basteroid7", "IngameGfx.dat"),
			daisy.createSpriteState("Basteroid8", "IngameGfx.dat")
		},
		{
			daisy.createSpriteState("Basteroid5", "IngameGfx.dat"),
			daisy.createSpriteState("Basteroid3", "IngameGfx.dat"),
			daisy.createSpriteState("Basteroid1", "IngameGfx.dat")
		},
		{
			daisy.createSpriteState("Basteroid2", "IngameGfx.dat"),
			daisy.createSpriteState("Basteroid4", "IngameGfx.dat"),
			daisy.createSpriteState("Basteroid6", "IngameGfx.dat")
		},
	}
	
	local numStars = 50 * MAP_SIZE
	for i=1,numStars do
		local z = math.floor(((i-1) * 3) / numStars)
		local sprite = starSprites[z+1][math.random(#(starSprites[z+1]))]
		table.insert(stars, {x=math.random(FIELD_LEFT * 2, FIELD_RIGHT * 2), y=math.random(FIELD_TOP * 2, FIELD_BOTTOM * 2), z=z, sprite=sprite})
	end
	bgSprite = daisy.createSpriteState("background", "IngameGfx.dat")
	bgPosition.x = math.random(640-1024, 0)
	bgPosition.y = math.random(480-1024, 0)
	
	for i=1,#constructionTypeGui do
	
		local spriteName = constructionTypeGui[i].spriteName[localPlayer]
		constructionTypeGui[i].normal = daisy.createSpriteState(spriteName .. "Normal", "IngameGfx.dat")
		constructionTypeGui[i].disabled = daisy.createSpriteState(spriteName .. "Disabled", "IngameGfx.dat")
		constructionTypeGui[i].highlighted = daisy.createSpriteState(spriteName .. "Highlighted", "IngameGfx.dat")
	
	end
	buildSprite = daisy.createSpriteState("BuildButtonOn", "IngameGfx.dat")
	buildBackground = daisy.createSpriteState("TreeBackground", "IngameGfx.dat")
	constructionTypeLines[1] = { daisy.createSpriteState("LineHorisontalNormal", "IngameGfx.dat"), daisy.createSpriteState("LineHorisontalDisabled", "IngameGfx.dat") }
	constructionTypeLines[2] = { daisy.createSpriteState("LineVerticalNormal", "IngameGfx.dat"), daisy.createSpriteState("LineVerticalDisabled", "IngameGfx.dat") }
	constructionTypeLines[3] = { daisy.createSpriteState("LineDiagonal1Normal", "IngameGfx.dat"), daisy.createSpriteState("LineDiagonal1Disabled", "IngameGfx.dat") }
	constructionTypeLines[4] = { daisy.createSpriteState("LineDiagonal2Normal", "IngameGfx.dat"), daisy.createSpriteState("LineDiagonal2Disabled", "IngameGfx.dat") }
	
	-- connect gui and data...
	for i=1,#constructionTypeGui do
		for asdf,item in pairs(constructionTypes) do
			if item == constructionTypeGui[i].type then
				item.guiSprite = constructionTypeGui[i].normal
				break
			end
		end
	end
	
	
	if not isMultiplayer then
		createAiBuildQueue()
	end

end

function unregisterGameState()
	hook.remove("frameUpdate", gameRun)
	hook.remove("frameRender", gameRender)
	hook.remove("keyPress", keyPressed)
	hook.remove("keyCharacter", charEntered)
end
