ASPECT_DEFS = {	
	movement = {
		setup = function(self, defaults)
			self.bounciness = defaults.bounciness or 0.5
			self.bounciness_z = defaults.bounciness_z or 0.5
			self.gravity = defaults.gravity or 200			
		end,
		init = function(self)
			self.dx = 0
			self.dy = 0
			self.dz = 0
			self.nx = self.x
			self.ny = self.y
			self.nz = self.z
		end,
		set_speed_2d = function(self, dx, dy)
			self.dx = dx
			self.dy = dy			
		end,
		set_speed = function(self, dx, dy, dz)
			self.dx = dx
			self.dy = dy
			self.dz = dz
		end,
		modify_speed = function(self, dx, dy, dz)
			self.dx = self.dx + dx
			self.dy = self.dy + dy
			self.dz = self.dz + dz
		end,		
		add_impact_force = function(self, dx, dy, dz)
			self.any_impact = true
			self.dx_impact = (self.dx_impact or 0)+ dx
			self.dy_impact = (self.dy_impact or 0)+ dy
			self.dx = self.dx + dx
			self.dy = self.dy + dy
			self.dz = self.dz + dz
		end,
		set_position = function(self,x,y,z)
			self.nx = x
			self.ny = y
			self.nz = z
		end,
		next_col_row = function(self, col, row)
		end,
	},
	use_item = {
		setup = function(self, defaults)			
			self.use_item_cooldown = defaults.use_item_cooldown or 2			
		end,
		init = function(self)
			self.use_item_delay = 0
		end,
		set_use_slot_intent = function(self, v)
			if v ~= self.use_slot then
				self.use_slot = v
				self.world:check_dynamic_systems(self)
			end
		end,		
	},
	light_emitter = {
		setup = function(self, defaults)			
			self.light_range = defaults.light_range or 5
			self.light_color = defaults.light_color or COLORS.white
		end,
		next_col_row = function(self, col, row)
			local world = self.world
			local universe = world.universe
			local color = self.light_color
			local range = self.light_range
			if self.light_added then
				assert(self.col)
				universe:adjust_light(self.col, self.row, color, range, -1)
			end
			universe:adjust_light(col, row, color, range, 1)
			self.light_added = true
		end,
		removing_world = function(self, world)
			if self.light_added then
				local universe = world.universe
				local range = self.light_range
				local color = self.light_color
				universe:adjust_light(self.col, self.row, color, range, -1)
				self.light_added = false
			end
		end,
		set_light_color = function(self, light)
			local color = self.light_color
			local range = self.light_range
			if self.light_added then
				universe:adjust_light(self.col, self.row, color, range, 1)
				self.light_added = false
			end
			self.light_color = light
			if self.col then
				universe:adjust_light(self.col, self.row, color, range, 1)
				self.light_added = true
			end
		end,
	},
	lit_by_tile = {
		setup = function(self, defaults)			
			self.light = COLORS.white
		end,
		next_col_row = function(self, col, row)
			local world = self.world
			local universe = world.universe			
			self.light = universe:get_light_at(col,row)
		end,
		setting_world = function(self, world)
			local universe = world.universe			
			local col,row = universe:position_to_col_row(self.x, self.y)
			self.light = universe:get_light_at(col,row)			
		end,
	},
	drop_item = {
		setup = function(self, defaults)			
			self.drop_z = defaults.drop_z or 10
		end,
		set_drop_slot_intent = function(self, v)
			if v ~= self.drop_slot then
				self.drop_slot = v
				self.world:check_dynamic_systems(self)
			end
		end,		
	},
	defense = {
		setup = function(self, defaults)
			self.defense_armor = defaults.defense_armor or 0
			self.defense_resistance = defaults.defense_resistance or 0
		end,
		init = function(self)
			self:refresh_defense_settings()
		end,
		inventory_changed = function(self)
			self:refresh_defense_settings()
		end,
		stats_changed = function(self)
			self:refresh_defense_settings()
		end,
		can_use_armor = function(self)
			if self.knockout_delay and self.knockout_delay > 0 then
				return false
			else
				return true
			end
		end,
		refresh_defense_settings = function(self)
			self:setup_aspect("defense")
			local armor = self.defense_armor
			local resistance = self.defense_resistance

			local stats = self.stats
			if stats then
				armor = armor + (stats.armor or 0)*0.25
				resistance = resistance + (stats.resistance or 0)*0.25
			end

			local inventory = self.inventory			
			if inventory then
				for slot_type,slot in pairs(inventory:get_selected_slots()) do
					if slot.def.defense then
						local item = slot:get_item()
						if item then
							local stats = item:get_stats()
							if stats then
								armor = armor + (stats.armor or 0)*0.25
								resistance = resistance + (stats.resistance or 0)*0.25
							end
						end
					end
				end
			end

			self.defense_armor = armor
			self.defense_resistance = resistance
		end,
	},
	attack = {
		setup = function(self, defaults)
			self.attack_slots = self.attack_slots or {
				primary = {},
				secondary = {},
			}
			local team = self.team

			if team and team.def.attack_tint then
				self.attack_tint = team.def.attack_tint
			else
				self.attack_tint = COLORS.attack
			end

			for index, slot in pairs(self.attack_slots) do
				slot.attack_recovery_max = defaults.attack_recovery_max or 1
				slot.attack_recovery_speed = defaults.attack_recovery_speed or 1				
				slot.attack_shots =  defaults.attack_shots or 1
				slot.attack_arc =  defaults.attack_arc or math.pi*0.25
				slot.attack_muzzle_speed = defaults.attack_muzzle_speed or 300				
				slot.attack_life_time =  defaults.attack_life_time or 1
				slot.attack_z = defaults.attack_z or 10
				slot.attack_radius = defaults.attack_radius or 10
				slot.attack_sprite = SPRITES.bullet
				slot.attack_outline = SPRITES.bullet
				slot.attack_tint = TINTS.white
				slot.attack_spinup = defaults.attack_spinup or 0
				slot.attack_knockback = defaults.attack_knockback or 0

				
				if team and team.def.projectile_outline then
					slot.attack_outline_tint = team.def.projectile_outline
				else
					slot.attack_outline_tint = TINTS.black
				end
				slot.attack_effect_power = 1
				slot.attack_effect = nil
				slot.attack_estimated_range = 0
				slot.attack_type = defaults.attack_type or "projectile"

				if slot.attack_def then
					slot.attack_def.cleanup(self, slot)
				end
				slot.attack_def = ATTACK_DEFS[slot.attack_type]
				slot.attack_def.init(self, slot)
			end
		end,
		can_use_attack = function(self)
			if self.knockout_delay and self.knockout_delay > 0 then
				return false
			else
				return true
			end
		end,
		stats_changed = function(self)
			self:refresh_defense_settings()
		end,
		set_team = function(self)
			self:refresh_attack_settings()
		end,
		inventory_changed = function(self)
			self:refresh_attack_settings()
		end,
		refresh_attack_settings = function(self)
			self:setup_aspect("attack")
			local stats = self.stats	
			local inventory = self.inventory

			for slot_type, slot in pairs(self.attack_slots) do
				local item = self.unarmed_item
				

				local inv_slot = nil
				if inventory then
					inv_slot = inventory:get_selected_slot_of_type(slot_type)
					item = inv_slot and inv_slot:get_item() or item
				end

				if item then
					--print(self.char_index, item.index)
					local stats = item:get_stats()
					if stats then
						--print("STATS")
						local start_muzzle = slot.attack_muzzle_speed
						slot.attack_recovery_max = slot.attack_recovery_max - math.min(0,stats.rate/slot.attack_recovery_speed)
						slot.attack_recovery_speed = slot.attack_recovery_speed + math.max(0,stats.rate)							
						slot.attack_shots = stats.shots
						slot.attack_muzzle_speed = slot.attack_muzzle_speed + stats.muzzle*100							

						local new_muzzle = slot.attack_muzzle_speed
						slot.attack_life_time = (1+stats.range)/new_muzzle*25
						slot.attack_z = slot.attack_z + 1
						slot.attack_radius = slot.attack_radius + 1
						--print(stats.speed, self.attack_muzzle_speed)
					end

					
					local effect = item:get_effect()
					if effect then
						slot.attack_effect = effect
					end

					local sprite,outline = item:get_projectile_sprites()
					if sprite then
						slot.attack_sprite = sprite
						slot.attack_outline = outline
					end

					local tint,outline_tint = item:get_tints()
					if tint then
						slot.attack_tint = tint
						slot.attack_outline_tint = outline_tint or slot.attack_outline_tint						
					end

					if slot.item_slot ~= inv_slot then
						if slot.item_slot then
							slot.item_slot:set_attack_slot(nil)
						end
						if inv_slot then
							inv_slot:set_attack_slot(slot)
						end
					end

					local attack_type = item:get_attack_type()
					if attack_type then
						slot.attack_type = attack_type
						slot.attack_def = ATTACK_DEFS[attack_type]						
					end

					local power_stat = item:get_power_stat()
					if power_stat and stats then
						slot.attack_effect_power = math.stat_mod(slot.attack_effect_power, stats[power_stat] or 0, 0.1)
					end				
				end				
				
				local estimate = slot.attack_radius + slot.attack_life_time * slot.attack_muzzle_speed
				--print("est", slot_type, estimate)
				slot.attack_estimated_range = estimate
				slot.attack_def.init(self, slot)
			end
		
		end,
		init = function(self)			
			for slot_type, slot in pairs(self.attack_slots) do				
				slot.delay = 0
				slot.delay_start = 0
				slot.spinup = 0
			end
			self:refresh_attack_settings()
		end,
		get_attack_slot = function(self, slot)
			return self.attack_slots[slot]
		end,
		set_attack_delay = function(self, slot, v)
			local slot_holder = self.attack_slots[slot]
			slot_holder.delay = v
			slot_holder.delay_start = v
		end,
		set_attack_intent = function(self, slot, v)
			self.attack_slots[slot].intent = v
		end,
		set_attack_slot = function(self, slot, v)
			self.attack_slots[slot].intent = v
		end,
		get_in_range_attack_slot = function(self, dist_to_target)
			local best = math.huge
			local best_slot = nil

			for index, slot in pairs(self.attack_slots) do
				local slot_range = slot.attack_estimated_range				
				if slot_range > dist_to_target and slot_range < best then
					best = slot_range
					best_slot = index
				end
			end

			return best_slot
		end,
		get_best_attack_range = function(self)
			local best = 0 
			for index, slot in pairs(self.attack_slots) do
				best = math.max(best, slot.attack_estimated_range)
			end
			return best
		end,
		get_worst_attack_range = function(self)
			local best = nil 
			for index, slot in pairs(self.attack_slots) do
				best = math.min(best or slot.attack_estimated_range, slot.attack_estimated_range)
			end
			return best or 0
		end,
		set_unarmed_item = function(self, item)
			self.unarmed_item = item
			self:refresh_attack_settings()
		end,
	},
	effect_feedback = {
		setup = function(self, defaults)
			self.feedback_z = defaults.feedback_z or 10
			self.feedback_scale = defaults.feedback_scale or 1
		end,
		init = function(self)
			self.r = 1
			self.g = 1
			self.b = 1
		end,
		set_feedback = function(self, feedback_type, amount)			
			if not self.feedback then
				self.feedback = {}				
			end
			self.feedback[feedback_type] = amount
			if not self.any_feedback then
				self.any_feedback = true
				self.world:check_dynamic_systems(self)
			end			
		end,
		add_feedback = function(self, feedback_type, amount)			
			if not self.feedback then
				self.feedback = {}				
			end
			self.feedback[feedback_type] = math.min(1,(self.feedback[feedback_type] or 0) + amount)
			if not self.any_feedback then
				self.any_feedback = true
				self.world:check_dynamic_systems(self)
			end			
		end,
		emit_feedback_text = function(self, speed, text, color, scale, font)
			local ent = Entity:new("feedback")
			ent:set_hud_text(text)
			if font then
				ent:set_hud_font(font)
			end
			local fscale = self.feedback_scale*(scale or 1)
			ent:set_hud_scale(fscale)
			if color then
				ent:set_hud_color(color)
			end
			ent:set_position(self.x, self.y, self.z + self.feedback_z)
			ent:set_speed(math.randomGaussian()*speed, math.randomGaussian()*speed, math.random()*speed + speed*2)
			self.world:add_ent(ent)
		end,
		clear_feedback = function(self)
			self.any_feedback = false
			self.world:check_dynamic_systems(self)
		end,
	},
	block = {
		setup = function(self, defaults)
			self.block_resistance = defaults.block_resistance or 1
			self.block_stamina_max =  defaults.block_stamina or 1
			self.block_stamina =  defaults.block_stamina or 1
			self.block_stamina_regeneration = defaults.block_stamina_regeneration or 1
			self.block_recovery_speed = defaults.block_recovery_speed or 1
		end,
		init = function(self)
			self.block_delay = 0
		end,		
	},
	hud_health = {
		setup = function(self, defaults)
			self.render_health_w = defaults.render_health_w or 10
			self.render_health_h = defaults.render_health_h or 2		
			self.render_health_y_offset = defaults.render_health_y_offset or 10
			self.render_health_color = COLORS.health
		end,
		add_y_offset = function(self, off)
			self.render_health_y_offset = self.render_health_y_offset + off
		end,
		health_render = function(self, x, y, scale, perspective, angle, a, r, g, b)
			local w = self.render_health_w*scale
			local h = self.render_health_h*scale
			local rx,ry = x-w*0.5,y-self.render_health_y_offset*scale-h*0.5
			local percent = self.health/self.health_max
			local col = self.render_health_color
			video.renderSpriteRectangle(rx-1,ry-1,w+2,h+2,128, 0,0,0)
			video.renderSpriteRectangle(rx,ry,w*percent,h,200,col.r,col.g,col.b)
			if self.stamina and self.stamina < self.stamina_max then
				local col = COLORS.stamina
				local stamina_percent = self.stamina/self.stamina_max
				video.renderSpriteRectangle(rx,ry+h,w*stamina_percent,1,200,col.r,col.g,col.b)
			end
		end,
		modify_health = function(self)
			local percent = self.health/self.health_max
			if percent > 0.5 then
				self.render_health_color = COLORS.health
			elseif percent > 0.25 then
				self.render_health_color = COLORS.health_medium
			else
				self.render_health_color = COLORS.health_low
			end
		end,
	},
	hud_stamina = {
		setup = function(self, defaults)
			self.render_stamina_w = defaults.render_stamina_w or 10
			self.render_stamina_h = defaults.render_stamina_h or 0.5		
			self.render_stamina_y_offset = defaults.render_stamina_y_offset or 8
		end,
		add_y_offset = function(self, off)
			self.render_stamina_y_offset = self.render_stamina_y_offset + off
		end,
		stamina_render = function(self, x, y, scale, perspective, angle, a, r, g, b)
			local w = self.render_stamina_w*scale
			local h = self.render_stamina_h*scale
			local rx,ry = x-w*0.5,y-self.render_stamina_y_offset*scale-h*0.5
			local percent = self.stamina/self.stamina_max
			local col = COLORS.stamina
			video.renderSpriteRectangle(rx-1,ry-1,w+2,h+2,64, 0,0,0)
			video.renderSpriteRectangle(rx,ry,w*percent,h,100,col.r,col.g,col.b)
		end,
	},
	stats = {
		set_stats = function(self, stats)
			self.stats = stats
			self:stats_changed()
		end,
		stats_changed = function(self)
		end,
	},
	stamina = {
		setup = function(self, defaults)
			self.stamina = self.stamina or defaults.stamina or 1
			self.stamina_max = defaults.stamina or 1
			self.stamina_recovery_speed = defaults.stamina_recovery_speed or 1
		end,
		init = function(self)	
		end,
		stats_changed = function(self, stats)
			self:refresh_stamina_settings()
		end,
		refresh_stamina_settings = function(self)
			self:setup_aspect("stamina")
			local stamina = self.stamina_max
			local recovery = self.stamina_recovery_speed

			local stats = self.stats
			if stats then
				stamina = math.stat_mod(stamina, stats.stamina, 0.1)
				recovery = math.stat_mod(recovery, stats.recovery, 0.05)
			end

			self.stamina_recovery_speed = recovery
			self.stamina_max = stamina
			self.stamina = stamina
		end,
		is_stamina_not_full = function(self)
			return self.stamina < self.stamina_max
		end,
		set_stamina = function(self, stamina)
			self.stamina = stamina
		end,		
		modify_stamina = function(self, stamina)
			self.stamina = math.clamp(self.stamina + stamina,0,self.stamina_max)			
			self.world:check_dynamic_systems(self)
		end,
	},
	health = {
		setup = function(self, defaults)
			self.health = self.health or defaults.health or 1
			self.health_max = defaults.health or 1
		end,
		stats_changed = function(self, stats)
			self:refresh_health_settings()
		end,
		refresh_health_settings = function(self)
			self:setup_aspect("health")
			local health = self.health_max

			local stats = self.stats
			if stats then
				health = math.stat_mod(health, stats.health, 0.5)				
			end

			self.health_max = health
			self.health = health

			--print("new health", self.char_index, health)
		end,
		init = function(self)			
		end,
		is_health_not_full = function(self)
			return self.health < self.health_max
		end,
		set_health = function(self, health)
			self.health = health
		end,		
		modify_health = function(self, health)
			self.health = math.clamp(self.health + health,0,self.health_max)
			if self.health <= 0 then
				self.dead = true
			end
			self.world:check_dynamic_systems(self)
		end,
	},
	team_target = {
		setup = function(self, defaults)
			self.radius = defaults.radius or 10
		end,		
	},
	team_bullet = {
		setup = function(self, defaults)
			self.radius = defaults.radius or 10
		end,		
	},
	home_location = {
		set_location = function(self, location)
			self.location = location
		end,		
	},
	angle = {		
		init = function(self)
			self.angle = self.angle or math.randomAngle()
		end,
		set_angle = function(self, angle)
			self.angle = angle
		end,
	},
	named = {		
		set_name = function(self, name)
			self.name = name
		end,
		get_name = function(self)
			return self.name
		end,
		verify_hud_name = function(self, hud_info)
			if self.name then
				hud_info:set_key("name", self.name)				
			end
		end,
	},
	name_render = {
		render_hud = function(self, x, y, scale, perspective, angle, a, r, g, b)
			video.renderTextSprites(self.name, x, y, "medium", 1, a, r, g, b)
		end,
	},	
	level = {		
		set_level = function(self, level)
			self.level = level
		end,
		verify_hud_name = function(self, hud_info)
			if self.level then
				hud_info:set_key("level", "Level:"..self.level)
			end
		end,
	},
	item = {
		set_item = function(self, item)
			self.item = item
		end,
		verify_hud_name = function(self, hud_info, compare)
			if self.item then
				self.item:apply_hud_template(hud_info, "name", compare)
			end
		end,
	},
	container = {
		set_container = function(self, container)
			self.container = container
		end,
	},
	inventory = {		
		set_inventory = function(self, inventory)
			self.inventory = inventory
			self:inventory_changed()
		end,
		inventory_changed = function(self)

		end,
	},
	team_owned = {		
		set_team = function(self, team)
			assert(not self.team, "switching team with this function is not supported")
			self.team = assert(team,"no team!")
		end,
	},
	area_generator = {		
		set_area_generator = function(self, area_generator, generator_range, generator_cell_range)
			self.area_generator = area_generator
			self.area_generator_range = generator_range
			self.area_generator_cell_range = generator_cell_range
		end,
	},
	interpolated = {
		set_position = function(self,x,y,z)
			self.rx = x
			self.ry = y
			self.rz = z
		end,
	},
	limited_life = {
		setup = function(self, defaults)
			self.life_time = defaults.life_time or 1			
		end,
		set_life_time = function(self,life_time)
			self.life_time = life_time			
		end,
	},
	impact_effect = {
		set_effect = function(self, effect, effect_power)
			self.effect = effect
			self.effect_power = effect_power
		end,
	},
	pickup_items = {
		setup = function(self, defaults)
			self.pickup_range = defaults.pickup_range or 10
			self.pickup_pull_range = defaults.pickup_pull_range or 100
			self.pickup_pull_speed = defaults.pickup_pull_speed or 100
			self.pickup_pull_acceleration = defaults.pickup_pull_acceleration or 100
			self.pickup_z = defaults.pickup_z or 8
		end,
		init = function(self)
			self.desired_pickups = {}
		end,
		set_desired_pickup = function(self, pickup)
			self.desired_pickups[pickup] = true
		end,
		remove_desired_pickup = function(self, pickup)
			self.desired_pickups[pickup] = nil
		end,
		clear_desired_pickups_out_of_range = function(self)
			local range2 = self.pickup_pull_range^2
			for ent, _ in pairs(self.desired_pickups) do
				local dist2 = math.distance2(ent.x, ent.y, self.x, self.y)
				if dist2 > range2 then				
					self.desired_pickups[ent] = nil
				end
			end	
		end,
		clear_desired_pickups = function(self)
			for ent, _ in pairs(self.desired_pickups) do
				ent:set_speed_2d(ent.dx*0.5, ent.dy*0.5)
				self.desired_pickups[ent] = nil
			end			
		end
	},
	paperdoll_render = {
		setup = function(self, defaults)
			self.angle_offset = defaults.angle_offset or 0
			self.angle_override = defaults.fixed_angle and 0 or nil
			self.render_offset_z_factor = 1
			self.render_offset_z = defaults.render_offset_z or 0
			self.paperdoll_sprites = {}
		end,
		set_sprites = function(self, sprite, outline)
			self.render_sprite = sprite
			self.outline_sprite = outline			
		end,
		refresh_sprites = function(self)
			table.clear(self.paperdoll_sprites)
			local inventory = self.inventory
			local offsets = inventory.def.sprite_offset
			local default_sprite_names = inventory.def.default_sprites
			for index, slot_type in ipairs(inventory.def.sprite_order) do
				local offset = offsets[slot_type]
				local ox,oy = offset.x,offset.y
				local item = inventory:get_selected_item_of_type(slot_type)
				local holder = nil
				if item then
					holder = {ox = ox, oy = oy}
					local sprite, out = item:get_sprites()
					holder.sprite = sprite
					holder.outline = out					
				else
					local slot_default = default_sprite_names[slot_type]
					if slot_default then
						holder = {ox = ox, oy = oy}
						holder.sprite = SPRITES[slot_default.sprite_name]
						holder.outline = SPRITES[slot_default.outline_name]
					end
				end
				if holder then
					table.insert(self.paperdoll_sprites, holder)
				end
			end
		end,		
		inventory_changed = function(self)
			self:refresh_sprites()
		end,
		set_tints = function(self, tint, outline_tint)
			self.tint = assert(tint)
			self.outline_tint = assert(outline_tint)
			self:restore_tint_color()
		end,
		restore_tint_color = function(self)			
			local outline_tint = self.outline_tint or TINTS.black
			self.r = 1
			self.g = 1
			self.b = 1
			self.out_a = outline_tint.a or 0
			self.out_r = outline_tint.r or 0
			self.out_g = outline_tint.g or 0
			self.out_b = outline_tint.b or 0
		end,
		set_flip = function(self, flip)
			self.flip = flip
		end,
		set_team = function(self, team)
			if team.def.outline_tint then
				if self.aspects.health then
					self.outline_tint = team.def.outline_tint					
				else
					self.outline_tint = team.def.outline_bg_tint					
				end
				self:restore_tint_color()
			end
		end,
		init = function(self)
			self.angle = self.angle or 0
			self.tint = self.tint or TINTS.white
			self.outline_tint = self.outline_tint or TINTS.black
			self:restore_tint_color()
			self.flip = false
		end,
		render = function(self, x, y, scale, perspective, angle, a, r, g, b)
			if self.render_sprite then
				local tint = self.tint
				local outline_tint = self.outline_tint
				local color = self.light or COLORS.white
				y = y - self.render_offset_z*self.render_offset_z_factor
				local r,g,b = color.r*self.r, color.g*self.g, color.b*self.b
				local aa,rr,gg,bb = a,r*tint.r, g*tint.g, b*tint.b
				local ang = (self.angle_override or self.angle) +self.angle_offset			
				local flip = self.flip
				--video.renderSpriteState(self.render_sprite, x, y, scale, ang, aa, rr,gg,bb, flip)
				local oaa,orr,ogg,obb = a*self.out_a, r*self.out_r, g*self.out_g, b*self.out_b
				local ff = flip and -1 or 1
				for index, holder in pairs(self.paperdoll_sprites) do
					video.renderSpriteState(holder.outline, x+holder.ox*scale*ff, y+holder.oy*scale, scale, ang, oaa, orr,ogg,obb, flip)
				end
				
				for index, holder in pairs(self.paperdoll_sprites) do
					video.renderSpriteState(holder.sprite, x+holder.ox*scale*ff, y+holder.oy*scale, scale, ang, aa, rr,gg,bb, flip)
				end
			end
		end,
	},
	shadow_render = {
		setup = function(self, defaults)
			self.shadow_sprite = SPRITES[defaults.sprite_name or "shadow_small"]
		end,
		render_shadow = function(self, x, y, scale, perspective, angle, a, r, g, b)
			local z_scale = self.z/100
			video.renderSpriteState(self.shadow_sprite, x, y, (scale + z_scale), angle, a/(1+z_scale), 0,0,0)
		end,
	},
	sprite_render = {
		setup = function(self, defaults)
			self.render_sprite = SPRITES[defaults.sprite_name or "resource_19"]
		end,
		init = function(self)
			self.angle = self.angle or 0
			self:restore_tint_color()
		end,
		restore_tint_color = function(self)			
			self.r = 1
			self.g = 1
			self.b = 1
		end,
		set_sprites = function(self, sprite)
			self.render_sprite = sprite	
		end,
		render = function(self, x, y, scale, perspective, angle, a, r, g, b)
			local light = self.light or COLORS.white
			local r,g,b = light.r*self.r, light.g*self.g, light.b*self.b
			video.renderSpriteState(self.render_sprite, x, y, scale, self.angle, a, r,g,b)
		end,
	},
	sprite_composite_render = {
		setup = function(self, defaults)
			self.angle_offset = defaults.angle_offset or 0
			self.angle_override = defaults.fixed_angle and 0 or nil
			self.render_offset_z_factor = 1
			self.render_offset_z = defaults.render_offset_z or 0
		end,
		set_sprites = function(self, sprite, outline)
			self.render_sprite = sprite
			self.outline_sprite = outline			
		end,
		set_tints = function(self, tint, outline_tint)
			self.tint = assert(tint)
			self.outline_tint = assert(outline_tint)
			self:restore_tint_color()
		end,
		restore_tint_color = function(self)			
			local outline_tint = self.outline_tint or TINTS.black
			self.r = 1
			self.g = 1
			self.b = 1
			self.out_a = outline_tint.a or 0
			self.out_r = outline_tint.r or 0
			self.out_g = outline_tint.g or 0
			self.out_b = outline_tint.b or 0
		end,
		set_flip = function(self, flip)
			self.flip = flip
		end,
		set_team = function(self, team)
			if team.def.outline_tint then
				if self.aspects.health then
					self.outline_tint = team.def.outline_tint					
				else
					self.outline_tint = team.def.outline_bg_tint					
				end
				self:restore_tint_color()
			end
		end,
		init = function(self)
			self.angle = self.angle or 0
			self.tint = self.tint or TINTS.white
			self.outline_tint = self.outline_tint or TINTS.black
			self:restore_tint_color()
			self.flip = false
		end,
		render = function(self, x, y, scale, perspective, angle, a, r, g, b)
			if self.render_sprite then
				local tint = self.tint
				local outline_tint = self.outline_tint
				local color = self.light or COLORS.white
				y = y - self.render_offset_z*self.render_offset_z_factor
				local r,g,b = color.r*self.r, color.g*self.g, color.b*self.b
				local aa,rr,gg,bb = a,r*tint.r, g*tint.g, b*tint.b
				local ang = (self.angle_override or self.angle) +self.angle_offset			
				local flip = self.flip
				video.renderSpriteState(self.render_sprite, x, y, scale, ang, aa, rr,gg,bb, flip)
				aa,rr,gg,bb = a*self.out_a, r*self.out_r, g*self.out_g, b*self.out_b
				video.renderSpriteState(self.outline_sprite, x, y, scale, ang, aa, rr,gg,bb, flip)
			end
		end,
	},
	item_render = {
		setup = function(self, defaults)
			self.item_height = defaults.item_height or 2
		end,
		init = function(self)
			self.angle = self.angle or 0
		end,		
		render = function(self, x, y, scale, perspective, angle, a, r, g, b)
			self.item:render(x,y-self.item_height*scale, scale, perspective, self.angle, a, r, g, b)			
		end,
	},
	pickupable = {
		setup = function(self, defaults)
			self.pickup_radius = defaults.pickup_radius or 10			
		end,
		set_item = function(self, item)
			if item then
				self.auto_pickup = item:should_pickup_automatically()
			end
		end,
	},
	hud_text = {
		setup = function(self, defaults)			
			self.hud_color = COLORS.white
			self.hud_text = ""
			self.hud_font = "small"
			self.hud_scale = 1
		end,
		set_hud_text = function(self, hud_text)
			self.hud_text = hud_text			
		end,
		set_hud_color = function(self, color)
			self.hud_color = color
		end,
		set_hud_font = function(self, font)
			self.hud_font = font
		end,
		set_hud_scale = function(self, scale)
			self.hud_scale = scale
		end,	
	},	
	hud_name = {
		setup = function(self, defaults)
			self.hud_radius = defaults.hud_radius or 10
		end,
		set_hud_name = function(self, hud_name)
			self.hud_name = hud_name			
		end,
		verify_hud_name = function(self, hud_info)
		end,
		set_hud_name_display_fade = function(self, amt, compare)
			self:set_hud_name_display(true, compare)
			self.hud_name_display_increase = amt
			self.hud_name_display_fade = -1
			self.world:check_dynamic_systems(self)
		end,	
		set_hud_name_display = function(self, v, compare)
			self.hud_name_display = v
			if v then
				if not self.hud_name or self.hud_name_compare ~= compare then
					local hud_info = HudInfo:new()
					self:verify_hud_name(hud_info, compare)
					self:set_hud_name(hud_info)
					self.hud_name_compare = compare
				end				
			else
				self:set_hud_name(nil)
			end
			self.hud_name_display_increase = nil
			self.hud_name_display_fade = nil
			self.world:check_dynamic_systems(self)
		end,		
	},	
	hud_info = {
		set_hud_info = function(self, hud_info)
			self.hud_info = hud_info			
		end,		
		set_hud_info_display = function(self, v, compare)
			self.hud_info_display = v
			if v then
				if self.item then
					local template = compare and "compare" or "info"
					self:set_hud_info(self.item:get_hud(template, compare))
				end				
			else
				self:set_hud_info(nil)
			end
			self.world:check_dynamic_systems(self)
		end,
		set_item = function(self, item)
			if self.hud_info_display then
				self:set_hud_info(item:get_hud("info"))
			end
		end,
	},	
	ground_friction = {
		setup = function(self, defaults)			
			self.ground_friction = defaults.ground_friction or 0.1
		end,
	},
	bipedal_thrust = {
		setup = function(self, defaults)
			self.thrust_max_speed = defaults.max_speed or 1
			self.thrust_acceleration = defaults.acceleration or 1
			self.impact_resistance = defaults.impact_resistance or 50			
			self.control_recovery_speed = defaults.control_recovery_speed or 2
			self.control_air = defaults.control_air or 0.1
			self.control_ground = defaults.control_ground or 1
			self.ground_friction = defaults.ground_friction or 1.75
			self.look_speed = defaults.look_speed or 1
			self.mass = defaults.impact_mass or 1
		end,
		init = function(self)
			self.thrust_target = 1
			self.thrust_factor = 1
			self.thrust_amount = 0
			self.knockout_delay = 0
			self.thrust_angle = math.randomAngle()
			self.look_angle = self.angle or math.randomAngle()
		end,
		add_friendly_impact_force = function(self, dx, dy, dz)
			self.dx = self.dx + dx
			self.dy = self.dy + dy
			self.dz = self.dz + dz
			local impact_amount = math.distance(0,0,dx, dy)
			local impact_effect = impact_amount/self.impact_resistance
			self.thrust_factor = self.thrust_factor/(1+impact_effect)
		end,
		stats_changed = function(self)
			self:refresh_bipedal_settings()
		end,
		refresh_bipedal_settings = function(self)
			self:setup_aspect("bipedal_thrust")
			local thrust_max_speed = self.thrust_max_speed
			local thrust_acceleration = self.thrust_acceleration
			local impact_resistance = self.impact_resistance
			local control_recovery_speed = self.control_recovery_speed
			local look_speed = self.look_speed
			local mass = self.mass

			local control_air = self.control_air
			local control_ground = self.control_ground
			

			local stats = self.stats
			if stats then
				local size = stats.size or 0
				local speed = stats.speed or 0
				local control = stats.control or 0

				impact_resistance = math.stat_mod(impact_resistance, size, 1)
				mass = math.stat_mod(mass, size, 1)
				thrust_acceleration = math.stat_mod(thrust_acceleration, speed, 0.1)
				thrust_max_speed = math.stat_mod(thrust_max_speed, speed, 0.1)
				look_speed = math.stat_mod(look_speed, speed, 0.25)

				control_recovery_speed = math.stat_mod(control_recovery_speed, control, 0.1)
				control_air = math.stat_mod(control_air, control, 0.1)
				control_ground = math.stat_mod(control_ground, control, 0.1)
			end

			self.thrust_max_speed = thrust_max_speed
			self.thrust_acceleration = thrust_acceleration
			self.impact_resistance = impact_resistance
			self.control_recovery_speed = control_recovery_speed
			self.look_speed = look_speed
			self.mass = mass

			self.control_air = control_air
			self.control_ground = control_ground
		end,
	},
	shop_keeper = {
		setup = function(self, defaults)
			self.shop_scan_delay_max = defaults.shop_scan_delay_max or 0.5
		end,
		init = function(self)
			self.shop_scan_delay = self.shop_scan_delay_max*math.random()
		end,
		set_shop = function(self, shop)
			self.shop = shop
			if self.world then
				self.world:check_dynamic_systems(self)
			end
		end,
		set_location = function(self, location)
			local shop = self.shop
			if shop then
				shop:set_location_name(location:get_name())
			end
		end,
		set_shop_customer = function(self, customer)			
			self.customer = customer
		end,
	},
	shop_customer = {
		setup = function(self, defaults)
			self.shopping_range = defaults.shopping_range or 100
		end,
		set_buy_slot_intent = function(self, v)
			if v ~= self.buy_slot then
				self.buy_slot = v
				self.world:check_dynamic_systems(self)
			end
		end,
		set_hovered_keeper = function(self, keeper)
			self.hovered_keeper = keeper
		end,
	},
	crafter = {
		setup = function(self, defaults)			
		end,
		set_craft_slot_intent = function(self, v)
			if v ~= self.craft_slot then
				self.craft_slot = v
				self.world:check_dynamic_systems(self)
			end
		end,		
	},
	player_input = {
		setup = function(self, defaults)
			self.highlight_radius = defaults.highlight_radius or 10
		end,
		init = function(self)
			self.thrust_angle = 0
			self.thrust_amount = 0
			self.hover_delay = 0
			self.intents = {}
		end,
		set_player = function(self, player)
			self.player = player
		end,
		set_input_vector = function(self, angle, amount)
			self.thrust_angle = angle
			self.thrust_amount = amount
		end,
		set_look_angle = function(self, angle)
			self.look_angle = assert(angle)
		end,
		get_action_intent = function(self, action)
			return self.intents[action]
		end,
		released_action_intent = function(self, action)
			local int = self.intents[action]
			if int then
				local def = INTENT_DEFS[int]
				if def.release_removes then
					self:set_action_intent(action, nil)
				end
			end
		end,
		set_action_intent = function(self, action, intent, target)
			local intents = self.intents
			local old = intents[action]
			if old then
				local def = INTENT_DEFS[old]
				def.remove(self)
			end
			intents[action] = intent
			if intent then
				local def = INTENT_DEFS[intent]
				def.set(self, target)
			end
		end,
		step_action_intents = function(self)
			for index, v in pairs(self.intents) do
				local def = INTENT_DEFS[v]
				def.step(self)
			end
		end,
		set_hovered_target = function(self, target)
			if target ~= self.hovered_target then
				local old = self.hovered_target
				if old and old.world then
					if old.aspects.hud_name then
						old:set_hud_name_display(false)
					end
				end
				self.hovered_target = target
				if target then
					if target.aspects.hud_name then
						target:set_hud_name_display(true)
					end
				end
			end
		end,
		set_hovered_pickup = function(self, pickup)
			if pickup ~= self.hovered_pickup then
				local old = self.hovered_pickup
				if old and old.world then
					if old.aspects.hud_info then
						old:set_hud_info_display(false)
					end
				end
				self.hovered_pickup = pickup
				if pickup then
					if pickup.aspects.hud_info then
						local item = pickup.item
						local item_slot = item:get_slot_type()
						local inventory = self.inventory
						local curr = inventory:get_selected_item_of_type(item_slot)
						if curr then							
							pickup:set_hud_info_display(true, curr)
						else
							pickup:set_hud_info_display(true)
						end
					end
				end
			end
		end,
	},
	ai_aggression_response_team_change = {
		aggressive_action_by = function(self, ent)
			local team = self.team			
			local agro_team = team:get_agro_team()
			if team ~= agro_team then				
				self.world.universe:change_ent_team(self, agro_team)
			end
		end,
	},
	ai_input = {
		init = function(self)
			self.thrust_angle = 0
			self.thrust_amount = 0
			self.intents = {}	
			self.desired_angle = math.randomAngle()
		end,
		set_look_angle = function(self, angle)
			self.look_angle = assert(angle)
		end,
		set_desired_vector = function(self, angle, amount)
			self.desired_angle = angle
			self.desired_amount = assert(amount,"yo")
		end,
		set_desired_flip = function(self, flip)
			self.desired_flip = flip
		end,
		set_action_intent = function(self, action, intent, target)
			local intents = self.intents
			local old = intents[action]
			if old then
				local def = INTENT_DEFS[old]
				def.remove(self)
			end
			intents[action] = intent
			if intent then
				local def = INTENT_DEFS[intent]
				def.set(self, target)
			end
		end,
		set_input_vector = function(self, angle, amount)
			self.thrust_angle = angle
			self.thrust_amount = amount
		end,
	},
	ai_tactics = {
		setup = function(self, defaults)
			self.ai_tactics_idle_template = defaults.idle_template or "idle"
			self.ai_tactics_target_template = defaults.target_template or "target_random"
			self.ai_tactics_engage_template = defaults.engage_template or "target_engage"			
		end,
		init = function(self)			
			self.ai_tactics_delay = 0
			self.tactics_template = self.ai_tactics_idle_template
		end,
		aggressive_action_by = function(self, ent)
			if not self.ai_target then
				if self.tactics_template ~= self.ai_tactics_engage_template then
					self:set_tactics_template(self.ai_tactics_engage_template)				
				end
			end
		end,
		set_ai_tactics = function(self, tactics)
			local old = self.ai_tactics
			if old then
				local pdef = AI_TACTICS[old]
				pdef.remove(self, self.old_target)
			end
			self.ai_tactics = tactics
			self.tactics_template_fresh = false
			local ai_target = self.ai_target
			if tactics then
				local pdef = assert(AI_TACTICS[tactics], tactics)
				if not pdef.require_target or ai_target then
					pdef.set(self, ai_target)
					self.old_target = ai_target
				else
					self.old_target = nil
					self.ai_tactics = nil
				end
			end
		end,
		set_tactics_template = function(self, template)
			self.tactics_template = template
			self.tactics_template_fresh = true
			local tdef = AI_TACTICS_TEMPLATES[template]
			self.ai_tactics_delay = math.min(self.ai_tactics_delay, tdef.min_delay)
		end,
		set_ai_target = function(self, target)
			if target then
				self:set_tactics_template(self.ai_tactics_target_template)
			else
				self:set_tactics_template(self.ai_tactics_idle_template)
			end
		end,
	},
	ai_detection = {
		setup = function(self, defaults)
			self.ai_engaged_detection_range = defaults.engaged_detection_range or 1500
			self.ai_idle_detection_range = defaults.detection_range or 500
		end,
		init = function(self)
			self.ai_scan_delay = 1 + math.random()
			self.ai_detection_range = self.ai_idle_detection_range
		end,
		set_ai_target = function(self, target)			
			self.ai_target = target
			if not target then
				self.ai_detection_range = self.ai_idle_detection_range
			end	
		end,
		aggressive_action_by = function(self, ent)
			if not self.ai_target then
				self.ai_scan_delay = 0
				self.ai_detection_range = self.ai_engaged_detection_range
			end
		end,
	},
	ai_target = {}
}