# -*- coding: utf-8 -*-
"""
UTc! module: src.collisions

Purpose
=======
 Provides routines for processing collisions between in-game ODE elements.
 
Legal
=====
 All code, unless otherwise indicated, is original, and subject to the
 terms of the GPLv3, which is provided in COPYING.
 
 (C) Neil Tallim, 2009
"""
import ode

import effects
import tankchan
import weapons

def collisionHandler(args, geom_1, geom_2):
	"""
	The general collision callback; it is invoked whenever any two elements touch
	in game-space, and it is used to determine the reaction.
	
	Specifically, this function delegates processing of the colliding elements to
	specialised sub-functions, which operate on the affected elements or note
	events for post-collision-cycle processing as appropriate.
	
	@type args: sequence(5)
	@param args: A collection of elements::
	    - active visual effects
	    - all elements that do not need to be further addressed in this cycle
	    - all elements with ODE presence that need to be destroyed post-cycle
	    - all players killed during this collision-cycle, as tuples of killer
	        and victim
	    - all players whose shields have falled during this collision-cycle
	    - the player's ship
	@type geom_1: ode.GeomObject
	@param geom_1: The first GeomObject being processed.
	@type geom_2: ode.GeomObject
	@param geom_2: The second GeomObject being processed.
	"""
	(visual_effects, processed_elements, dead_elements, kills, fallen_shields, player) = args
	if geom_1.tank_chan == geom_2.tank_chan or [None for element in processed_elements if element == geom_1 or element == geom_2] or not ode.collide(geom_1, geom_2):
		return
		
	type_1 = type(geom_1.parent)
	type_2 = type(geom_2.parent)
	
	if type_1 == weapons.Bolt and not type_2 == weapons.Bolt: #Bolt-bolt collisions can be ignored.
		visual_effects += _collisionHandleBolt(geom_1, geom_2, type_2, processed_elements, dead_elements, kills, fallen_shields)
	elif type_2 == weapons.Bolt and not type_1 == weapons.Bolt: #Bolt-bolt collisions can be ignored.
		visual_effects += _collisionHandleBolt(geom_2, geom_1, type_1, processed_elements, dead_elements, kills, fallen_shields)
	elif type_1 == weapons.Missile:
		visual_effects += _collisionHandleMissile(geom_1, geom_2, type_2, processed_elements, dead_elements, kills, fallen_shields)
	elif type_2 == weapons.Missile:
		visual_effects += _collisionHandleMissile(geom_2, geom_1, type_1, processed_elements, dead_elements, kills, fallen_shields)
	elif type_1 == tankchan.TankChan and geom_1.parent.isAlive():
		visual_effects += _collisionHandleTankChan(geom_1, geom_2, type_2, kills, player)
	elif type_2 == tankchan.TankChan and geom_2.parent.isAlive():
		visual_effects += _collisionHandleTankChan(geom_2, geom_1, type_1, kills, player)
		
def _collisionHandleBolt(bolt_geom, obj_geom, obj_type, processed_elements, dead_elements, kills, fallen_shields):
	"""
	Handles collisions involving a bolt and another object.
	
	This includes collisions with ships, missiles, and obstacles.
	Collisions with bolts may be safely ignored.
	
	@type bolt_geom: ode.GeomObject
	@param bolt_geom: The GeomObject of the bolt.
	@type obj_geom: ode.GeomObject
	@param obj_geom: The GeomObject that the bolt collided with.
	@type obj_type: type
	@param obj_type: The type of object the bolt collided with.
	@type processed_elements: list
	@param processed_elements: A list of all elements that do not need to be
	    further addressed in this cycle.
	@type dead_elements: list
	@param dead_elements: A list of all elements with ODE presence that need to
	    be destroyed post-cycle.
	@type kills: list
	@param kills: A list of all players killed during this collision-cycle, as
	    tuples of killer and victim.
	@type fallen_shields: list
	@param fallen_shields: A list of all players whose shields have fallen during
	    this collision-cycle.
	
	@rtype: list
	@return: A list of all visual effects resulting from collisions involving
	    the bolt.
	"""
	processed_elements.append(bolt_geom)
	visual_effects = []
	bolt = bolt_geom.parent
	
	if obj_type == tankchan.TankChan:
		tank_chan = obj_geom.parent
		if tank_chan.isAlive():
			dead_elements.append((bolt, tank_chan, obj_geom.subsystem))
			if obj_geom.subsystem == tankchan.SUBSYSTEM_PROJECTION:
				if tank_chan.isShielded(): #Damage shields.
					if not tank_chan.damageShielding(bolt.getShieldDamage()):
						fallen_shields.append(tank_chan)
					visual_effects.append(effects.ShieldExplosion(bolt.getPosition()))
				else: #Distribute damage.
					if not tank_chan.damageHull(bolt.getHullDamage()):
						kills.append((bolt_geom.tank_chan, tank_chan))
						visual_effects.append(effects.ShipExplosion(tank_chan.getPosition()))
					else:
						visual_effects.append(effects.HullExplosion(bolt.getPosition()))
						subsystem_damage = bolt.getSubsystemDamage() / 4.0
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_SENSORS)
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_ENGINES)
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_SHIELDS)
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_WEAPONS)
			else:
				if not tank_chan.damageHull(bolt.getHullDamage()):
					kills.append((bolt_geom.tank_chan, tank_chan))
					visual_effects.append(effects.ShipExplosion(tank_chan.getPosition()))
				else:
					visual_effects.append(effects.HullExplosion(bolt.getPosition()))
					tank_chan.damageSubsystem(bolt.getSubsystemDamage(), obj_geom.subsystem)
	elif obj_type == weapons.Missile:
		missile = obj_geom.parent
		processed_elements.append(obj_geom)
		dead_elements.append((bolt, missile, 0))
		dead_elements.append((missile, bolt, 0))
		visual_effects.append(effects.MissileExplosion(missile.getPosition()))
	elif obj_geom.tank_chan is None:
		dead_elements.append((bolt, None, 0))
		visual_effects.append(effects.HullExplosion(bolt.getPosition()))
	return visual_effects
	
def _collisionHandleMissile(missile_geom, obj_geom, obj_type, processed_elements, dead_elements, kills, fallen_shields):
	"""
	Handles collisions involving a missile and another object.
	
	This includes collisions with ships, missiles, and obstacles.
	Collisions with bolts are filtered before this is called.
	
	@type missile_geom: ode.GeomObject
	@param missile_geom: The GeomObject of the missile.
	@type obj_geom: ode.GeomObject
	@param obj_geom: The GeomObject that the missile collided with.
	@type obj_type: type
	@param obj_type: The type of object the missile collided with.
	@type processed_elements: list
	@param processed_elements: A list of all elements that do not need to be
	    further addressed in this cycle.
	@type dead_elements: list
	@param dead_elements: A list of all elements with ODE presence that need to
	    be destroyed post-cycle.
	@type kills: list
	@param kills: A list of all players killed during this collision-cycle, as
	    tuples of killer and victim.
	@type fallen_shields: list
	@param fallen_shields: A list of all players whose shields have fallen during
	    this collision-cycle.
	
	@rtype: list
	@return: A list of all visual effects resulting from collisions involving
	    the missile.
	"""
	processed_elements.append(missile_geom)
	visual_effects = []
	missile = missile_geom.parent
	
	if obj_type == tankchan.TankChan:
		tank_chan = obj_geom.parent
		if tank_chan.isAlive():
			dead_elements.append((missile, tank_chan, obj_geom.subsystem))
			if obj_geom.subsystem == tankchan.SUBSYSTEM_PROJECTION:
				if tank_chan.isShielded(): #Damage shields.
					if not tank_chan.damageShielding(missile.getShieldDamage()):
						fallen_shields.append(tank_chan)
					visual_effects.append(effects.MissileExplosion(missile.getPosition()))
				else: #Distribute damage.
					if not tank_chan.damageHull(missile.getHullDamage()):
						kills.append((missile_geom.tank_chan, tank_chan))
						visual_effects.append(effects.ShipExplosion(tank_chan.getPosition()))
					else:
						visual_effects.append(effects.MissileExplosion(missile.getPosition()))
						subsystem_damage = missile.getSubsystemDamage() / 4.0
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_SENSORS)
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_ENGINES)
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_SHIELDS)
						tank_chan.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_WEAPONS)
			else:
				if not tank_chan.damageHull(missile.getHullDamage()):
					kills.append((missile_geom.tank_chan, tank_chan))
					visual_effects.append(effects.ShipExplosion(tank_chan.getPosition()))
				else:
					visual_effects.append(effects.MissileExplosion(missile.getPosition()))
					tank_chan.damageSubsystem(missile.getSubsystemDamage(), obj_geom.subsystem)
	elif obj_type == weapons.Missile:
		missile_2 = obj_geom.parent
		processed_elements.append(obj_geom)
		dead_elements.append((missile, missile_2, 0))
		dead_elements.append((missile_2, missile, 0))
		visual_effects.append(effects.MissileExplosion(missile.getPosition()))
	elif obj_type == weapons.ECM or obj_geom.tank_chan is None:
		dead_elements.append((missile, None, 0))
		visual_effects.append(effects.MissileExplosion(missile.getPosition()))
	return visual_effects
	
def _collisionHandleTankChan(tank_chan_geom, obj_geom, obj_type, kills, player):
	"""
	Handles collisions involving a ship and another object.
	
	This includes collisions with ships and obstacles.
	Collisions with bolts and missiles are filtered before this is called.
	
	@type tank_chan_geom: ode.GeomObject
	@param tank_chan_geom: The GeomObject of the part of the ship that collided.
	@type obj_geom: ode.GeomObject
	@param obj_geom: The GeomObject that the ship collided with.
	@type obj_type: type
	@param obj_type: The type of object the ship collided with.
	@type kills: list
	@param kills: A list of all players killed during this collision-cycle, as
	    tuples of killer and victim.
	@type player: L{tankchan.TankChan}
	@param player: The player's ship, used to filter out irrelevant collisions.
	
	@rtype: list
	@return: A list of all visual effects resulting from collisions involving
	    the ship.
	"""
	visual_effects = []
	tank_chan = tank_chan_geom.parent
	if tank_chan.isAlive():
		if obj_type == tankchan.TankChan:
			tank_chan_2 = obj_geom.parent
			if tank_chan == player or tank_chan_2 == player:
				if tank_chan_2.isAlive():
					kills.append((tank_chan, tank_chan_2))
					kills.append((tank_chan_2, tank_chan))
					tank_chan.explode()
					tank_chan_2.explode()
					visual_effects.append(effects.ShipExplosion(tank_chan.getPosition()))
					visual_effects.append(effects.ShipExplosion(tank_chan_2.getPosition()))
		elif tank_chan == player and obj_geom.tank_chan is None:
			kills.append((None, tank_chan))
			tank_chan.explode()
			visual_effects.append(effects.ShipExplosion(tank_chan.getPosition()))
	return visual_effects
	