#********************************************************************************************************
# Name: Rally Racing for Battlefield 2142
# Author: Mach10 <mach10@comcast.net>
# Version 5.0 
# Copyright  MNM Creations, LLC  - www.mnmcreationsllc.com
#----------------------------------------------------------------------------------------------------------------------------
# Conditions and Limitations: You may use this file locally, on listening and dedicated servers. You MAY NOT 
# copy, redistribute, or post this file with out the expressed permission from the author/copyright holder.
# You may contact me and request these rights via e-mail: mach10@comcast.net
#This software is provided "as is". In no event shall the author, authors or contributors be held
#liable for  any direct, indirect, incidental, special, exemplary, or consequential damages (including)
#but not limited to, procurement of substitute good or services; loss of use, data or profits; or 
#business interruption) however caused and on any theory of liability, whether in contract, strict liability
#arising in any way out of the use of this software, even if advised of the possibility of such damage.
#=====
# Purpose: The file creates a new game mode for Battlefield 2142, Rally Racing.
#=====
# Game Mode Information: You must spawn on the Coalition team, and take a DPV from your non-cap to the other
# team's non-cap and back again. The player who completes the number of laps below will win.
# Change Log v1.0:
# -
#*********************************************************************************************************

class Rally_Game:
	def __init__(self):
		self.number_of_laps = 3
		self.firstPlace = None
		self.secondPlace = None
		self.thirdPlace = None
		self.displayWinnersTimer = None
		self.displayWinnersTimerIndex = 0
		self.numberOfPlayers = 0
		self.raceStartTime = 0
		self.raceStarted = None
		self.raceStartTimer = None
		self.totalCpNumber = 0
		self.startEngines = None
		self.redLight = None
		self.yellowLight1 = None
		self.yellowLight2 = None
		self.yellowLight3 = None
		self.yellowLight4 = None
		self.greenLight = None
		self.goGreen = None
		
		
Rally = Rally_Game()

import host
import bf2
import math
from game.scoringCommon import addScore, RPL
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(0)
	else:
		host.sh_setEnableCommander(0)
		
	if g_debug: print "gpm_rally.py initialized"
		
def deinit():
	bf2.triggerManager.destroyAllTriggers()
	global g_controlPoints
	g_controlPoints = []
	bf2.triggerManager.destroyAllTriggers()
	
	host.unregisterGameStatusHandler(onGameStatusChanged)
	host.unregisterHandler('PlayerSpawn', onPlayerSpawn)
	host.unregisterHandler('EnterVehicle', onEnterVehicle)
	host.unregisterHandler('ExitVehicle', onExitVehicle)
	
	if g_debug: print "gpm_rally.py uninitialized"

def onGameStatusChanged(status):

	global g_controlPoints
	levelRestart()
	if status == bf2.GameStatus.Playing:
		g_controlPoints = bf2.objectManager.getObjectsOfType('dice.hfe.world.ObjectTemplate.ControlPoint')
		for obj in g_controlPoints:
			radius = float(obj.getTemplateProperty('radius'))
			if obj.cp_getParam('team') == 1: #1 is the racing team, change if needed
				id = bf2.triggerManager.createRadiusTrigger(obj, RallyTrigger, '<<PCO>>', radius, (1, 2, 3))
				obj.triggerId = id
				obj.lastAttackingTeam = 0
				Rally.totalCpNumber += 1
			
		bf2.gameLogic.setTickets(1, 1)
		bf2.gameLogic.setTickets(2, 1)
		
		bf2.gameLogic.setTicketState(1, 0)
		bf2.gameLogic.setTicketState(2, 0)

		host.registerHandler('PlayerSpawn', onPlayerSpawn)
		host.registerHandler('EnterVehicle', onEnterVehicle)
		host.registerHandler('ExitVehicle', onExitVehicle)
		
		if g_debug: print "Rally gamemode initialized."

	else:
		bf2.triggerManager.destroyAllTriggers()
		g_controlPoints = []
	
DOWNWARDS = -1
UPWARDS = 1	
		

def RallyTrigger(triggerId, cp, vehicle, enter, userData):
	pcos = bf2.triggerManager.getObjects(cp.triggerId)
	cpId = int(cp.getTemplateProperty('ControlPointID'))
	for o in pcos:
		if not o: continue 
		if o.getParent(): continue
		occupyingPlayers = o.getOccupyingPlayers()
		for p in occupyingPlayers:
			if p != occupyingPlayers[0]: 
				continue
				
			if p.isAlive() and not p.isManDown():
				if not p.killed:
					if p.getDefaultVehicle() != p.getVehicle():
						if p.getTeam() == 2:
							return
						
						if cpId == 1:
							if p.lastCP == 0: #execute start trigger
								p.lastCP = 1
								p.startTime = float("%.2f" % host.timer_getWallTime())
								
							elif p.lastCP == Rally.totalCpNumber: #execute for start/finish trigger
								p.lastCP = 1
								p.lapsCompleted += 1
								p.score.cpCaptures += 1
								addScore(p, 1, RPL)
								p.lapTime = float("%.2f" % (host.timer_getWallTime() - p.startTime))
								previousSplit = p.splitTime
								p.splitTime = float("%.2f" % host.timer_getWallTime())
								splitPrint = float("%.2f" %(p.splitTime - previousSplit))
								host.rcon_invoke("game.sayall \"" + p.getName() + " Lap|Split " + str(p.lapTime) + " | " + str(splitPrint) + "\"")
								p.startTime = float("%.2f" % host.timer_getWallTime())
								if p.lapsCompleted == Rally.number_of_laps: #a passed Checkpoint = 1 cpCapture, 1 lap = passing all checkPoints, known as len(g_controlPoints) which equals the length of the array
									if checkWinners(p) == 1:
										Rally.displayWinnersTimer = bf2.Timer(displayWinners, 0, 1)
										Rally.displayWinnersTimer.setRecurring(1.0)

						else:
							if cpId == p.lastCP + 1: #execute checkpoint trigger
								p.lastCP += 1
								p.score.cpCaptures += 1
								addScore(p, 1, RPL)
								if not p.splitTime:
									p.splitTime = float("%.2f" % host.timer_getWallTime())
									splitPrint = float("%.2f" %(p.splitTime - p.startTime))
									host.rcon_invoke("game.sayall \"" + p.getName() + " Split: " + str(splitPrint) + "\"")
								else:
									previousSplit = p.splitTime
									p.splitTime = float("%.2f" % host.timer_getWallTime())
									splitPrint = float("%.2f" %(p.splitTime - previousSplit))
									lapPrint = float("%.2f" % (p.splitTime - p.startTime))
									host.rcon_invoke("game.sayall \"" + p.getName() + " Split Checkpoint|Lap: " + str(splitPrint) + " | " + str(lapPrint) + "\"")

def displayWinners(data):
	if not Rally.secondPlace:
		Rally.secondPlace = 'N/A'
	if not Rally.thirdPlace:
		Rally.thirdPlace = 'N/A'
	host.rcon_invoke("game.sayAll \"" + "First Place " + Rally.firstPlace + "\"")
	host.rcon_invoke("game.sayAll \"" + "Second Place " + Rally.secondPlace + "\"")
	host.rcon_invoke("game.sayAll \"" + "Third Place " + Rally.thirdPlace + "\"")
	Rally.displayWinnersTimerIndex += 1
	if Rally.displayWinnersTimerIndex >= 5:
		CleanUp()

	
def CleanUp():
	try:
		Rally.displayWinnersTimer.destroy()
		Rally.displayWinnersTimer = None
	except: pass
	
	Rally.firstPlace = None
	Rally.secondPlace = None
	Rally.thirdPlace = None
	Rally.displayWinnersTimerIndex = 0
	Rally.numberOfPlayers = 0
	Rally.raceStarted = 0
	Rally.raceStartTime = 0
	clearOldTimerInfo()
	timeLimitReached()
	
def levelRestart():
	try:
		Rally.displayWinnersTimer.destroy()
		Rally.displayWinnersTimer = None
	except: pass
	
	Rally.firstPlace = None
	Rally.secondPlace = None
	Rally.thirdPlace = None
	Rally.displayWinnersTimerIndex = 0
	Rally.numberOfPlayers = 0
	Rally.raceStarted = 0
	Rally.raceStartTime = 0
	Rally.totalCpNumber = 0
	clearOldTimerInfo()
	

def clearOldTimerInfo():
	for player in bf2.playerManager.getPlayers():
		player.killed = False
		player.lapTime = None
		player.splitTime = None
		player.startTime = None
		player.lastCP = 0
		player.lapsCompleted = 0
		

def checkWinners(player):
	numberOfPlayers = bf2.playerManager.getNumberOfPlayersInTeam(1)
	
	
	if numberOfPlayers == 1:
		if onePlayerRally(player) == 1:
			return 1
		
	elif numberOfPlayers == 2:
		if twoPlayerRally(player) == 1:
			return 1
		
	elif numberOfPlayers >= 3:
		if threePlayerRally(player) == 1:
			return 1
		
def onePlayerRally(player):
	if not Rally.firstPlace:
		Rally.firstPlace = player.getName()
		return 1

def twoPlayerRally(player):
	if not Rally.firstPlace:
		Rally.firstPlace = player.getName()

	elif Rally.firstPlace is not player.getName():
		Rally.secondPlace = player.getName()
		return 1

def threePlayerRally(player):
	if not Rally.firstPlace:
		Rally.firstPlace = player.getName()

	elif not Rally.secondPlace and Rally.firstPlace is not player.getName():
		Rally.secondPlace = player.getName()
		
	elif not Rally.thirdPlace and Rally.firstPlace is not player.getName() and Rally.secondPlace is not player.getName():
		Rally.thirdPlace = player.getName()
		return 1


def onPlayerSpawn(player, soldier):
	try:
		temp = player.killed
	except:
		player.killed = False
		player.lapTime = None
		player.splitTime = None
		player.startTime = None
		player.splitTime1 = None
		player.splitTime2 = None
		player.lastCP = 0
		player.lapsCompleted = 0
		
	if not Rally.raceStarted:
		Rally.raceStarted = 1
		Rally.raceStartTime = 30
		Rally.raceStartTimer = bf2.Timer(startRace, 0, 1)
		Rally.raceStartTimer.setRecurring(1.0)
		getRallyObjects()
		
def startRace(data):
	Rally.raceStartTime -= 1
	
	if Rally.raceStartTime == 25:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 25 seconds!"  + "\"")
		
	elif Rally.raceStartTime == 20:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 20 seconds!"  + "\"")
		
	elif Rally.raceStartTime == 15:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 15 seconds!"  + "\"")
		
		playSound(Rally.startEngines)
	elif Rally.raceStartTime == 10:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 10 seconds!"  + "\"")
		
	elif Rally.raceStartTime == 5:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 5..."  + "\"")
		try:
			Rally.redLight.setDamage(-1)
		except: pass
		
	elif Rally.raceStartTime == 4:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 4..."  + "\"")
		try:
			Rally.yellowLight1.setDamage(-1)
		except: pass
		
		
	elif Rally.raceStartTime == 3:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 3..."  + "\"")
		try:
			Rally.yellowLight2.setDamage(-1)
		except:  pass
		
		
	elif Rally.raceStartTime == 2:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 2..."  + "\"")
		try:
			Rally.yellowLight3.setDamage(-1)
		except: pass
		
		
	elif Rally.raceStartTime == 1:
		host.rcon_invoke("game.sayAll \"" + "Race will begin in 1..."  + "\"")
		try:
			Rally.yellowLight4.setDamage(-1)
		except: pass
		
	elif Rally.raceStartTime == 0:
		host.rcon_invoke("game.sayAll \"" + "Race!!"  + "\"")
		playSound(Rally.goGreen)
		
		resetPlayerTimes()
		Rally.greenLight.blinkTimer = bf2.Timer(blinkLightTimer, 0.2, 1, Rally.greenLight)	
		
		Rally.raceStartTimer.destroy()
		Rally.raceStartTimer = None
	
def onEnterVehicle(player, vehicle, freeSoldier = False):
	updateCaptureStatus(vehicle)

def onExitVehicle(player, vehicle):
	cp = getOccupyingCP(player)
	player.setIsInsideCP(cp != None and cp.cp_getParam('unableToChangeTeam') == 0)
	updateCaptureStatus(vehicle)
		
def timeLimitReached():
	host.sgl_endGame(1, 3, 0)
	
		
def updateCaptureStatus(vehicle):	
	rootVehicle = bf2.objectManager.getRootParent(vehicle)
	playersInVehicle = rootVehicle.getOccupyingPlayers()
	for p in playersInVehicle:
		cp = getOccupyingCP(p)
		p.setIsInsideCP(cp != None and cp.cp_getParam('unableToChangeTeam') == 0 and p == playersInVehicle[0])
	
def getOccupyingCP(player):
	vehicle = player.getVehicle()
	playerPos = vehicle.getPosition()
	
	closestCP = None
	if len(g_controlPoints) == 0: return None
	for obj in g_controlPoints:
		distanceTo = getVectorDistance(playerPos, obj.getPosition())
		if closestCP == None or distanceTo < closestCPdist:
			closestCP = obj
			closestCPdist = distanceTo
	
	pcos = bf2.triggerManager.getObjects(closestCP.triggerId)
	for o in pcos:
		if o == player.getDefaultVehicle():
			return closestCP
		else:
			for p in o.getOccupyingPlayers():
				if p == player:
					return closestCP
	
	return None

def getVectorDistance(pos1, pos2):
	diffVec = [0.0, 0.0, 0.0]
	diffVec[0] = math.fabs(pos1[0] - pos2[0])
	diffVec[1] = math.fabs(pos1[1] - pos2[1])
	diffVec[2] = math.fabs(pos1[2] - pos2[2])
	 
	return math.sqrt(diffVec[0] * diffVec[0] + diffVec[1] * diffVec[1] + diffVec[2] * diffVec[2])

def playSound(obj):
	try:
		obj.setDamage(-1)
	except: pass
	
	obj.timer = bf2.Timer(repairSound, 0.1, 1, obj)
	
	
def repairSound(obj):
	try:
		obj.setDamage(50000)
		
	except: pass
	
	obj.timer.destroy()
	obj.timer = None
	
def blinkLightTimer(obj):
	obj.setDamage(-1)
	obj.blinked = 0
	obj.timer = bf2.Timer(repairLight, 0.2, 1, obj)

	
def repairLight(obj):
	try:
		obj.setDamage(50000)
		
	except: pass
	if obj.blinked > 10:
		obj.blinkTimer.destroy()
		obj.blinkTimer = None
		
	else:
		obj.destroyTimer = bf2.Timer(destroyLight, 0.2, 1, obj)

	obj.timer.destroy()
	obj.timer = None

	
def destroyLight(obj):
	obj.blinked += 1
	obj.setDamage(-1)
	obj.destroyTimer.destroy()
	obj.destroyTimer = None
	obj.timer = bf2.Timer(repairLight, 0.2, 1, obj)
	
	
def getRallyObjects():
	pcos = bf2.objectManager.getObjectsOfType('dice.hfe.world.ObjectTemplate.PlayerControlObject')
	for o in pcos:
		if o.templateName == "start_engines":
			Rally.startEngines = o
			
		elif o.templateName == "red_light":
			Rally.redLight = o
			
		elif o.templateName == "yellow_light1":
			Rally.yellowLight1 = o
			
		elif o.templateName == "yellow_light2":
			Rally.yellowLight2 = o
			
		elif o.templateName == "yellow_light3":
			Rally.yellowLight3 = o
			
		elif o.templateName == "yellow_light4":
			Rally.yellowLight4 = o
			
		elif o.templateName == "green_light":
			Rally.greenLight = o
			
		elif o.templateName == "go_green":
			Rally.goGreen = o

def resetPlayerTimes():
	racers = bf2.playerManager.getPlayers()
	for p in racers:
		if p.getTeam() == 1:
			p.startTime = float("%.2f" % host.timer_getWallTime())
