# titans

TAKEOVERTYPE_CAPTURE = 1
TAKEOVERTYPE_NEUTRALIZE = 2

SCORE_CAPTURE = 2
SCORE_NEUTRALIZE = 1
SCORE_CAPTUREASSIST = 1
SCORE_NEUTRALIZEASSIST = 1
SCORE_DEFEND = 1
TITAN_SURVIVEPINDIST = 60
TITAN_INSIDEDIST = 100

Top = 0
Middle = 1
Bottom = 2

import host
import bf2
import gpm_cq
import math
from game.scoringCommon import addScore, RPL, getClosestTitan
from bf2 import g_debug

g_controlPoints = [] # cache, as this map won't change

def init():
	# events hook
	host.registerGameStatusHandler(onGameStatusChanged)
	if host.sgl_getIsAIGame() == 1:
		host.sh_setEnableCommander(1)
	else:
		host.sh_setEnableCommander(1)
		
	host.registerHandler('TimeLimitReached', onTimeLimitReached, 1)	

	if g_debug: print "gpm_ti.py initialized"
		
		
		
def deinit():
	bf2.triggerManager.destroyAllTriggers()
	global g_controlPoints
	g_controlPoints = []
	
	host.unregisterGameStatusHandler(onGameStatusChanged)
	
	host.unregisterHandler('ControlPointChangedOwner', onCPStatusChange)
	host.unregisterHandler('TicketLimitReached', onTicketLimitReached)
	host.unregisterHandler('TimeLimitReached', onTimeLimitReached, 1)	
	host.unregisterHandler('PlayerDeath', onPlayerDeath)
	host.unregisterHandler('PlayerKilled', onPlayerKilled)
	host.unregisterHandler('PlayerRevived', onPlayerRevived)
	host.unregisterHandler('PlayerSpawn', onPlayerSpawn)
	host.unregisterHandler('EnterVehicle', onEnterVehicle)
	host.unregisterHandler('ExitVehicle', onExitVehicle)
	host.unregisterHandler('VehicleDamage', onVehicleDamage)
	host.unregisterHandler('VehicleSpawn', onVehicleSpawn, 1)
	host.unregisterHandler('VehicleDestroyed', onVehicleDestroyed)
	host.unregisterHandler('VehicleRepair', onVehicleRepair)	
	
	if g_debug: print "gpm_ti.py uninitialized"



def onGameStatusChanged(status):

	global g_controlPoints		

	if status == bf2.GameStatus.Playing: 

		# add control point triggers
		g_controlPoints = bf2.objectManager.getObjectsOfType('dice.hfe.world.ObjectTemplate.ControlPoint')
		gpm_cq.g_controlPoints = g_controlPoints

		for obj in g_controlPoints:
			radius = float(obj.getTemplateProperty('radius'))
			isHemi = int(obj.cp_getParam('isHemisphere'))

			if isHemi != 0:
				id = bf2.triggerManager.createHemiSphericalTrigger(obj, onCPTrigger, '<<PCO>>', radius, (1, 2, 3))
			else:
				id = bf2.triggerManager.createRadiusTrigger(obj, onCPTrigger, '<<PCO>>', radius, (1, 2, 3))			

			obj.triggerId = id
			obj.lastAttackingTeam = 0

			if obj.cp_getParam('team') > 0:
				obj.flagPosition = gpm_cq.Top
			else:
				obj.flagPosition = gpm_cq.Bottom

		host.registerHandler('ControlPointChangedOwner', onCPStatusChange)

		# setup ticket system
		ticketsTeam1 = gpm_cq.calcStartTickets(bf2.gameLogic.getDefaultTickets(1))
		ticketsTeam2 = gpm_cq.calcStartTickets(bf2.gameLogic.getDefaultTickets(2))
		
		bf2.gameLogic.setTickets(1, ticketsTeam1)
		bf2.gameLogic.setTickets(2, ticketsTeam2)

		bf2.gameLogic.setTicketState(1, 0)
		bf2.gameLogic.setTicketState(2, 0)

		setTicketLimits()
		setupEventHandlers()
		updateTicketLoss()

		if g_debug: print "Titans gamemode initialized."
	else:
		bf2.triggerManager.destroyAllTriggers()
		g_controlPoints = []

def setTicketLimits():
	bf2.gameLogic.setTicketLimit(1, 1, 0)
	bf2.gameLogic.setTicketLimit(2, 1, 0)	
	#bf2.gameLogic.setTicketLimit(1, 3, 1)
	#bf2.gameLogic.setTicketLimit(2, 3, 1)
	bf2.gameLogic.setTicketLimit(1, 4, 12)
	bf2.gameLogic.setTicketLimit(2, 4, 12)
	bf2.gameLogic.setTicketLimit(1, 5, 24)
	bf2.gameLogic.setTicketLimit(2, 5, 24)
	bf2.gameLogic.setTicketLimit(1, 6, 36)
	bf2.gameLogic.setTicketLimit(2, 6, 36)
	bf2.gameLogic.setTicketLimit(1, 7, 48)
	bf2.gameLogic.setTicketLimit(2, 7, 48)
	bf2.gameLogic.setTicketLimit(1, 8, 59)
	bf2.gameLogic.setTicketLimit(2, 8, 59)
	bf2.gameLogic.setTicketLimit(1, 9, 60)
	bf2.gameLogic.setTicketLimit(2, 9, 60)
	bf2.gameLogic.setTicketLimit(1, 10, 62)
	bf2.gameLogic.setTicketLimit(2, 10, 62)
	bf2.gameLogic.setTicketLimit(1, 11, 70)
	bf2.gameLogic.setTicketLimit(2, 11, 70)
	bf2.gameLogic.setTicketLimit(1, 12, 80)
	bf2.gameLogic.setTicketLimit(2, 12, 80)
	bf2.gameLogic.setTicketLimit(1, 13, 90)
	bf2.gameLogic.setTicketLimit(2, 13, 90)
	bf2.gameLogic.setTicketLimit(1, 14, 99)
	bf2.gameLogic.setTicketLimit(2, 14, 99)

	host.registerHandler('TicketLimitReached', onTicketLimitReached)

def setupEventHandlers():
	# player events
	host.registerHandler('PlayerDeath', onPlayerDeath)
	host.registerHandler('PlayerKilled', onPlayerKilled)
	host.registerHandler('PlayerRevived', onPlayerRevived)
	host.registerHandler('PlayerSpawn', onPlayerSpawn)
	host.registerHandler('EnterVehicle', onEnterVehicle)
	host.registerHandler('ExitVehicle', onExitVehicle)

	# vehicle events
	host.registerHandler('VehicleDamage', onVehicleDamage)
	host.registerHandler('VehicleSpawn', onVehicleSpawn, 1)
	host.registerHandler('VehicleDestroyed', onVehicleDestroyed)
	host.registerHandler('VehicleRepair', onVehicleRepair)	

def debugTitanHitPoints(object):
	if g_debug: print "Total number of titan hitpoints for team: ", object.getTeam(), " = ", object.getDamage()

#Check if the object is a titan and if it's valid
def checkTitan(object):
	if object == None:
		return 0
		
	if object.isValid() and object.isTitan():
		if object.getTeam() != 0:
			return 1
	
	if object.isValid() and object.isTitan():
		if g_debug: print "Illegal team (", object.getTeam(), ") on titan: ", object.templateName
	elif object.isValid() and object.isTitan():
		if g_debug: print "Titan is not valid!"
		return 0

#Add or remove hitpoints from the total number of hitpoints and calculate the new number of tickets
def updateTitanHitPoints(object, hitPoints):
	newHealth = object.getDamage() - hitPoints
	if g_debug: print "Titan: ", object.templateName," of team: ", object.getTeam(), " newHealth: ", newHealth

	if (newHealth <= 0.0):
		ticketsTeam = 0.0;
	elif (newHealth <= 1.0):
		ticketsTeam = 1.0;
	else:
		ticketsTeam = 100.0*(newHealth/object.getMaxDamage())
		
	bf2.gameLogic.setTickets(object.getTeam(), int(math.ceil(ticketsTeam)))

#Update the total amount of hitpoints possible for ticket calculation
def updateTitanMaxHitPoints(team, hitPoints):	
	updateTicketLoss()

#Object is damaged
def onVehicleDamage(attacker, object, hitPoints):
	if checkTitan(object):
		if g_debug: print "Titan: ", object.templateName," of team: ", object.getTeam(), " damage: ", hitPoints
		updateTitanHitPoints(object, hitPoints)	
		
#Object has spawned
def onVehicleSpawn(object):
	if checkTitan(object):
		if g_debug: print "Titan: ", object.templateName," of team: ", object.getTeam(), " spawned"
		updateTitanMaxHitPoints(object.getTeam(), object.getMaxDamage())		
		updateTitanHitPoints(object, 0)
		
#Object has been destroyed
def onVehicleDestroyed(object, attacker):
	if checkTitan(object):
		if g_debug: print "Titan: ", object.templateName, " of team: ", object.getTeam(), " destroyed"
		updateTitanHitPoints(object, object.getDamage())	
		
		winner = 1
		if (object.getTeam() == 1):
			winner = 2 

		host.sgl_endGame(winner, 3, 25)		

		for p in bf2.playerManager.getPlayers():
			if p:
				closestTitan = getClosestTitan(p.getVehicle(), TITAN_INSIDEDIST)
				p.stats.aliveOnTitanDestruction = False

				if p.getTeam() == winner and p.isAlive() and closestTitan and closestTitan.getTeam() != p.getTeam():
					p.stats.aliveOnTitanDestruction = True
			
				if g_debug: print p.getName(), " was ", p.stats.aliveOnTitanDestruction, " alive when titan was destroyed"

#Object has been repaired
def onVehicleRepair(attacker, object, hitPoints):
	if checkTitan(object):
		if g_debug: print "Titan: ", object.templateName, " of team: ", object.getTeam(), " was repaired with ", hitPoints, " hitpoints!"
		updateTitanHitPoints(object, -hitPoints)
		
def calcStartTickets(mapDefaultTickets):
	return int(mapDefaultTickets * (bf2.serverSettings.getTicketRatio() / 100.0))	
	
	
def onTimeLimitReached(value):
	#Not enough players on the server - other timelimits not allowed in titans mode
	if (value != -1):
		host.sgl_endGame(0, 0, 0)
	

# update ticket system
def updateTicketLoss():
	ticketLossPerSecTeam1 = calcTicketLossForTeam(1, 2)
	ticketLossPerSecTeam2 = calcTicketLossForTeam(2, 1)

	if g_debug: print "Team 1 ticketloss: ", ticketLossPerSecTeam1
	if g_debug: print "Team 2 ticketloss: ", ticketLossPerSecTeam2

	bf2.gameLogic.setTicketChangePerSecond(1, -ticketLossPerSecTeam1)
	bf2.gameLogic.setTicketChangePerSecond(2, -ticketLossPerSecTeam2)
	
# actual ticket loss calculation function
def calcTicketLossForTeam(ownTeam, otherTeam):
	return 0


# called when a control point flag reached top or bottom
def onCPStatusChange(cp, top):
	playerId = -1
	takeoverType = -1
	newTeam = -1
	scoringTeam = -1
	
	if top:	cp.flagPosition = Top
	else:   cp.flagPosition = Bottom
	
	# determine capture / neutralize / defend
	if cp.cp_getParam('team') != 0:

		if top:
			# regained flag, do nothing
			pass
			
		else:
			# neutralize
			newTeam = 0
			if cp.cp_getParam('team') == 1:
				scoringTeam = 2
			else:
				scoringTeam = 1
				
			takeoverType = TAKEOVERTYPE_NEUTRALIZE

	else:

		if top:
			# capture
			newTeam = cp.cp_getParam('flag')
			scoringTeam = newTeam
			takeoverType = TAKEOVERTYPE_CAPTURE

		else:
			# hit bottom, but still neutral
			pass

	
	# scoring
	if takeoverType > 0:
		pcos = bf2.triggerManager.getObjects(cp.triggerId)
	
		# count number of players
		scoringPlayers = []
		firstPlayers = []
		for o in pcos:
			if not o or o.getParent() or checkTitan(o): continue

			occupyingPlayers = o.getOccupyingPlayers()
			for p in occupyingPlayers:
			
				# only count first player in a vehicle
				if p != occupyingPlayers[0]: 
					continue
					
				if p.isAlive() and not p.isManDown() and p.getTeam() == scoringTeam:
					if len(firstPlayers) == 0 or p.enterCpAt < firstPlayers[0].enterCpAt:
						firstPlayers = [p]
					elif p.enterCpAt == firstPlayers[0].enterCpAt:
						firstPlayers += [p]
					
					if not p in scoringPlayers:
						scoringPlayers += [p]
	
		# deal score
		for p in scoringPlayers:
			oldScore = p.score.score;
			if takeoverType == TAKEOVERTYPE_CAPTURE:
				if p in firstPlayers:
					p.score.cpCaptures += 1
					addScore(p, SCORE_CAPTURE, RPL)
					bf2.gameLogic.sendGameEvent(p, 12, 0) #12 = Conquest, 0 = Capture
					playerId = p.index
				else:
					p.score.cpAssists += 1
					addScore(p, SCORE_CAPTUREASSIST, RPL)
					bf2.gameLogic.sendGameEvent(p, 12, 2) #12 = Conquest, 2 = Assist


			elif takeoverType == TAKEOVERTYPE_NEUTRALIZE:
				if p in firstPlayers:
					p.score.cpNeutralizes += 1
					addScore(p, SCORE_NEUTRALIZE, RPL)
					bf2.gameLogic.sendGameEvent(p, 12, 3) #12 = Conquest, 3 = Neutralize
					playerId = p.index
				else:
					p.score.cpNeutralizeAssists += 1
					addScore(p, SCORE_NEUTRALIZEASSIST, RPL)
					bf2.gameLogic.sendGameEvent(p, 12, 4) #12 = Conquest, 4 = Neutralize assist
					
				

	# immediate ticket loss for opposite team
	# enemyTicketLossInstant = cp.cp_getParam('enemyTicketLossWhenCaptured')
	# if enemyTicketLossInstant > 0 and newTeam > 0:
		
	#	if newTeam == 1:
	#		punishedTeam = 2
	#	elif newTeam == 2:
	#		punishedTeam = 1
		
	#	tickets = bf2.gameLogic.getTickets(punishedTeam)
	#	tickets -= enemyTicketLossInstant
	#	bf2.gameLogic.setTickets(punishedTeam, tickets)
	
	
	# update control point	
	cp.cp_setParam('playerId', playerId) #always set player first
	if newTeam != -1 and cp.cp_getParam('team') != newTeam:
		cp.cp_setParam('team', newTeam)
		
	onCPTrigger(cp.triggerId, cp, None, None, None)
	updateTicketLoss()

				
				
def onPlayerDeath(victim, vehicle):		

	# punish team with one ticket
	if victim == None:
		return
	team = victim.getTeam()

	#	if g_sessionTitanMaxArmorMap[team] == 0:
	#		teamTickets = bf2.gameLogic.getTickets(team)
	#		teamTickets -= 1
	#		bf2.gameLogic.setTickets(team, teamTickets)

	# check if it was the last player
	foundLivingPlayer = False
	for p in bf2.playerManager.getPlayers():
		if p != victim and p.getTeam() == victim.getTeam() and p.isAlive():
			foundLivingPlayer = True
	
	if not foundLivingPlayer:
		updateTicketLoss()

# called when tickets reach a predetermined limit (negativ value means that the tickets have become less than the limit)
def onTicketLimitReached(team, limitId):
	if g_debug: print "limitReached ", limitId
	updateTicketWarningTi(team, limitId)
	
# called when the ticket state should be updated (for triggering messages and sounds based on tickets left)
def updateTicketWarningTi(team, limitId):
	oldTicketState = bf2.gameLogic.getTicketState(team)
	newTicketState = 0
	
	if (oldTicketState >= 20):
		newTicketState = 20		

	if (limitId == -2):
		newTicketState = 20
	
	elif (limitId == 2):
		newTicketState = 0		

	elif (limitId == -1):
		newTicketState += 1

	elif (limitId == -4):
		newTicketState += 2

	elif (limitId == -5):
		newTicketState += 3

	elif (limitId == -6):
		newTicketState += 4

	elif (limitId == -7):
		newTicketState += 5

	elif (limitId == -8):
		newTicketState += 6

	elif (limitId == -9):
		newTicketState += 7

	elif (limitId == -10):
		newTicketState += 8

	elif (limitId == -11):
		newTicketState += 9

	elif (limitId == -12):
		newTicketState += 10

	elif (limitId == -13):
		newTicketState += 11

	elif (limitId == -14):
		newTicketState += 12

	if (oldTicketState != newTicketState):
		bf2.gameLogic.setTicketState(team, newTicketState)


def updateTicketWarning(team, limitId):
	gpm_cq.updateTicketWarning(team, limitId)
	

def onCPTrigger(triggerId, cp, vehicle, enter, userData):
	if not vehicle or not checkTitan(bf2.objectManager.getRootParent(vehicle)):
		onCPTriggerCQ(triggerId, cp, vehicle, enter, userData)
	else:
		if g_debug: print "Object was a titan - cannot take CP!"
	

# called when someone enters or exits cp radius
def onCPTriggerCQ(triggerId, cp, vehicle, enter, userData):
	if not cp.isValid(): return
	
	if vehicle and vehicle.getParent(): return
	
	# can this cp be captured at all?
	if cp.cp_getParam('unableToChangeTeam') != 0:
		return					
		
	playersInVehicle = None	
	if vehicle:
		playersInVehicle = vehicle.getOccupyingPlayers()
	
	if enter:
		for p in playersInVehicle:
			tempCp = gpm_cq.getOccupyingCP(p)
			if tempCp != None:
				if not p.getIsInsideCP():
					if g_debug: print "Resetting enterPctAt for player ", p.getName()
					p.enterCpAt = host.timer_getWallTime()
	
	if vehicle:	
		for p in playersInVehicle:
			# only count first player in a vehicle
			if p == playersInVehicle[0]:  
				if g_debug: print p.index, " is in radius. v=", vehicle.templateName
				tempCp = gpm_cq.getOccupyingCP(p)

				if (tempCp != None and p.isAlive() and not p.isManDown()):
					p.setIsInsideCP(1)	
				else:
					p.setIsInsideCP(0)	
			else:
				p.setIsInsideCP(0)
				if enter == 1:
					bf2.gameLogic.sendHudEvent(p, 71, 49) #71 = HEEnableHelpMessage, 49 = VHMExitToCaptureFlag;


	# count people in radius
	team1Occupants = 0
	team2Occupants = 0

	pcos = bf2.triggerManager.getObjects(cp.triggerId)
	for o in pcos:
		if not o or checkTitan(o): continue # you can get None in the result tuple when the host can't figure out what object left the trigger or if it's a titan
		if o.getParent(): continue # getOccupyingPlayers returns all players downwards in the hierarchy, so dont count them twice
		occupyingPlayers = o.getOccupyingPlayers()
		for p in occupyingPlayers:
			
			# only count first player in a vehicle
			if p != occupyingPlayers[0]: 
				continue
				
			if p.isAlive() and not p.isManDown():
				
				if not p.killed:
					if g_debug: print "Player ", p.getName(), " is inside radius"

					if p.getTeam() == 1:
						team1Occupants += 1
					elif p.getTeam() == 2:
						team2Occupants += 1


	# determine who is taking control
	team1OverWeight = team1Occupants - team2Occupants
	attackOverWeight = 0

	if team1OverWeight > 0:
		attackingTeam = 1
	elif team1OverWeight < 0:
		attackingTeam = 2
	else:
		attackingTeam = 0

	
	if team1Occupants == 0 and team2Occupants == 0:

		# nobody here, slowly go back to owning team
		if cp.cp_getParam('team') == 0:
			attackOverWeight = -0.5
		else:
			attackOverWeight = 0.5
			
		timeToChangeControl = cp.cp_getParam('timeToLoseControl')

	else:

		# raise flag if already ours, or at bottom and neutral. Otherwise lower first.
		if cp.cp_getParam('flag') == attackingTeam or (cp.flagPosition == Bottom and cp.cp_getParam('team') == 0):

			# our flag, raise
			attackOverWeight = abs(team1OverWeight)
			timeToChangeControl = cp.cp_getParam('timeToGetControl')
		else:

			# other team raised flag, lower first
			attackOverWeight = - abs(team1OverWeight)
			timeToChangeControl = cp.cp_getParam('timeToLoseControl')


	if cp.cp_getParam('onlyTakeableByTeam') != 0 and cp.cp_getParam('onlyTakeableByTeam') != attackingTeam:
		return


	# flag can only be changed when at bottom
	if cp.flagPosition == Bottom:
		cp.cp_setParam('flag', attackingTeam)


	# calculate flag raising/lowering speed
	if timeToChangeControl > 0:
		takeOverChangePerSecond = 1.0 * attackOverWeight / timeToChangeControl
	else:
		takeOverChangePerSecond = 0.0

	if (cp.flagPosition == Top and takeOverChangePerSecond > 0) or (cp.flagPosition == Bottom and takeOverChangePerSecond < 0):
		takeOverChangePerSecond = 0.0

	if abs(takeOverChangePerSecond) > 0:
		cp.flagPosition = Middle
				
	cp.cp_setParam('takeOverChangePerSecond', takeOverChangePerSecond)



def onPlayerKilled(victim, attacker, weapon, assists, object):
	gpm_cq.onPlayerKilledCQ(victim, attacker, weapon, assists, object)
			
			
def onPlayerRevived(victim, attacker):
	gpm_cq.onPlayerRevived(victim, attacker)
	
	
def onPlayerSpawn(player, soldier):
	gpm_cq.onPlayerSpawn(player, soldier)	
	
	
def onEnterVehicle(player, vehicle, freeSoldier = False):
	if player == None:
		return

	if vehicle and vehicle.templateName == "parachute" and player and player.isAlive() and player.stats.aliveOnTitanDestruction:
		closestTitan = getClosestTitan(player.getVehicle(), TITAN_SURVIVEPINDIST)

		if not closestTitan:
			if g_debug: print player.getName(), " survived titan explosion"
			player.stats.titanSurvives = 1
			player.stats.aliveOnTitanDestruction = False

	if not checkTitan(bf2.objectManager.getRootParent(vehicle)):
		gpm_cq.onEnterVehicle(player, vehicle, freeSoldier)
	else:
		if g_debug: print "Player ", player.getName(), "entered a titan - don't update CP!"

def onExitVehicle(player, vehicle):
	if not checkTitan(vehicle):
		gpm_cq.onExitVehicle(player, bf2.objectManager.getRootParent(vehicle))
	else:
		if g_debug: print "Player ", player.getName(), "exited a titan - don't update CP!"
	
