5166 lines
148 KiB
Python
5166 lines
148 KiB
Python
# THIS MODULE IS TO REPLACE THE OLD Car_controll.py MODULE
|
|
# WHICH ENDED TOO LARGE AND TOO HARD TO MAINTAIN.
|
|
|
|
import os
|
|
import bge
|
|
import time
|
|
import math
|
|
import json
|
|
import bpy
|
|
import random
|
|
import numpy
|
|
import aud
|
|
import mathutils
|
|
import traceback
|
|
|
|
|
|
from Scripts import Reuse
|
|
from Scripts import Destruction
|
|
from Scripts import Opt
|
|
from Scripts import Map
|
|
from Scripts import Script
|
|
from Scripts import Tools
|
|
from Scripts import Money
|
|
from Scripts import Garage
|
|
from Scripts import Mouse
|
|
from Scripts import Settings
|
|
from Scripts import Music
|
|
from Scripts import Racing
|
|
from Scripts import Input
|
|
from Scripts import GameTime
|
|
|
|
from Scripts import Multiplayer_Shared
|
|
from Scripts import Multiplayer_Client
|
|
|
|
from Scripts import Character
|
|
|
|
from Scripts.Common import *
|
|
|
|
controls = {
|
|
|
|
"forward" :keycodes["UpArrow"],
|
|
"backward":keycodes["DownArrow"],
|
|
"left" :keycodes["LeftArrow"],
|
|
"right" :keycodes["RightArrow"],
|
|
"drift" :keycodes["LCtrl"],
|
|
"nitro" :keycodes["Z"],
|
|
"resque" :keycodes["BackSpace"],
|
|
"gearup" :keycodes["W"],
|
|
"geardown":keycodes["S"],
|
|
"autogear":keycodes["Q"],
|
|
"dynamcam":keycodes["C"],
|
|
"horn" :keycodes["H"],
|
|
|
|
# Truck
|
|
"unload" :keycodes["D"]
|
|
}
|
|
|
|
if "veh_controls" not in bge.logic.globalDict:
|
|
bge.logic.globalDict["veh_controls"] = controls
|
|
controls = bge.logic.globalDict["veh_controls"]
|
|
|
|
# Data about various cars in the game
|
|
|
|
def LoadCarData():
|
|
|
|
# Loading Json animation data files.
|
|
|
|
folder = bge.logic.expandPath("//cardata/")
|
|
|
|
if "carData" not in bge.logic.globalDict:
|
|
|
|
carData = {}
|
|
|
|
for f in os.listdir(folder):
|
|
if f.endswith(".json"):
|
|
print("Reading", f, "...")
|
|
with open(folder+f) as fl:
|
|
data = json.load(fl)
|
|
|
|
name = f.replace(".json", "")
|
|
|
|
carData[name] = data
|
|
bge.logic.globalDict["carData"] = carData
|
|
|
|
return bge.logic.globalDict["carData"]
|
|
|
|
carData = LoadCarData()
|
|
|
|
def AssingProbablity(carData):
|
|
|
|
if "NPC_cars" not in bge.logic.globalDict:
|
|
|
|
NPC_cars = list(carData.keys())
|
|
NPC_type_cars = {}
|
|
|
|
for carname in carData:
|
|
car = carData[carname]
|
|
t = car.get("type", "race-car")
|
|
if t not in NPC_type_cars:
|
|
NPC_type_cars[t] = []
|
|
NPC_type_cars[t].append(carname)
|
|
|
|
npccp = []
|
|
for carname in carData:
|
|
car = carData[carname]
|
|
npccp.append(car.get("probability",1))
|
|
NPC_cars_probability = []
|
|
for i in npccp:
|
|
NPC_cars_probability.append( 1 / sum(npccp) * i)
|
|
bge.logic.globalDict["NPC_cars"] = NPC_cars
|
|
bge.logic.globalDict["NPC_type_cars"] = NPC_type_cars
|
|
bge.logic.globalDict["NPC_cars_probability"] = NPC_cars_probability
|
|
|
|
return bge.logic.globalDict["NPC_cars"], bge.logic.globalDict["NPC_type_cars"], bge.logic.globalDict["NPC_cars_probability"]
|
|
|
|
NPC_cars, NPC_type_cars, NPC_cars_probability = AssingProbablity(carData)
|
|
|
|
# Position of the garage fix point.
|
|
fixGaragePosition = [-754.61, -1043.9, 409.32]
|
|
|
|
def UpdateCar(car):
|
|
|
|
# This function runs on every frame for each car,
|
|
# making the car interactable in the game.
|
|
|
|
|
|
# Clear the car and get the scene
|
|
scene, car = GetSceneAndCar(car)
|
|
dani = scene.objects["Dani_Box"]
|
|
|
|
|
|
# Mapping all cars on the minimap ( for debuging )
|
|
# Map.Show(car, color=GetColor(car))
|
|
|
|
# Performace optimization method.
|
|
if car.get("racing") or car.get("active") or InView(car):
|
|
|
|
if not car.get("inview"):
|
|
if not car.get("isCargo"):
|
|
car.restorePhysics()
|
|
car["inview"] = True
|
|
|
|
|
|
# Make sure this car is not deleted when we see it
|
|
if CloseToCamera(car, 200):
|
|
if car in Reuse.reuse.get(car.name, []):
|
|
Reuse.reuse[car.name].remove(car)
|
|
if car.get("npc") and not car.get("driver"):
|
|
AddDriver(car)
|
|
print(consoleForm(car),clr["bold"]+clr["tdyl"],"Removed from Reuse.", clr["norm"])
|
|
car["nodespawn"] = 100
|
|
if car not in bge.logic.globalDict["allcars"]:
|
|
bge.logic.globalDict["allcars"].append(car)
|
|
print(consoleForm(car),clr["bold"]+clr["tdyl"],"Restored.", clr["norm"])
|
|
car["nodespawn"] = 100
|
|
|
|
# Updating the car physics parameters
|
|
PhysicsUpdate(car)
|
|
|
|
|
|
|
|
# Truck updates
|
|
if "Truck" in str(car):
|
|
UpdateCargo(car)
|
|
|
|
# Car Control
|
|
if car.get("active"):
|
|
UserControl(car)
|
|
|
|
|
|
elif car.get("npc") == "npc":
|
|
NormalNPC(car)
|
|
|
|
elif car.get("npc") == "pursuit":
|
|
AngryPursuit(car)
|
|
|
|
elif car.get("npc") == "story":
|
|
pass # If it is controlled from story
|
|
|
|
|
|
elif car.get("racing"):
|
|
RacingAI(car)
|
|
|
|
|
|
|
|
elif NetworkControlled(car):
|
|
pass
|
|
|
|
# If nobody controls the car.
|
|
else:
|
|
IdleBraking(car)
|
|
|
|
# Network control
|
|
|
|
if NetworkControlled(car):
|
|
NetworkControl(car)
|
|
|
|
|
|
# Apply Turn
|
|
ApplyTurn(car)
|
|
|
|
# Automatic Transmission
|
|
if car.get("autogear", True):
|
|
AutomaticTransmission(car)
|
|
|
|
# Forces ( Drag Force, Down Force )
|
|
x = 0
|
|
y = DragForce(car)
|
|
z = -DownForce(car)
|
|
car.applyForce([x, y, 0], True)
|
|
car.applyForce([0, 0, z], True)
|
|
|
|
#if not car.get("underwater"):
|
|
# car.applyTorque([-z/2*(car.localLinearVelocity[1] < 0)
|
|
# , 0,0], True)
|
|
|
|
if car.get("handbraking"):
|
|
HandBraking(car)
|
|
|
|
# Give car the sound
|
|
EngineSound(car)
|
|
DriftSound(car)
|
|
|
|
# Smoke and fire
|
|
SmokeAndFire(car)
|
|
#GroundPuffsOfSmoke(car)
|
|
|
|
# Manual fixing.
|
|
# Moved to Tools.py
|
|
|
|
# Making so police pusuits you for speeding.
|
|
v = -car.localLinearVelocity.y
|
|
if v > 60 and bge.logic.globalDict["LODchunks"]["TheCity"]["now"] == "high":
|
|
PolicePursuit(car)
|
|
|
|
|
|
# Explosion timer
|
|
if car.get("explosion-timer"):
|
|
car["explosion-timer"] -= 1
|
|
if car["explosion-timer"] <= 0:
|
|
BlowUp(car)
|
|
|
|
|
|
else:
|
|
if car.get("inview", True):
|
|
car.suspendPhysics()
|
|
car["inview"] = False
|
|
|
|
if car.get("nodespawn", 0): car["nodespawn"] -= 1
|
|
|
|
def GetSceneAndCar(car):
|
|
|
|
# This module parses the inputs of other modules.
|
|
# And gives back bge.scene and the car object.
|
|
|
|
# We don't know if what we get here is the controller
|
|
# where the script is running, or the object itself.
|
|
|
|
if type(car) == bge.types.SCA_PythonController:
|
|
car = car.owner
|
|
|
|
scene = bge.logic.getCurrentScene()
|
|
|
|
return scene, car
|
|
|
|
def PhysicsUpdate(car):
|
|
|
|
# Updating the car physics to
|
|
# work with a more realistic
|
|
# effect, while cheating at some
|
|
# things.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
toFPS = Opt.ToFPS()
|
|
|
|
# If car has no physics yet, give it physics
|
|
if not getCarPhysics(car):
|
|
InitCarPhysics(car)
|
|
|
|
# Doors Swining
|
|
GetPhysicsForces(car)
|
|
SwingDoors(car)
|
|
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
|
|
wheelObject = car["wheels"][n]
|
|
health = wheelObject["health"] ** 2
|
|
|
|
# Damping
|
|
suspension = wheelObject["suspension"] * health
|
|
k = car["specs"]["suspentionDamping"]
|
|
suspensionDamping = 2 * k * math.sqrt(suspension)
|
|
|
|
hbf = 1.0
|
|
if car.get("handbraking"):
|
|
if not wheel.get("front"): hbf = 0.5
|
|
else: hbf = 0.6
|
|
|
|
getCarPhysics(car).setSuspensionStiffness(suspension, n)
|
|
getCarPhysics(car).setSuspensionDamping(suspensionDamping, n)
|
|
getCarPhysics(car).setTyreFriction(car["specs"]["grip"]*math.sqrt(car.mass)*hbf, n)
|
|
getCarPhysics(car).setRollInfluence(car["specs"]["roll"], n)
|
|
|
|
# Applying the engine acceleration
|
|
# to the wheels.
|
|
ApplyEngine(car)
|
|
|
|
# Making sure to clear the acceleration
|
|
# for the next frame.
|
|
|
|
# if not car.get("braking"):
|
|
# car["driftturn"] = 0.0
|
|
car["accelerating"] = False
|
|
car["braking"] = False
|
|
|
|
def getCarPhysics(car):
|
|
|
|
# Getting car physics. Car physics
|
|
# is referring to the Vehicle Constraint
|
|
# from the bullet physics. Not the rigid
|
|
# body of the main part of the car.
|
|
|
|
try:
|
|
return bge.constraints.getVehicleConstraint(car["VehicleConstraintId"])
|
|
except:
|
|
return None
|
|
|
|
def InitCarPhysics(car):
|
|
|
|
# This part of code runs once per car. It generates the
|
|
# Bullet Physics Vehicle Constraint setup.
|
|
|
|
# Clear the car and get the scene
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
# Get the data about this car
|
|
car["specs"] = GetCarSpecs(car.get("carData", car)).copy()
|
|
|
|
# Create a constraint
|
|
carPhysics = bge.constraints.createVehicle(car.getPhysicsId())
|
|
car["cid"] = carPhysics.getConstraintId()
|
|
car["VehicleConstraintId"] = carPhysics.getConstraintId()
|
|
|
|
|
|
# Adding wheels
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
AddWheelToCar(car, wheel)
|
|
car["wheels"][n].position = carPhysics.getWheelPosition(n)
|
|
|
|
|
|
# Connect doors and other hinged objects
|
|
car["doors"] = []
|
|
|
|
|
|
|
|
for obj in car.childrenRecursive:
|
|
if obj.get("door"):
|
|
car["doors"].append(obj)
|
|
obj.suspendPhysics()
|
|
|
|
|
|
|
|
# Init Various things we might need
|
|
|
|
# Engine things
|
|
car["rpm"] = float(0.0)
|
|
car["health"] = float(1.0)
|
|
car["nitro"] = float(0.0)
|
|
|
|
car["shake"] = float(0.0)
|
|
|
|
# Set the mass of the car. We divide the mass
|
|
# by 10, since there is a limit of how strong
|
|
# can be car's suspension. UPBGE does not provide
|
|
# a way ( yet ) to override this limit.
|
|
car.mass = car["specs"]["mass"] / 10
|
|
|
|
# Create a callback for collisions
|
|
def OnCollisionDo(obj, point, normal, points):
|
|
|
|
# For readability reason, I put the code
|
|
# for the task itself in a separate funtion
|
|
# below.
|
|
|
|
if car["active"] or (InView(car) and Opt.GoodFPS("Collision"),0.6):
|
|
OnCollision(car, obj, point, normal, points)
|
|
|
|
# I would love to be able to do a Scheduling instead of simple
|
|
# FPS checking for non player cars. But this results in Seg-Faults
|
|
# because apperately the script tries to read "points" that are
|
|
# freed after the simulation step of the collision is over.
|
|
|
|
car.collisionCallbacks.append(OnCollisionDo)
|
|
|
|
# For trucks
|
|
UnloadCargo(car)
|
|
|
|
|
|
print(consoleForm(car),clr["bold"]+clr["tdrd"],"Physics Initilized.", clr["norm"])
|
|
|
|
def GetCarSpecs(car):
|
|
|
|
# This function returns the car specs
|
|
# for a given car.
|
|
|
|
# Clearing the key
|
|
try: key = car["carData"]
|
|
except:
|
|
try: key = car.name
|
|
except: key = str(car)
|
|
|
|
# Return the data, or some data if there is none.
|
|
if key in carData:
|
|
return carData[key]
|
|
else:
|
|
return carData[list(carData.keys())[0]]
|
|
|
|
def AddWheelToCar(car, wheel):
|
|
|
|
# This function adds a new wheel.
|
|
|
|
# Getting the object of the wheel
|
|
wheelname = car.get("wheelname", car.name.replace("Box", "Wheel"))+wheel["object"]
|
|
wheelObject = car.children[wheelname]
|
|
|
|
|
|
|
|
# Add suspension data to the wheel
|
|
wheelObject["suspension"] = car["specs"]["suspention"]
|
|
|
|
# Add health data to the wheel
|
|
wheelObject["health"] = 1.0
|
|
|
|
# Adding this wheel in to the car data for later
|
|
# use.
|
|
|
|
if "wheels" not in car:
|
|
car["wheels"] = []
|
|
|
|
car["wheels"].append(wheelObject)
|
|
|
|
# Removing the parent of the wheel ( it can mess things up if we don't )
|
|
wheelObject.removeParent()
|
|
wheelObject.suspendPhysics()
|
|
|
|
# Adding the wheel to the car physics
|
|
getCarPhysics(car).addWheel(wheelObject,
|
|
wheel["xyz"],
|
|
wheel["down"],
|
|
wheel["axel"],
|
|
wheel["suspensionRest"],
|
|
wheel["radius"],
|
|
wheel["front"])
|
|
|
|
def UserControl(car):
|
|
|
|
# This function makes user control
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
keys = bge.logic.globalDict["keys"]
|
|
settings = bge.logic.globalDict["settings"]
|
|
|
|
if settings.get("veh_mouse"):
|
|
|
|
MouseControl(car)
|
|
|
|
else:
|
|
# Acceleration
|
|
|
|
if controls["forward"] in keys:
|
|
Accelerate(car)
|
|
|
|
if controls["backward"] in keys:
|
|
Accelerate(car, -1)
|
|
|
|
if controls["backward"] not in keys and controls["forward"] not in keys:
|
|
|
|
# Making sure you can actually stop the car
|
|
v = -car.localLinearVelocity[1]
|
|
if v < 0: v *= -1
|
|
if v > 5:
|
|
ReleaseBreakes(car)
|
|
else:
|
|
IdleBraking(car)
|
|
|
|
# Turning
|
|
|
|
if controls["right"] in keys:
|
|
TurnRight(car)
|
|
|
|
elif controls["left"] in keys:
|
|
TurnLeft(car)
|
|
|
|
if controls["horn"] in keys:
|
|
if not car.get("police"):
|
|
HornSound(car)
|
|
elif not car.get("geartoggletimer"):
|
|
car["horning"] = not car.get("horning", False)
|
|
car["geartoggletimer"] = 10
|
|
|
|
if controls["nitro"] in keys:
|
|
StartNitro(car)
|
|
else:
|
|
StopNitro(car)
|
|
|
|
|
|
|
|
if controls["drift"]in keys:
|
|
HandBrake(car)
|
|
|
|
# Racing
|
|
if controls["resque"]in keys:
|
|
car["abort-resque"] = 0
|
|
ResqueRacer(car)
|
|
|
|
# Gears
|
|
|
|
if controls["gearup"] in keys and not car.get("geartoggletimer"):
|
|
GearUp(car)
|
|
car["geartoggletimer"] = 10
|
|
|
|
elif controls["geardown"] in keys and not car.get("geartoggletimer"):
|
|
GearDown(car)
|
|
car["geartoggletimer"] = 10
|
|
|
|
|
|
# Toggling automatic transmission on and off with Q
|
|
if controls["autogear"] in keys and not car.get("geartoggletimer"):
|
|
car["autogear"] = not car.get("autogear", True)
|
|
car["geartoggletimer"] = 10
|
|
|
|
# Counting down the timer
|
|
if car.get("geartoggletimer"):
|
|
car["geartoggletimer"] -= 1
|
|
|
|
|
|
#Toggling truck stuff
|
|
|
|
if controls["unload"] in keys and not car.get("cargocountdown"):
|
|
ChangeCargo(car)
|
|
car["cargocountdown"] = 100
|
|
|
|
if car.get("cargocountdown"):
|
|
car["cargocountdown"] -= 1
|
|
|
|
# Toggling dynamic camera
|
|
if controls["dynamcam"] in keys and not car.get("camtoggletimer"):
|
|
car["dynamiccam"] = not car.get("dynamiccam", True)
|
|
car["shake"] = 0.0
|
|
car["camtoggletimer"] = 10
|
|
|
|
if car.get("camtoggletimer"):
|
|
car["camtoggletimer"] -= 1
|
|
|
|
|
|
|
|
# User UI
|
|
if car.get("dynamiccam", True):
|
|
DynamicCamera(car)
|
|
else:
|
|
Mouse.MouseLookActual(car.children["CarCamTarget"], True)
|
|
|
|
# Anti Fall
|
|
Character.AntiFall(car)
|
|
|
|
UpdateTachometer(car)
|
|
UpdateSpeedometer(car)
|
|
UpdateGearUI(car)
|
|
UpdateRedline(car)
|
|
UpdateHealthGauge(car)
|
|
UpdateNitroMeter(car)
|
|
UpdateFixMap(car)
|
|
|
|
if car.get("horning"):
|
|
PoliceSound(car)
|
|
else:
|
|
StopPoliceSound(car)
|
|
|
|
# Various things that happen only to the user's car
|
|
CloseCall(car)
|
|
|
|
# Teaching Racing AI
|
|
if car.get("driver",{}).get("race") in bge.logic.globalDict["races"].keys():
|
|
try:
|
|
RacingTeacher(car)
|
|
except:pass
|
|
|
|
# Grabbing control when on multiplayer
|
|
if settings.get("multiplayer"):
|
|
Multiplayer_Client.GrabOwnership(car)
|
|
|
|
# Music update
|
|
# l = 0
|
|
# if IsChased(car):
|
|
# l = 3
|
|
# elif -car.localLinearVelocity.y > 50:
|
|
# l = 2
|
|
# elif -car.localLinearVelocity.y > 10:
|
|
# l = 1
|
|
|
|
# bge.logic.globalDict["music_level"] = l
|
|
|
|
#Music.HeatUp( car.localLinearVelocity.y / 1000 )
|
|
l = 0
|
|
if car.get("driver", {}).get("race"): l = 1
|
|
Music.HeatTo( l + ( -car.localLinearVelocity.y / 40 ) + (len(IsChased(car))))
|
|
|
|
# Making night lights
|
|
GameTime.NightLightsEffect(car)
|
|
|
|
def MouseControl(car):
|
|
|
|
# This fucntion will control the car with a mouse.
|
|
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
# Getting mouse position on the screen.
|
|
mouse = bge.logic.mouse
|
|
pos = list(mouse.position)
|
|
|
|
# Centering the cursor when pressing RMB
|
|
mouseinput = bge.logic.globalDict["mouse"]
|
|
if mousecodes["RMB"] in mouseinput:
|
|
Mouse.CenterCursor()
|
|
|
|
# LMB brakes
|
|
if mousecodes["LMB"] in mouseinput:
|
|
v = -car.localLinearVelocity[1]
|
|
ActivateBreaks(car, v, True)
|
|
|
|
|
|
# Making sure that the mouse doesn't escape the window
|
|
if pos[0] > 0.9:
|
|
bge.render.setMousePosition(int(bge.render.getWindowWidth()*0.9),
|
|
int(bge.render.getWindowHeight() * pos[1]))
|
|
elif pos[0] < 0.1:
|
|
bge.render.setMousePosition(int(bge.render.getWindowWidth() * 0.1),
|
|
int(bge.render.getWindowHeight() * pos[1]))
|
|
|
|
if pos[1] > 0.9:
|
|
bge.render.setMousePosition(int(bge.render.getWindowWidth() * pos[0]),
|
|
int(bge.render.getWindowHeight()*0.9))
|
|
elif pos[1] < 0.1:
|
|
bge.render.setMousePosition(int(bge.render.getWindowWidth() * pos[0]),
|
|
int(bge.render.getWindowHeight() * 0.1))
|
|
|
|
pos[0] -= 0.5
|
|
pos[1] -= 0.5
|
|
pos[0] *= 2.2
|
|
pos[1] *= 2.2
|
|
|
|
# Blind spot
|
|
bs = 0.1
|
|
|
|
if pos[0] > 0: p0 = 1
|
|
else: p0 = -1
|
|
if pos[1] > 0: p1 = 1
|
|
else: p1 = -1
|
|
|
|
# if pos[0] > bs or pos[0] < -bs:
|
|
# p = pos[0]
|
|
# pos[0] = (( (pos[0] * p0) * (1+bs) ) - bs) * p0
|
|
|
|
# else: pos[0] = 0.0
|
|
|
|
a = pos[1]
|
|
t = pos[0]
|
|
|
|
if pos[1] > bs or pos[1] < -bs:
|
|
|
|
pos[1] = (( (pos[1] * p1) * (1+bs) ) - bs) * p1
|
|
pos[1] = ( ( ( pos[1] * p1 ) ** 2 ) * p1)
|
|
Accelerate(car, -pos[1] , mouse=True)
|
|
else:
|
|
pos[1] = 0.0
|
|
v = -car.localLinearVelocity[1]
|
|
if v < 0: v *= -1
|
|
if v > 5:
|
|
ReleaseBreakes(car)
|
|
else:
|
|
IdleBraking(car)
|
|
|
|
|
|
car["turn"] = -( ( ( pos[0] * p0 ) ** 2 ) * p0)
|
|
|
|
# UI indication
|
|
turnpiece = scene.objects["Mouse_Turn_UI_Piece"]
|
|
turnframe = scene.objects["Mouse_Turn_UI"]
|
|
m = 0.062012 * turnpiece.worldScale[0]
|
|
turnpiece.worldPosition = RelativePoint(turnframe,
|
|
(m*t,0,0)
|
|
)
|
|
accelpiece = scene.objects["Mouse_Accelation_UI_Piece"]
|
|
accelframe = scene.objects["Mouse_Accelation_UI"]
|
|
|
|
m = 0.062012 * accelpiece.worldScale[0]
|
|
accelpiece.worldPosition = RelativePoint(accelframe,
|
|
(m*a,0,0)
|
|
)
|
|
|
|
def Accelerate(car, factor=1, mouse=False):
|
|
|
|
# This function accelerates the car
|
|
|
|
# When changing direction we want it to act like
|
|
# brakes.
|
|
v = -car.localLinearVelocity[1]
|
|
if v * factor < -0.5:
|
|
|
|
# Activating breaks
|
|
ActivateBreaks(car, factor, True)
|
|
|
|
else:
|
|
|
|
# Reverting to NOT breaking.
|
|
ReleaseBreakes(car)
|
|
|
|
# Reving the engine, and telling the code that
|
|
# we are accelerating now.
|
|
|
|
Rev(car, factor, mouse)
|
|
car["accelerating"] = True
|
|
|
|
def ActivateBreaks(car, factor=1, stable=False):
|
|
|
|
# Activating brakes.
|
|
|
|
strenght = factor
|
|
if strenght < 0: strenght *= -1
|
|
Abs = ABS(car)
|
|
turn = Turning(car)
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
|
|
wheelObject = car["wheels"][n]
|
|
health = wheelObject["health"] ** 5
|
|
|
|
# If the car moves forward stopping only with back
|
|
# wheels so it will not fly into the air.
|
|
if ( factor < 0 and not wheel["front"] ) or ( factor > 0 and wheel["front"] ):
|
|
getCarPhysics(car).applyBraking(Abs*health*strenght, n)
|
|
else:
|
|
getCarPhysics(car).applyBraking(0, n)
|
|
|
|
# Stablizing the brakes
|
|
if stable and car["specs"].get("stableBrake", True):
|
|
r = car.localAngularVelocity[2]
|
|
car.applyTorque([0,0,-r*car.mass*20], True)
|
|
|
|
car["braking"] = True
|
|
|
|
def ReleaseBreakes(car):
|
|
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
getCarPhysics(car).applyBraking(0, n)
|
|
|
|
|
|
def StartNitro(car):
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
# Skipping
|
|
if not car.get("active") and not Opt.GoodFPS("Nitro", 0.9) or not car.get("NitroCan") :
|
|
return
|
|
|
|
# Nitro Ended
|
|
if car.get("nitro", 0) < 0.04:
|
|
StopNitro(car)
|
|
RemoveNitroCan(car)
|
|
car["nitro"] = 0.0
|
|
return
|
|
|
|
|
|
for nitroObject in car.children:
|
|
if "nitro" in nitroObject.name:
|
|
if not nitroObject.get("nitroCone"):
|
|
nitroCone = Reuse.Create("NitroCone")
|
|
nitroObject["nitroCone"] = nitroCone
|
|
nitroCone.position = nitroObject.worldPosition
|
|
nitroCone.orientation = nitroObject.worldOrientation
|
|
nitroCone.setParent(nitroObject)
|
|
|
|
if car.get("active"):
|
|
cam = scene.active_camera
|
|
if cam.lens > 15:
|
|
cam.lens -= 0.3
|
|
|
|
NitroSound(car)
|
|
ReduceNitro(car, 0.015)
|
|
|
|
|
|
# Fake push on the car forward
|
|
car.applyForce([0,-5*car.mass, 0], True)
|
|
|
|
car["nitroing"] = True
|
|
|
|
def StopNitro(car):
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
for nitroObject in car.children:
|
|
if "nitro" in nitroObject.name:
|
|
if nitroObject.get("nitroCone"):
|
|
nitroObject["nitroCone"].removeParent()
|
|
Reuse.Delete(nitroObject["nitroCone"])
|
|
nitroObject["nitroCone"] = None
|
|
|
|
if car.get("active"):
|
|
cam = scene.active_camera
|
|
if cam.lens < 20:
|
|
cam.lens += 0.3
|
|
|
|
NitroSoundStop(car)
|
|
|
|
car["nitroing"] = False
|
|
|
|
def AddNitro(car, amount):
|
|
|
|
# Deprecated
|
|
return
|
|
if amount < 0: amount *= -1
|
|
car["nitro"] = max(0, car.get("nitro", 0) + amount)
|
|
|
|
def ReduceNitro(car, amount):
|
|
|
|
if amount < 0: amount *= -1
|
|
car["nitro"] = min(car.get("nitro", 0) - amount, 10)
|
|
|
|
def AddNitroCan(car):
|
|
|
|
if "NitroCanProxy" not in car.childrenRecursive:
|
|
return
|
|
|
|
|
|
nitrocan = Reuse.Create("NitroCan")
|
|
nitroproxy = car.childrenRecursive["NitroCanProxy"]
|
|
|
|
nitrocan.position = nitroproxy.position
|
|
nitrocan.worldOrientation = nitroproxy.worldOrientation
|
|
nitrocan.setParent(nitroproxy)
|
|
car["NitroCan"] = True
|
|
car["NitroCanObject"] = nitrocan
|
|
|
|
def RemoveNitroCan(car):
|
|
|
|
if not car.get("NitroCan"):
|
|
return
|
|
|
|
|
|
nitrocan = car["NitroCanObject"]
|
|
nitrocan.removeParent()
|
|
Reuse.Delete(nitrocan)
|
|
car["nitro"] = 0.0
|
|
car["NitroCan"] = False
|
|
|
|
|
|
def AddSpoiler(car, spoilertype=None):
|
|
|
|
if "SpoilerProxy" not in car.childrenRecursive:
|
|
return
|
|
|
|
if not spoilertype:
|
|
spoilers = []
|
|
for name in Garage.shop:
|
|
usemodel = Garage.shop[name].get("usemodel")
|
|
if "Spoiler" in name and usemodel:
|
|
spoilers.append(usemodel)
|
|
spoilertype = random.choice(spoilers)
|
|
|
|
|
|
|
|
spoiler = Reuse.Create(spoilertype)
|
|
spoilerproxy = car.childrenRecursive["SpoilerProxy"]
|
|
|
|
spoiler.position = spoilerproxy.position
|
|
spoiler.worldOrientation = spoilerproxy.worldOrientation
|
|
spoiler.scaling = spoilerproxy.scaling
|
|
spoiler.setParent(spoilerproxy)
|
|
ColorPart(car, spoiler)
|
|
car["Spoiler"] = spoilertype
|
|
car["SpoilerObject"] = spoiler
|
|
|
|
def RemoveSpoiler(car):
|
|
|
|
if not car.get("Spoiler"):
|
|
return
|
|
|
|
spoilertype = car["Spoiler"]
|
|
spoiler = car["SpoilerObject"]
|
|
spoiler.removeParent()
|
|
Reuse.Delete(spoiler)
|
|
car["Spoiler"] = None
|
|
car["SpoilerObject"] = None
|
|
return spoilertype
|
|
|
|
def GetTunnedEffects(car):
|
|
|
|
# This function will return a sum total of all effects added
|
|
# to the car.
|
|
|
|
effects = {}
|
|
|
|
names = []
|
|
if car.get("Spoiler"):
|
|
for name in Garage.shop:
|
|
item = Garage.shop[name]
|
|
if item.get("usemodel") == car["Spoiler"]:
|
|
names.append(name)
|
|
|
|
for name in names:
|
|
item = Garage.shop.get(name, {})
|
|
effe = item.get("effects", {})
|
|
for e in effe:
|
|
effect = effe[e]
|
|
effects[e] = effects.get(e,1) * effect
|
|
|
|
return effects
|
|
|
|
def UpdateNitroMeter(car):
|
|
|
|
# This function shows nitro amount
|
|
# for the user.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
nitrometer = scene.objects["Gage_Nitro"]
|
|
nitrovalue = car.get("nitro", 0) / 10 * 100
|
|
|
|
if not car.get("NitroCan"):
|
|
nitrovalue = 0.0
|
|
|
|
nitrometer["Nitro"] = nitrovalue
|
|
|
|
|
|
def ABS(car):
|
|
|
|
# This function implements a basic
|
|
# Anti-lock Braking System. And
|
|
# returns a braking value, to be
|
|
# used in the Bullet's Car's Braking
|
|
# function.
|
|
|
|
# Due to it being a game and not a
|
|
# perfect simulation of car dynamics
|
|
# it will not be your typical ABS
|
|
# but rather something that acts similarly.
|
|
|
|
brakes = car["specs"]["brakes"]
|
|
cb = car.mass * brakes
|
|
|
|
# Here is the magic. The strong the "abs" value
|
|
# for a specific car, the more damping will any
|
|
# sudden burst of rotation on Z axis recieve.
|
|
# Basically, higher "abs" = lower drifting.
|
|
|
|
strength = car["specs"]["abs"]
|
|
car.localAngularVelocity[2] *= Turning(car) ** strength
|
|
|
|
return cb
|
|
|
|
def IdleBraking(car):
|
|
|
|
# When car is just standing still
|
|
# and nobody is driving it, we
|
|
# want to car to keep standing where
|
|
# it is standing and not roll down hills
|
|
# and stuff. For this we activate breaks.
|
|
|
|
Abs = ABS(car)
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
getCarPhysics(car).applyBraking(Abs, n)
|
|
|
|
def GearUp(car):
|
|
|
|
# First we gear up.
|
|
oldgear = car.get("gear", 0)
|
|
gears = car["specs"]["gears"]
|
|
car["gear"] = min( len( gears ) - 1 , car.get("gear", 0) + 1 )
|
|
|
|
# Then we calculate how much to change the RPM
|
|
car["rpm"] *= gears[car["gear"]] / gears[oldgear]
|
|
GearShiftSound(car)
|
|
|
|
def GearDown(car):
|
|
|
|
# Same as GearUP() but in reverse.
|
|
|
|
oldgear = car.get("gear", 0)
|
|
gears = car["specs"]["gears"]
|
|
car["gear"] = max( 0 , car.get("gear", 0) - 1 )
|
|
car["rpm"] *= gears[car["gear"]] / gears[oldgear]
|
|
GearShiftSound(car)
|
|
|
|
def GetGearRatio(car):
|
|
|
|
# Gets a ratio between engine
|
|
# speed and wheel speed based on
|
|
# the current selected gear.
|
|
|
|
gear = car.get("gear", 0)
|
|
gearRatio = car["specs"]["gears"][gear]
|
|
return gearRatio
|
|
|
|
def AutomaticTransmission(car):
|
|
|
|
# This function automatically shifts
|
|
# gears.
|
|
|
|
idle = car["specs"]["idle"]
|
|
redline = car["specs"]["redline"]
|
|
gears = car["specs"]["gears"]
|
|
|
|
# Gearing up when reaching the redline
|
|
# We add a bit to the rpm to avoid damage
|
|
# to the engine.
|
|
if car["rpm"]*1.1 >= redline and car.get("gear",0)+1 != len( gears ):
|
|
GearUp(car)
|
|
|
|
# Gearing down when reaching idle
|
|
elif car["rpm"] < idle and car.get("gear"):
|
|
GearDown(car)
|
|
|
|
# Reverse gear is gear 1 and if we have too much resistance
|
|
# also gear 1.
|
|
if car["rpm"] <= 0 or EngineResistance(car) < 0.1:
|
|
car["gear"] = 0
|
|
|
|
def Rev(car, factor=1, mouse=False):
|
|
|
|
# This function simulates the acceleration of
|
|
# the internals of the engine recorded in
|
|
# car["rpm"].
|
|
|
|
torque = Torque(car)
|
|
radius = car["specs"]["wheels"][0]["radius"]
|
|
mass = car["specs"]["mass"]
|
|
maxrpm = car["specs"]["maxrpm"]
|
|
|
|
# Get how many RPM there will be in 1 second
|
|
force = torque / radius
|
|
|
|
resistance = EngineResistance(car)
|
|
|
|
accel = Opt.Velocity(force) / max( 0.1, ( mass / GetGearRatio(car) ) ) * resistance
|
|
addrot = accel / ( math.pi * 2 )
|
|
addRPM = addrot * 60
|
|
|
|
# Get how many RPM there will be in 1 frame
|
|
addRPM /= bge.logic.getAverageFrameRate()
|
|
|
|
# Add idle RPM. Starting engine from the idle
|
|
# instead of from 0.
|
|
withIdle = RPMWithIdle(car, factor)
|
|
|
|
# Calculating everything.
|
|
if withIdle < 0: flip = -1
|
|
else : flip = 1
|
|
|
|
|
|
#if not mouse:
|
|
# car["rpm"] = ( ( math.sqrt( withIdle * flip) * flip ) + (addRPM * factor) ) ** 2 * factor
|
|
|
|
# If the mouse is not being used, we want to simulate the soft pressing of the
|
|
# button.
|
|
if not mouse:
|
|
factor = Input.SimulatePress(str(car["cid"])+"_throttle",
|
|
MIN=0,
|
|
MAX=1,
|
|
SPEED=0.02) * factor
|
|
|
|
if factor < 0 and car["rpm"] > factor * maxrpm:
|
|
car["rpm"] = ( ( math.sqrt( withIdle * flip) * flip ) - (addRPM) ) ** 2 *-1
|
|
elif factor > 0 and car["rpm"] < factor * maxrpm:
|
|
car["rpm"] = ( ( math.sqrt( withIdle * flip) * flip ) + (addRPM) ) ** 2
|
|
|
|
|
|
# Making sure not to exceed the maximum RPM
|
|
car["rpm"] = min( maxrpm, car["rpm"])
|
|
car["rpm"] = max(-maxrpm, car["rpm"])
|
|
|
|
# If revs are above the redline add damage to the engine
|
|
redline = car["specs"]["redline"]
|
|
factor = ( car["rpm"] - redline ) / ( maxrpm - redline ) * int( car["rpm"] > redline )
|
|
car["health"] = max(0, car.get("health", 1) - ( factor / 1000 ))
|
|
if factor:
|
|
RedlineSound(car, volume=min(3, factor*10))
|
|
|
|
# Making sure to set 0th gear at negative RPM
|
|
if car["rpm"] < 0: car["gear"] = 0
|
|
|
|
def EngineResistance(car):
|
|
|
|
# This function simulates the resistance
|
|
# ground and various obsticles on the engine
|
|
# of the car. If you push against the wall,
|
|
# this function will prevent the engine from
|
|
# reving hard. Also it is used to make it
|
|
# nearly impossible to start the car from a
|
|
# high gear.
|
|
|
|
gear = GetGearRatio(car)
|
|
if not car.get("gear"):
|
|
return 1
|
|
|
|
# First we need to calculate expected speed
|
|
radius = car["specs"]["wheels"][0]["radius"]
|
|
ev = car["rpm"] / gear / 60 * radius
|
|
|
|
# Then we get the real value
|
|
v = -car.localLinearVelocity[1]
|
|
|
|
# Then return the fraction
|
|
try : return min(1, v / ev )
|
|
except: return 0 # If the expected velocity is 0, we return 0.
|
|
|
|
def RPMWithIdle(car, factor=None, normalize=False):
|
|
|
|
# Returns RPM of the car with Idle RPM present.
|
|
|
|
# Sometimes the factor should be specified manually.
|
|
if factor == None:
|
|
if car["rpm"] < 0: factor = -1
|
|
else : factor = 1
|
|
|
|
# Making the idle
|
|
idle = car["specs"]["idle"] * factor
|
|
if factor < 0: withIdle = min(idle, car["rpm"])
|
|
else : withIdle = max(idle, car["rpm"])
|
|
|
|
# Sometimes we want to normalize it ( like for sound ).
|
|
if normalize and withIdle < 0: withIdle *= -1
|
|
|
|
return withIdle
|
|
|
|
def HorsePower(car):
|
|
|
|
# This function calculates estimated
|
|
# horse power given the current RPM
|
|
# of the engine.
|
|
|
|
# The approximation is done using a Sinewave
|
|
# from values of 0 to 3 forths of Pi.
|
|
|
|
rpm = car["rpm"]
|
|
maxrpm = car["specs"]["maxrpm"]
|
|
horses = car["specs"]["hp"]
|
|
|
|
# Nitro
|
|
if car.get("nitroing"):
|
|
return horses * 10
|
|
|
|
return math.sin(rpm/maxrpm*(math.pi/4*3)) * horses
|
|
|
|
def Torque(car):
|
|
|
|
# This function calculates torque of
|
|
# the car. Based on an estimate of
|
|
# a torque curve.
|
|
|
|
rpm = car["rpm"]
|
|
maxrpm = car["specs"]["maxrpm"]
|
|
torque = car["specs"]["torque"]
|
|
health = car.get("health", 1)
|
|
|
|
# Nitro
|
|
if car.get("nitroing"):
|
|
return torque * 10
|
|
|
|
return math.sin((rpm/maxrpm*(math.pi/4*3))+math.pi/4) * torque * health
|
|
|
|
def HorsePowerToWatts(value):
|
|
return value * 745.6998715823
|
|
|
|
def AirDencity(car, ignoreWater=False):
|
|
|
|
# Air density function will return
|
|
# the density of air at various elevations
|
|
# including the dencity of water under water.
|
|
|
|
# Water is a bit below the center of the game map.
|
|
waterlevel = -9.5
|
|
|
|
if car.position[2] > waterlevel or not car.get("underwater") or ignoreWater:
|
|
|
|
# We will aproximate the air dencity due to elevation
|
|
# with a simple sine wave. Since the graph looks a bit
|
|
# like the sine way between the values of pi and pi + half a pi.
|
|
# Or in the case of this code, flipped sine from 0 to half a pi.
|
|
|
|
maxelevation = 10000 # Roughly where the air is least dense
|
|
mindensity = 0.023 # Roughly the density at mount everest
|
|
maxdensity = 1.2 # Roughly density of air at sea level
|
|
|
|
heightproxy = 1 - ( ( car.position[2] - waterlevel ) / maxelevation )
|
|
density = math.sin( math.pi / 2 * heightproxy ) * maxdensity
|
|
density = min(maxdensity, density)
|
|
|
|
# Making sure to check that the car is not underwater anymore
|
|
car["underwater"] = False
|
|
|
|
return density
|
|
|
|
else:
|
|
|
|
# Completerly faking water density.
|
|
|
|
return max(2, min( 20, ( car.position[2] - waterlevel ) * -10 ))
|
|
|
|
def WaterBoyancy(car):
|
|
|
|
# This function moves car upward,
|
|
# if the car is underwater.
|
|
# See DownForce() for where it is
|
|
# running.
|
|
|
|
g = -9.8
|
|
m = car.mass
|
|
v = 0.9 # Less then 1 so car could sink, but slowly.
|
|
|
|
return g * m * v - DragForce(car)
|
|
|
|
def DragForce(car):
|
|
|
|
effects = GetTunnedEffects(car)
|
|
cd = car["specs"]["drag"]
|
|
cd = cd * effects.get("drag", 1)
|
|
|
|
p = AirDencity(car)
|
|
v = -car.localLinearVelocity[1]
|
|
A = 1.5 # Frontal area of the car. I estimate it here.
|
|
|
|
if v > 0: flip = 1
|
|
else : flip = -1
|
|
|
|
return (cd / 2 * p * ( v ** 2 ) * A ) * flip
|
|
|
|
def DownForce(car):
|
|
|
|
if not car.get("underwater"):
|
|
|
|
effects = GetTunnedEffects(car)
|
|
cl = car["specs"]["downforce"]
|
|
cl = cl * effects.get("downforce", 1)
|
|
|
|
p = AirDencity(car, ignoreWater=True)
|
|
v = -car.localLinearVelocity[1]
|
|
A = 0.1 # Based on nothing
|
|
|
|
df = cl * p * ( v ** 2 ) * A
|
|
|
|
# making sure that the car will not derail
|
|
|
|
# Distance to the ground when suspension is holding it in place
|
|
wp = car["specs"]["wheels"][0]["xyz"][2]
|
|
wr = car["specs"]["wheels"][0]["radius"]
|
|
dtg = wp-wr-0.2
|
|
|
|
down = RelativePoint(car, [0,0,dtg])
|
|
hit = car.rayCastTo(down)
|
|
|
|
# is the car is on the ground, limit downforce
|
|
# to the force of the suspension.
|
|
if hit:
|
|
df = min(car["specs"]["suspention"]/2, df)
|
|
|
|
return df
|
|
|
|
else:
|
|
return WaterBoyancy(car)
|
|
|
|
def ApplyEngine(car):
|
|
|
|
toFPS = Opt.ToFPS()
|
|
|
|
hp = HorsePower(car)
|
|
force = HorsePowerToWatts(hp) / 100
|
|
grip = car["specs"]["grip"]
|
|
|
|
# Here is a little cheat to help the car start
|
|
resistance = EngineResistance(car)
|
|
if car.get("gear",0) == 0:
|
|
resistance = 1
|
|
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
|
|
wheelObject = car["wheels"][n]
|
|
health = (wheelObject["health"] + car["health"]) / 2
|
|
|
|
rightwheel = ( wheel["front"] and force > 1 ) or ( not wheel["front"] and force < 1 )
|
|
|
|
if rightwheel and not car.get("braking"):
|
|
getCarPhysics(car).applyEngineForce(force * health * resistance / GetGearRatio(car) / grip, n)
|
|
else:
|
|
getCarPhysics(car).applyEngineForce(0, n)
|
|
|
|
|
|
# Decrease RPMS
|
|
if not car.get("accelerating") and not NetworkControlled(car):
|
|
car["rpm"] *= 0.98 * EngineResistance(car)
|
|
|
|
def ApplyTurn(car):
|
|
|
|
# This function applies turn
|
|
|
|
toFPS = Opt.ToFPS()
|
|
|
|
# Making sure the car is not turning more than it is possible.
|
|
|
|
maxturn = car["specs"]["maxturn"]
|
|
car["turn"] = max(-maxturn,
|
|
min(maxturn,
|
|
car["turn"]
|
|
)
|
|
)
|
|
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
|
|
wheelObject = car["wheels"][n]
|
|
health = wheelObject["health"] ** 2
|
|
|
|
if wheel["front"]:
|
|
getCarPhysics(car).setSteeringValue(car["turn"]*health, n)
|
|
else:
|
|
getCarPhysics(car).setSteeringValue(0, n)
|
|
|
|
# Fake turn Aid
|
|
vo = car.localLinearVelocity[1]
|
|
v = vo
|
|
if v < 0: v *= -1
|
|
|
|
if not car.get("handbraking", 0):
|
|
|
|
S = car["specs"].get("turnFake", 20)
|
|
|
|
if v > 0.01 and vo < 0:
|
|
car.applyTorque([0,0,car["turn"] * S * car.mass],True)
|
|
elif v > 0.01 and vo > 0:
|
|
car.applyTorque([0,0,car["turn"] * -S * car.mass],True)
|
|
|
|
|
|
# Auto going straiter
|
|
if not car.get("turning"):
|
|
if car["turn"] < 0:
|
|
car["turn"] += 0.05 * toFPS
|
|
if car["turn"] > 0:
|
|
car["turn"] = 0
|
|
else:
|
|
car["turn"] -= 0.05 * toFPS
|
|
if car["turn"] < 0:
|
|
car["turn"] = 0
|
|
|
|
car["turning"] = False
|
|
|
|
def TurnRight(car):
|
|
|
|
toFPS = Opt.ToFPS()
|
|
settings = bge.logic.globalDict.get("settings", {})
|
|
ts = settings.get("turn", 1.0)
|
|
if car.get("handbraking") or not car.get("active"): ts = 1.0
|
|
|
|
if car["turn"] > 0: car["turn"] *= -0.5
|
|
car["turn"] -= 0.005 * toFPS * ts
|
|
car["turning"]= True
|
|
|
|
def TurnLeft(car):
|
|
|
|
toFPS = Opt.ToFPS()
|
|
settings = bge.logic.globalDict.get("settings", {})
|
|
ts = settings.get("turn", 1.0)
|
|
if car.get("handbraking") or not car.get("active"): ts = 1.0
|
|
|
|
if car["turn"] < 0: car["turn"] *= -0.5
|
|
car["turn"] += 0.005 * toFPS * ts
|
|
car["turning"]= True
|
|
|
|
def HandBrake(car):
|
|
|
|
car["handbraking"] = 30
|
|
|
|
def HandBraking(car):
|
|
|
|
# This is an implementation of
|
|
# a fake handbrake. It is fake
|
|
# since normal drifting could
|
|
# be technically simulated using
|
|
# the physics engine. But it is
|
|
# a very delicate thing that results
|
|
# in borderline undrivable cars.
|
|
# So instead there is this function
|
|
# that is used instead to approximate
|
|
# the behaviour of the drift.
|
|
|
|
cv = car.localLinearVelocity
|
|
v = cv.y
|
|
if v <0: v *= -1
|
|
|
|
car["handbraking"] = car.get("handbraking", 30) - 1
|
|
|
|
if v < 10:
|
|
car["handbraking"] = 0
|
|
if car.get("accelerating"):
|
|
return
|
|
|
|
turning = Turning(car)
|
|
if turning and not car["handbraking"]:
|
|
car["handbraking"] = 2
|
|
|
|
hbf = car["handbraking"] / 30
|
|
|
|
|
|
if car["handbraking"] <= 0:
|
|
car["driftturn"] = 0.0
|
|
|
|
if not OnGround(car):
|
|
return
|
|
|
|
factor = car.localLinearVelocity[1]
|
|
if factor > 0: factor = 1.0
|
|
else : factor = -1.0
|
|
|
|
#if car.get("accelerating"):
|
|
# factor /= 20
|
|
|
|
ActivateBreaks(car, factor*hbf)
|
|
|
|
|
|
# Recording the turn at which the drift started.
|
|
# And using it to add a fake torque to the car.
|
|
# which will simulate drifting experience a bit
|
|
# more accurately.
|
|
|
|
dt = car.get("driftturn",0)
|
|
if dt < 0: dt *= -1
|
|
|
|
# If turn increased in the same direction as the drift turn, we increase drift turn
|
|
if ( dt < turning and ( ( car.get("driftturn",0 ) < 0 ) == ( car["turn"] < 0 ) ) )\
|
|
or not car.get("driftturn",0 ):
|
|
|
|
car["driftturn"] = float(car["turn"])
|
|
|
|
|
|
# Adding fake turn to the car
|
|
# car.applyTorque([0,
|
|
# 0,
|
|
# ((car["driftturn"])+(car["turn"]/2))*car.mass*10*hbf
|
|
# ], True)
|
|
|
|
# Instead of reducing friction on the wheels, this
|
|
# will simulate that, but with more control.
|
|
# if car.get("accelerating"):
|
|
# cv.x += -car["driftturn"] * v / 50 *hbf
|
|
# cv.y += turning * v / 20 * hbf
|
|
# car.applyForce(cv * car.mass * 0.5 * hbf, True)
|
|
|
|
def Turning(car):
|
|
|
|
# This funtion returns the fraction
|
|
# of how far the front wheels have
|
|
# been turned for turning the car.
|
|
|
|
t = car["turn"]
|
|
if t < 0: t *= -1
|
|
maxturn = car["specs"]["maxturn"]
|
|
|
|
return t / maxturn
|
|
|
|
def UpSideDown(car):
|
|
|
|
rot = car.localOrientation.to_euler()
|
|
upsideDown = not ( -2.5 < rot[0] < 2.5 and -2.5 < rot[1] < 2.5 )
|
|
return upsideDown
|
|
|
|
def DynamicCamera(car):
|
|
|
|
# This is a function that moves the camera
|
|
# in a dynamic way.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
|
|
toFPS = Opt.ToFPS()
|
|
|
|
cam = scene.active_camera
|
|
camtarget = car.children["CarCamTarget"]
|
|
|
|
onground = OnGround(car, -2)
|
|
someanim = bge.logic.globalDict.get("CameraTargetChange")
|
|
|
|
|
|
|
|
|
|
spin = GetSpin(car)
|
|
|
|
dontseecar = cam.rayCastTo(car) not in [car]+list(car.childrenRecursive)
|
|
|
|
# The effect of showing the car if
|
|
# the car went up side down.
|
|
|
|
upsidedown = UpSideDown(car)
|
|
|
|
# if upsidedown and dontseecar:
|
|
# camrot = camtarget.localOrientation.to_euler()
|
|
# camrot[0] -= 0.05
|
|
# camtarget.localOrientation= camrot
|
|
|
|
#if upsidedown and spin < 1:
|
|
# return # To cancel the rest of the effect
|
|
|
|
########### AIR CAMERA DISCONNECT EFFECT ##########
|
|
|
|
if not onground and not someanim:
|
|
Character.ChangeCameraTarget(camtarget,
|
|
0,
|
|
rotate=False)
|
|
car["return-cam"] = True
|
|
|
|
|
|
if onground and car.get("return-cam"):
|
|
|
|
car["return-cam"] = False
|
|
|
|
Character.ChangeCameraTarget(camtarget,
|
|
30,
|
|
rotate=True)
|
|
car["zoom"] = 2
|
|
return
|
|
|
|
#######################################################
|
|
|
|
|
|
# The effect is zooming in when something
|
|
# covers up the car.
|
|
|
|
# if dontseecar and camtarget.scaling[0] > 0.1:
|
|
# camtarget.scaling *= 0.97
|
|
|
|
# elif camtarget.scaling[0] <= 0.8 and scene.objects["SecondaryCamSensor"].rayCastTo(car) in [car]+list(car.childrenRecursive):
|
|
# camtarget.scaling *= 1.1
|
|
|
|
# if camtarget.scaling[0] < 0.1:
|
|
# camtarget.scaling = [0.1, 0.1, 0.1]
|
|
# if camtarget.scaling[0] > 2:
|
|
# camtarget.scaling = [2, 2, 2]
|
|
|
|
# Then we ar going to rotate the camera around
|
|
# the car ( using a parenting point in the middle
|
|
# of the car ) based on various momentums of
|
|
# the car.
|
|
|
|
camrot = mathutils.Vector(camtarget.localOrientation.to_euler())
|
|
|
|
reference = car.getVectTo(car.position + car.getLinearVelocity())[2]
|
|
rotref = car.localAngularVelocity
|
|
|
|
backorfoth = ((reference[1]+1)/2)
|
|
if backorfoth > 0.6: backorforth = 1
|
|
else: backorforth = 0
|
|
|
|
if camtarget["back"] and backorforth: backorforth = 0
|
|
elif camtarget["back"]: backorforth = 1
|
|
|
|
camnewrot = mathutils.Vector( [ 0.3 - max(-0.1, min(0.2, rotref[0]/2)),# - (car["engine"]*0.005),
|
|
0 - (rotref[2]/10) - (rotref[1]/6),
|
|
( math.pi * backorforth ) - (rotref[2]/4)] )
|
|
|
|
# If the car spins too hard, we want to offset it.
|
|
camnewrot -= rotref / 10 * min(1,spin)
|
|
|
|
if backorforth and camrot[2] < 0:
|
|
camnewrot[2] -= math.pi * 2
|
|
|
|
camnewrot = mathutils.Vector(camnewrot)
|
|
camrot = camrot + (( camnewrot - camrot ) / 10 )
|
|
camtarget.localOrientation = camrot
|
|
|
|
|
|
# Shake ( shift ) from speed and collisions
|
|
# Moved ( interestingly enough ) to Music.py
|
|
|
|
# v = car.localLinearVelocity[1]
|
|
# if v < 0: v *= -1
|
|
# v = max(0, v - 15)
|
|
# v = v / 20000
|
|
|
|
# if car.get("shake", 0) > 0.01:
|
|
# v += car["shake"] / 60
|
|
# car["shake"] *= 0.92
|
|
|
|
#cam.shift_x = random.uniform(0,v)
|
|
#cam.shift_y = random.uniform(0,v)
|
|
|
|
|
|
def UpdateTachometer(car):
|
|
|
|
# This function shows engine RPMs
|
|
# for the user.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
tachometer = scene.objects["Gage_Tachometer"]
|
|
rpmvalue = RPMWithIdle(car, normalize=True) / car["specs"]["maxrpm"] * 100
|
|
tachometer["Health"] = rpmvalue
|
|
|
|
def UpdateHealthGauge(car):
|
|
|
|
# This function shows engine RPMs
|
|
# for the user.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
tachometer = scene.objects["Gage_Health"]
|
|
tachometer["Health"] = car.get("health", 1) * 100
|
|
|
|
def UpdateSpeedometer(car):
|
|
|
|
# This function shows car speed
|
|
# to the user.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
kmh = GetSpeed(car)
|
|
|
|
speedometer = scene.objects["Gage_Arrow"]
|
|
|
|
# Speedometer in game shows between 0 and 400 kmh
|
|
# and the whole rotation takes -245 degrees or
|
|
# -4.276 radians.
|
|
|
|
whole = -4.276
|
|
|
|
# And with that we can apply the rotation of the gauage.
|
|
rotation = kmh / 400 * whole
|
|
speedometer.orientation = [0,0, rotation]
|
|
|
|
def UpdateRedline(car):
|
|
|
|
# This function updates redline.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
|
|
redline = car["specs"]["redline"]
|
|
arrow = scene.objects["Gage_Redline"]
|
|
maxrpm = car["specs"]["maxrpm"]
|
|
whole = -4.276 # See UpdateSpeedometer
|
|
|
|
rotation = redline / maxrpm * whole
|
|
arrow.orientation = [0,0,rotation]
|
|
|
|
def UpdateGearUI(car):
|
|
|
|
# This function shows to the user
|
|
# in what gear the car is right now.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
# Getting gear number
|
|
gear = car.get("gear",0) + 1 # First gear is 0 in the system
|
|
|
|
# Reverse gear
|
|
if car["rpm"] < 0:
|
|
gear = "R"
|
|
|
|
scene.objects["Gage_Gear"]["Text"] = str(gear)
|
|
|
|
def UpdateFixMap(car):
|
|
|
|
# This shows the fix icon on the map if
|
|
# the car is broken
|
|
|
|
if car.get("health", 1.0) < 1.0:
|
|
|
|
Map.Show(fixGaragePosition, icon="Map_Fix", color=[0.8,0.8,0.8])
|
|
|
|
|
|
def GetSpeed(car, real=False):
|
|
|
|
# When car moves forward, it is moving
|
|
# backwards in the engine
|
|
v = -car.localLinearVelocity[1]
|
|
|
|
# Now we can find kmh
|
|
kmh = v / 3.6
|
|
|
|
# But in the actuall game this real value feels
|
|
# too small compared to the mayhem that is happening
|
|
# in the game. So we quadrupple the speed, thus
|
|
# 25 KM/H will show up as 100 KM/H to the user.
|
|
|
|
kmh *= 4
|
|
|
|
# Sometimes we need the real speed ( as in
|
|
# when we go backwards, the speed will be < 0 ).
|
|
# But sometimes we just want to know the speed
|
|
# no matter the direction.
|
|
|
|
if not real and kmh < 0:
|
|
kmh *= -1
|
|
|
|
return kmh
|
|
|
|
def GetSpin(car):
|
|
|
|
# This function returns average
|
|
# normalized angular velocity.
|
|
# Basically, how hard does the car
|
|
# spin in any direction.
|
|
|
|
# Getting angular velocity
|
|
xyz = car.getAngularVelocity()
|
|
|
|
# Normalizing it
|
|
for n, i in enumerate(xyz):
|
|
if i < 0:
|
|
xyz[n] *= -1
|
|
|
|
# Averaging it
|
|
av = sum(xyz) / 3
|
|
|
|
# Returning it
|
|
return av
|
|
|
|
def RelativePoint(car, point, rotation=None):
|
|
|
|
# This will return any relative point
|
|
# in relation to the car in 3D space.
|
|
|
|
# First we want to convert the point to
|
|
# a vector.
|
|
point = mathutils.Vector(point)
|
|
|
|
# And get the car's rotation in eulers.
|
|
if not rotation:
|
|
rotation = car.orientation.to_euler()
|
|
|
|
# Then we rotate the point by the car's rotation
|
|
# using, the blessing, that is the mathutils
|
|
# module.
|
|
point.rotate(rotation)
|
|
|
|
# And finally we move the point to the car's
|
|
# world location.
|
|
point = point + car.worldPosition
|
|
|
|
# Returning
|
|
return point
|
|
|
|
def OnGround(car, distance=-1):
|
|
|
|
# Returns whether the car
|
|
# in on the ground or not.
|
|
|
|
# Getting the relative position of the ground
|
|
down = RelativePoint(car, [0,0,distance])
|
|
|
|
# See if there is grown under the car
|
|
ray = car.rayCast(down, poly=True)
|
|
|
|
# If nothing is sensed, we return None
|
|
if not ray[1]:
|
|
return None
|
|
|
|
# Else we return the material of the object underneith
|
|
# Useful to see on what kind of ground the car is.
|
|
else:
|
|
try: return ray[3].material
|
|
# If there is no material data it will game the name
|
|
# of the object
|
|
except: return ray[0].name
|
|
|
|
def GroundPuffsOfSmoke(car):
|
|
|
|
# This will puff smoke from wheels
|
|
# if driving on anything with "ground" in
|
|
# it's name.
|
|
|
|
if "ground" in str(OnGround(car)).lower():
|
|
speed = min(1, GetSpeed(car) / 30)
|
|
for wheel in car["wheels"]:
|
|
pos = wheel.position.copy()
|
|
Destruction.Dust(pos, 0.05*speed)
|
|
|
|
def CloseToCamera(car, d=50):
|
|
|
|
cam = bge.logic.getCurrentScene().active_camera
|
|
distance = car.getDistanceTo(cam)
|
|
|
|
return distance < d
|
|
|
|
def OnCollision(car, obj, point, normal, points):
|
|
|
|
# This function is responsible for
|
|
# breaking the car when it hits things.
|
|
|
|
#print("--------", car, obj)
|
|
|
|
if not CloseToCamera(car):
|
|
return
|
|
|
|
# We have a set of objects called separators
|
|
# for bots to see the middle of the road.
|
|
# we want to ignore those for this calculation.
|
|
if "Separation" in str(obj) or "Dani" in str(obj):
|
|
return
|
|
|
|
if car.get("abort-resque", 0) > 10:
|
|
car["abort-resque"] -= 10
|
|
|
|
# If we are hitting another car
|
|
# make that other car also get damage:
|
|
if obj in bge.logic.globalDict["allcars"]:
|
|
if not obj.get("active") and not Opt.GoodFPS("Collision"):
|
|
OnCollision(obj, car, point, normal, points)
|
|
|
|
# To find whether we want to do sparkles or
|
|
# dust, we need to find which material we
|
|
# collide with. Unfortunately there is no
|
|
# simple way to do it. But rather we will
|
|
# need to cast a ray toward the point.
|
|
|
|
# First we fire the ray
|
|
ray = car.rayCast(point, # Adding a bit to the distance
|
|
dist=car.getDistanceTo(point)+0.01, # to insure that
|
|
poly=True) # it will actually hit something.
|
|
|
|
# And then we extract the material from this operation.
|
|
if ray[3]: material = ray[3].material
|
|
else : material = ""
|
|
|
|
|
|
# For when we collide with water, we want to
|
|
# make it so the car will behave underwaterly.
|
|
if "water" in str(material).lower() or "water" in str(obj).lower():
|
|
|
|
# Setting car as underwater
|
|
if not car.get("underwater"):
|
|
car["underwater"] = True
|
|
if car.get("driver"):
|
|
car["driver"]["underwater"] = True
|
|
|
|
# Splashing
|
|
if not car.get("splashtimer"):
|
|
z = car.getLinearVelocity()[2]
|
|
y = car.localLinearVelocity[2]
|
|
if z < 0: z *= -1
|
|
if y < 0: y *= -1
|
|
force = z + y
|
|
Destruction.WaterSplash(car.position.copy(),
|
|
1,
|
|
car.orientation.to_euler()[2],
|
|
force)
|
|
car["splashtimer"] = 10
|
|
else:
|
|
car["splashtimer"] -= 1
|
|
return
|
|
|
|
|
|
# If the car already blew up we don't want
|
|
# to continue breaking it
|
|
if car.get("blown"):
|
|
return
|
|
|
|
v = -car.localLinearVelocity[1]
|
|
|
|
# Trying to optimize the colisions a bit
|
|
if not car.get("active") and not obj.get("active") and not Opt.GoodFPS("NPC Collision", 0.8):
|
|
return
|
|
elif not Opt.GoodFPS("Active Collision", 0.5) and v < 20:
|
|
return
|
|
|
|
|
|
|
|
# Making sure not to count close calls if you
|
|
# actually hit something.
|
|
|
|
bge.logic.globalDict["closeCallIgnore"] = True
|
|
|
|
# Now for every detected collision at the moment.
|
|
for point in list(points)[:2]:
|
|
|
|
force = point.appliedImpulse / car.mass / 2
|
|
|
|
if UpSideDown(car) and not OnGround(car):
|
|
force = 3
|
|
|
|
if not car.get("active") and force < 10:
|
|
force *= 3
|
|
force = min(50, force)
|
|
|
|
if obj in bge.logic.globalDict["allcars"]:
|
|
|
|
force *= 2
|
|
force = max(3, min(50, force))
|
|
|
|
# Under a low enough value we ignore everything
|
|
if force < 0.025:
|
|
|
|
force = 0.5
|
|
|
|
# Camera shake
|
|
car["shake"] = max(car.get("shake",0), force)
|
|
|
|
|
|
# Nitro
|
|
#AddNitro(car, force/20)
|
|
|
|
return
|
|
else:
|
|
|
|
# If we colide with ground, do dust
|
|
# if "ground" in str(material).lower():
|
|
# Destruction.Dust(point.worldPoint,
|
|
# 0.05 * min(1, force/20))
|
|
#else: # do particles.
|
|
if car.get("active") or Opt.GoodFPS("particles"):
|
|
Destruction.particles("Sparkle", point.worldPoint,
|
|
despawn = random.randint(30, 100),
|
|
amount = random.randint(0, 3),
|
|
max = 50,
|
|
spread = 0,
|
|
normal = 0.04 * min(1, force),
|
|
velocity = car.getLinearVelocity() * 0.015)
|
|
|
|
car["health"] = max(0, car["health"] - ( force / 100 ))
|
|
|
|
|
|
# Scrape sound
|
|
if force < 0.1:
|
|
ScrapeSound(car, position=point.worldPoint, volume=max(1,min(3,force*200)))
|
|
else:
|
|
HitSound(car, position=point.worldPoint, volume=min(3,force/2))
|
|
|
|
# For each wheel, we are going to specify health as well
|
|
# so that the car to loose handling as well as engine power
|
|
# due to breaking.
|
|
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
|
|
wheelObject = car["wheels"][n]
|
|
if ( wheel["xyz"][0] < 0 ) == ( point.localPointA[0] < 0 ) \
|
|
and ( wheel["xyz"][1] < 0 ) == ( point.localPointA[1] < 0 ) :
|
|
wheelObject["health"] = max(0, wheelObject["health"] - ( force / 50 ))
|
|
|
|
# Popping the wheel.
|
|
|
|
# Disconneting whe wheel from the car is impossible with the
|
|
# current implementation of the Vehicle constraint. But we
|
|
# can do the next best thing. Hiding it and spawing a new
|
|
# wheel in it's stead.
|
|
|
|
if wheelObject["health"] < 0.1 and wheelObject.visible:
|
|
|
|
wheelObject.visible = False
|
|
|
|
newWheel = Reuse.Create(wheelObject.name, 500)
|
|
newWheel.position = wheelObject.position
|
|
newWheel.orientation = wheelObject.orientation
|
|
newWheel.scaling = wheelObject.scaling
|
|
ColorPart(car, newWheel)
|
|
|
|
# Now a little physics cheat to make this pop
|
|
# more exciting! Let the wheel have the same
|
|
# initial velocity as the car.
|
|
|
|
newWheel.worldLinearVelocity = car.worldLinearVelocity
|
|
|
|
# And of course a Poping sound
|
|
|
|
WheelPopSound(car, newWheel.position)
|
|
|
|
# Opening and destroying doors
|
|
if force > 0.5:
|
|
closestDistance = 100
|
|
for door in car["doors"]:
|
|
distance = door.getDistanceTo(point.worldPoint)
|
|
if distance < closestDistance:
|
|
closestDistance = distance
|
|
thedoor = door
|
|
|
|
# Opening the door
|
|
try:
|
|
thedoor["locked"] = False
|
|
thedoor["health"] = max(0, thedoor["health"] - ( force / 20 ))
|
|
if "Borked" in thedoor:
|
|
#print(consoleForm(car), "replacing", thedoor, "to borked")
|
|
thedoor.replaceMesh(thedoor["Borked"])
|
|
GlassSound(car, position=car.position, volume=3)
|
|
except:
|
|
pass
|
|
|
|
|
|
# Consequences to the car
|
|
if car["health"] < 0.3:
|
|
ChangeBody(car, good=False)
|
|
RemoveSpoiler(car)
|
|
|
|
# Explosion
|
|
if car["health"] < 0.03 and not car.get("blown"):
|
|
BlowUp(car)
|
|
|
|
# Camera shake
|
|
car["shake"] = car.get("shake",0) + force
|
|
car["crash"] = force
|
|
if car.get("active"):
|
|
Music.HeatUp(force/50)
|
|
|
|
|
|
# Nitro
|
|
#AddNitro(car, force/20)
|
|
|
|
# Angry Pursuit
|
|
if car.get("npc") == "npc" and obj in bge.logic.globalDict["allcars"]\
|
|
and obj.get("target") != car:
|
|
car["target"] = obj
|
|
car["npc"] = "pursuit"
|
|
|
|
# Voice
|
|
StartEnemySounds(car)
|
|
|
|
elif car.get("npc") == "pursuit" and obj == car.get("target"):
|
|
|
|
HitEnemySound(car)
|
|
|
|
def BlowUp(car):
|
|
|
|
# Blowing up the car
|
|
if car.get("blown"):
|
|
return
|
|
|
|
Destruction.Explosion(car.worldPosition.copy(), mass=50, size=30)
|
|
car["blown"] = True
|
|
|
|
ChangeBody(car, good=False)
|
|
RemoveSpoiler(car)
|
|
RemoveNitroCan(car)
|
|
RemoveFireBall(car)
|
|
|
|
# Push everybody out of the car
|
|
if car.get("driver"):
|
|
car["npc"] = ""
|
|
character = car["driver"]
|
|
Character.getOutCar(character)
|
|
car.worldLinearVelocity = [0,0,10]
|
|
car.localAngularVelocity = [10,0,0]
|
|
|
|
character["carblewup"] = car
|
|
character["target"] = car.get("target")
|
|
|
|
for p in car.get("passangers", []):
|
|
Character.getOutCar(p)
|
|
|
|
# Detatch all wheels.
|
|
for n, wheel in enumerate(car["specs"]["wheels"]):
|
|
# Detatching all wheels:
|
|
wheelObject = car["wheels"][n]
|
|
wheelObject.visible = False
|
|
wheelObject["health"] = 0
|
|
newWheel = Reuse.Create(wheelObject.name, 500)
|
|
newWheel.position = wheelObject.position
|
|
newWheel.orientation = wheelObject.orientation
|
|
newWheel.scaling = wheelObject.scaling
|
|
ColorPart(car, newWheel)
|
|
WheelPopSound(car, newWheel.position)
|
|
|
|
# Detach all doors
|
|
for door in car.get("doors",[]):
|
|
door["health"] = 0
|
|
SwingDoors(car)
|
|
|
|
print(consoleForm(car), "Exploded")
|
|
|
|
def Material(car, value=(0,1,0), attribute="MainColor"):
|
|
|
|
# This function edits car's attributes in the blender space
|
|
# mainly to edit the parameters of materials.
|
|
|
|
if "colors" not in car:
|
|
car["colors"] = {}
|
|
|
|
car["colors"][attribute] = value
|
|
|
|
# Getting objects
|
|
for obj in car.childrenRecursive:
|
|
ColorPart(car, obj)
|
|
|
|
for wheel in car.get("wheels", []):
|
|
ColorPart(car, wheel)
|
|
|
|
# Neon
|
|
if attribute == "SecondaryColor" and car in bge.logic.globalDict["cars"]:
|
|
|
|
if "Neon" not in car:
|
|
light = Reuse.Create("NeonLamp")
|
|
light.blenderObject.data = light.blenderObject.data.copy()
|
|
light.position = car.position
|
|
light.setParent(car)
|
|
car["Neon"] = light
|
|
|
|
light = car["Neon"]
|
|
light.blenderObject.data.color = value
|
|
light.blenderObject.data.use_shadow = False
|
|
|
|
def SmartColor(car, color=None):
|
|
|
|
# Coloring the car. This is not easy
|
|
# since we have multiple possible attributes.
|
|
|
|
# If there is nothing passed into the color.
|
|
# we choose the default color for the car.
|
|
if color == None:
|
|
for attribute in car["specs"].get("material", []):
|
|
value = car["specs"]["material"][attribute]
|
|
Material(car, value, attribute)
|
|
|
|
elif color == "random":
|
|
for attribute in ["MainColor", "SecondaryColor"]:
|
|
value = (random.random(),
|
|
random.random(),
|
|
random.random())
|
|
Material(car, value, attribute)
|
|
|
|
elif color == "pallete":
|
|
|
|
# This is a bit more comlex to make the colors
|
|
# look good with one another. But this option
|
|
# is trying to do just that.
|
|
|
|
maincol = mathutils.Color((0.5,0.5,0.5))
|
|
secondcol = mathutils.Color((0.5,0.5,0.5))
|
|
|
|
# We gonna select whether it is low or high
|
|
# saturation first.
|
|
|
|
maincol.s = numpy.random.choice([0, 1], p=[0.6,0.4])
|
|
|
|
# Let's deal with gray scale first
|
|
if not maincol.s:
|
|
|
|
# We don't ever want the car to be white
|
|
maincol.v = random.choice([0.001,0.65])
|
|
|
|
# If it is closer to black we want to select a
|
|
# strong color for the secondary. Otherwise secondary
|
|
# is white.
|
|
if maincol.v < 0.2:
|
|
secondcol.s = 1
|
|
secondcol.v = 1
|
|
secondcol.h = random.random()
|
|
else:
|
|
secondcol.s = 2
|
|
secondcol.v = 1
|
|
secondcol.h = random.random()
|
|
|
|
|
|
else:
|
|
|
|
maincol.h = random.random()
|
|
maincol.v = random.random()
|
|
|
|
|
|
# For this the secondary color is complex.
|
|
# Since it is very specific and not everything
|
|
# looks good together.
|
|
|
|
if maincol.h < 0.16: # Red and orange.
|
|
secondcol.s = 1
|
|
secondcol.v = 1
|
|
secondcol.h = random.uniform(maincol.h, 0.16)
|
|
|
|
elif maincol.h < 0.333: # Green to yellowish
|
|
secondcol.s = 1
|
|
secondcol.v = 1
|
|
secondcol.h = random.uniform(0.16, 0.3)
|
|
|
|
elif maincol.h < 0.6: # Green-Blue to desaturated.
|
|
secondcol.s = random.uniform(0,0.7)
|
|
secondcol.v = 1
|
|
secondcol.h = maincol.h
|
|
|
|
elif maincol.h < 0.67: # Blue to Green-Blueish.
|
|
secondcol.s = 1
|
|
secondcol.v = 1
|
|
secondcol.h = random.uniform(0.444, 0.67)
|
|
|
|
else: # Magenta and pink to desaturated.
|
|
secondcol.s = random.uniform(0,0.7)
|
|
secondcol.v = 1
|
|
secondcol.h = maincol.h
|
|
|
|
color = {"MainColor":maincol, "SecondaryColor":secondcol}
|
|
|
|
for attribute in color:
|
|
value = color[attribute]
|
|
Material(car, value, attribute)
|
|
|
|
def ColorPart(car, part):
|
|
|
|
# This function colors a specific part
|
|
# of the car, into the colors of the car.
|
|
|
|
for attribute in car.get("colors", []):
|
|
value = car["colors"][attribute]
|
|
part.blenderObject[attribute] = value
|
|
|
|
def GetColor(car):
|
|
|
|
try: return car["colors"]["MainColor"]
|
|
except: [1,0,0]
|
|
|
|
def YellowBlue():
|
|
|
|
# Gets random hue of yellows and blues.
|
|
off = random.uniform(0, 0.12)
|
|
yl = random.choice((0, 0.5))
|
|
return yl+off
|
|
|
|
def ChangeBody(car, good=False):
|
|
|
|
# This function changes the car body.
|
|
|
|
# Getting objects
|
|
#rig = car.children[car.get("rig","RIG")]
|
|
body = car.childrenRecursive["Car_body"]
|
|
|
|
# If we are breakingt he car, record what
|
|
# we had before so we could fix it later.
|
|
if not good:
|
|
if "good" not in body:
|
|
body["good"] = body.meshes[0].name
|
|
|
|
# And then execute the change of mesh
|
|
if not body.get("mesh", "Good") == "Borked":
|
|
body.replaceMesh(car.get("borked", "Borked"))
|
|
GlassSound(car, position=car.position, volume=3)
|
|
body["mesh"] = "Borked"
|
|
|
|
if "Neon" in car:
|
|
car["Neon"].blenderObject.data.energy = 0
|
|
|
|
# Otherwise restore the car
|
|
else:
|
|
try:
|
|
if not body.get("mesh", "Good") == "Good":
|
|
body.replaceMesh(body["good"])
|
|
body["mesh"] = "Good"
|
|
except:
|
|
pass
|
|
|
|
RemoveFireBall(car)
|
|
|
|
|
|
if "Neon" in car:
|
|
car["Neon"].blenderObject.data.energy = 1000
|
|
|
|
def SmokeAndFire(car):
|
|
|
|
# This function drawns smoke and fire
|
|
if not car.get("underwater"):
|
|
health = car.get("health", 1)
|
|
smokeCoefitient = max(0, 1-health*2)*0.1
|
|
fireCoefitient = max(0, 1-health*4)*0.1
|
|
smokeemiter = car.children["SmokeEmmiter"]
|
|
Destruction.Smoke(smokeemiter.position, smokeCoefitient, smokeemiter.scaling.x)
|
|
Destruction.Fire(smokeemiter.position, fireCoefitient)
|
|
|
|
if "FireBall" not in car and health < 0.3:
|
|
car["FireBall"] = Destruction.AttatchFireBall(smokeemiter.position,
|
|
smokeemiter,
|
|
1.5)
|
|
car["FireBall"].scaling = smokeemiter.scaling
|
|
else:
|
|
RemoveFireBall(car)
|
|
|
|
def RemoveFireBall(car):
|
|
|
|
if "FireBall" in car:
|
|
Destruction.DeleteFireBall(car["FireBall"])
|
|
del car["FireBall"]
|
|
|
|
def Fix(car):
|
|
|
|
# Fixing the car. Restoring
|
|
# the car to the unbroken,
|
|
# 100% health, state.
|
|
|
|
# Restoring health values
|
|
car["health"] = 1
|
|
car["blown"] = False
|
|
for wheel in car.get("wheels", []):
|
|
wheel["health"] = 1
|
|
wheel.visible = True
|
|
|
|
# Restoring the doors.
|
|
for door in car.get("doors",[]):
|
|
|
|
# Restoring rotation of the door
|
|
rot = door.orientation.to_euler()
|
|
for i in range(3):
|
|
if i == door.get("axis",2):
|
|
rot[i] = door.get("lockat", 0)
|
|
else:
|
|
rot[i] = 0
|
|
|
|
door.orientation = rot
|
|
door["locked"] = True
|
|
|
|
# Restoring the shape
|
|
door.visible = True
|
|
if "Good" in door:
|
|
door.replaceMesh(door["Good"])
|
|
door.suspendPhysics()
|
|
|
|
door["health"] = 1.0
|
|
|
|
# Restoring the body work.
|
|
ChangeBody(car, good=True)
|
|
|
|
# Flipping it over
|
|
|
|
rot = car.orientation.to_euler()
|
|
rot.y = 0
|
|
rot.x = 0
|
|
car.orientation = rot
|
|
|
|
def InView(car):
|
|
|
|
# This function will determen if the
|
|
# car is visible from the perspective
|
|
# of the active camera.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
dani = scene.objects["Dani_Box"]
|
|
|
|
camera = scene.active_camera
|
|
deleteDistance = 400
|
|
|
|
# To avoid false positives, any car very
|
|
# close to the camera will be flagged as
|
|
# visible. Even if it is not.
|
|
|
|
if car.getDistanceTo(camera) < 100:
|
|
return True
|
|
|
|
# Then we check whether the car is farther
|
|
# then the delete distance ( in which case
|
|
# it will soon despawn anyway ).
|
|
|
|
if car.getDistanceTo(camera) > deleteDistance:
|
|
return False
|
|
|
|
# If chasing dani.
|
|
if car in IsChased(dani.get("driving")):
|
|
return True
|
|
|
|
# Next we check if the car is toward the direction
|
|
# of the camera.
|
|
|
|
if not camera.pointInsideFrustum(car.worldPosition) == camera.INSIDE:
|
|
return False
|
|
|
|
# And finally we check if any visible object
|
|
# is obscuring the car.
|
|
|
|
try:
|
|
obj = camera.rayCastTo(car)
|
|
if obj not in [car]+list(car.childrenRecursive) and obj.visible:
|
|
return False
|
|
except:
|
|
pass
|
|
|
|
# If all tests are passed, the car is visible.
|
|
return True
|
|
|
|
def GetPhysicsForces(car):
|
|
|
|
# It would be great if UPBGE had a way
|
|
# to measure forces on the car. But
|
|
# there isn't such a way, so I will make
|
|
# my stupid attemt at appriximating them.
|
|
|
|
# To do it I will be comparing linear velocity
|
|
# of the car, to linear velocity car used
|
|
# to have a frame ago. This multiplying by the
|
|
# FPS will result in a crude approximation of
|
|
# the forces that are acting on the car.
|
|
|
|
v = car.localLinearVelocity
|
|
pv = car.get("prevLocalLinearVelocity", mathutils.Vector((0,0,0)))
|
|
|
|
car["localLinearForces"] = ( v - pv ) * bge.logic.getAverageFrameRate()
|
|
|
|
car["prevLocalLinearVelocity"] = v.copy()
|
|
|
|
def SwingDoors(car):
|
|
|
|
# Doors and other hinged objects might
|
|
# be swang around by the momentum of
|
|
# the car.
|
|
|
|
reference = ( car["localLinearForces"] + car.localLinearVelocity ) / 2
|
|
|
|
|
|
for door in car.get("doors",[]):
|
|
|
|
# If locked, skip
|
|
if door.get("locked", True) or not door.visible:
|
|
continue
|
|
|
|
# Getting door data
|
|
minrot = door.get("minrot", 0)
|
|
maxrot = door.get("maxrot", math.pi/2)
|
|
lockat = door.get("lockat", 0)
|
|
breakat= door.get("breakat", math.pi/2)
|
|
axis = door.get("axis", 2)
|
|
factor = door.get("factor", 1.0)
|
|
offset = door.get("offset", math.pi)
|
|
|
|
# Rotating door
|
|
rot = door.localOrientation.to_euler()
|
|
rot[axis] = (math.atan2(*(i for n, i in enumerate(reference) if n != axis )) + offset ) * factor
|
|
for n in range(3):
|
|
if n != axis:
|
|
rot[n] = 0
|
|
|
|
# Gravity assisted autolocking.
|
|
# For hoods and things like that.
|
|
# Where not moving means that gravity pushes
|
|
# the door down.
|
|
|
|
if door.get("gravity", axis != 2):
|
|
|
|
if axis == 1: gravityaxis = 2
|
|
else : gravityaxis = 1
|
|
|
|
|
|
gravityfactor = reference[gravityaxis]
|
|
if gravityfactor < 0: gravityfactor *= -1
|
|
gravityfactor = min(30, gravityfactor)/30
|
|
gravityfactor = 1 - gravityfactor
|
|
|
|
rot[axis] = rot[axis]+((lockat - rot[axis])*gravityfactor)
|
|
|
|
|
|
# Rotation clamping.
|
|
maxrot -= minrot
|
|
rot[axis] -= minrot
|
|
if rot[axis] < 0: rot[axis] += math.pi*2
|
|
|
|
if rot[axis] > (maxrot/2)+math.pi:
|
|
rot[axis] = 0
|
|
|
|
|
|
elif rot[axis] > maxrot:
|
|
rot[axis] = maxrot
|
|
|
|
|
|
rot[axis] += minrot
|
|
|
|
# Damping the effect a little.
|
|
oldrot = door.localOrientation.to_euler()[axis]
|
|
diff = ( rot[axis] - oldrot )
|
|
if diff > math.pi: diff -= math.pi*2
|
|
elif diff < -math.pi: diff += math.pi*2
|
|
rot[axis] = oldrot + ( diff / 5 )
|
|
|
|
# Applying rotation.
|
|
door.localOrientation = rot
|
|
|
|
# Locking door
|
|
if door["health"] > 0.5 and round(rot[axis],2) == round(lockat, 2):
|
|
door["locked"] = True
|
|
rot[axis] = lockat
|
|
|
|
# Detaching the door
|
|
if round(rot[axis],3) == round(breakat, 3):
|
|
door["health"] -= 0.003
|
|
|
|
if door["health"] < 0.1:
|
|
|
|
door.visible = False
|
|
|
|
newdoor = Reuse.Create(door.name, 500)
|
|
if "Borked" in door:
|
|
newdoor.replaceMesh(door["Borked"])
|
|
newdoor.position = door.position
|
|
newdoor.orientation = door.orientation
|
|
newdoor.worldLinearVelocity = car.worldLinearVelocity / 1.5
|
|
ColorPart(car, newdoor)
|
|
|
|
def CloseCall(car):
|
|
|
|
# This function will measure close calls
|
|
# of almost colisions to other cars. And
|
|
# give rewards to the player based on them.
|
|
|
|
allcars = bge.logic.globalDict["allcars"]
|
|
|
|
|
|
|
|
for othercar in allcars:
|
|
if othercar != car:
|
|
distance = car.getDistanceTo(othercar)
|
|
|
|
if distance < 7:
|
|
|
|
# Ignoring if actually hit something, see OnCollision()
|
|
if bge.logic.globalDict.get("closeCallIgnore"):
|
|
bge.logic.globalDict["closeCallScore"] = 0
|
|
bge.logic.globalDict["closeCallCar"] = None
|
|
break
|
|
|
|
# Calculating score
|
|
v = -car.localLinearVelocity[1]
|
|
if v < 0: v *= -1
|
|
score = ( -( distance - 7 ) * v ) / 100
|
|
|
|
if score > bge.logic.globalDict.get("closeCallScore", 0):
|
|
bge.logic.globalDict["closeCallScore"] = score
|
|
bge.logic.globalDict["closeCallCar"] = othercar
|
|
|
|
|
|
elif bge.logic.globalDict.get("closeCallCar") == othercar:
|
|
|
|
score = bge.logic.globalDict["closeCallScore"]
|
|
bge.logic.globalDict["closeCallScore"] = 0
|
|
bge.logic.globalDict["closeCallCar"] = None
|
|
|
|
Music.HeatUp(score / 5)
|
|
|
|
if score > 1:
|
|
bge.logic.globalDict["print"] = "Oh god"+("!"*int(round(score)))
|
|
DaniOhGodSound(car)
|
|
Money.Recieve(score*25)
|
|
HornSound(othercar)
|
|
|
|
if score > 2:
|
|
PolicePursuit(car)
|
|
|
|
car["shake"] = score
|
|
car["near"] = score
|
|
|
|
|
|
|
|
else:
|
|
bge.logic.globalDict["closeCallIgnore"] = False
|
|
|
|
def NearRoad(car):
|
|
|
|
# Calculates how close you are to a road.
|
|
spawnAtDistance = bge.logic.globalDict["spawnAtDistance"]
|
|
addrs = Opt.Surround(car.position, spawnAtDistance)
|
|
closestLight = spawnAtDistance * 2
|
|
for addr in addrs:
|
|
for obj in Opt.chunks.get(addr, {}).get("objects", []):
|
|
if obj["name"] == "LightStand":
|
|
d = car.getDistanceTo(obj["position"])
|
|
if d < closestLight:
|
|
closestLight = d
|
|
return closestLight
|
|
|
|
##### PAPSES TRUCK SPECIFIC FUNCTIONS ###
|
|
|
|
def ChangeCargo(car):
|
|
|
|
# Making sure it is a truck
|
|
if "Truck" not in car.name:
|
|
return
|
|
|
|
# Telling the Updater function whether the
|
|
# truck should be unloaded or not.
|
|
car["be_unloaded"] = not car.get("be_unloaded", False)
|
|
|
|
def UnloadCargo(car):
|
|
|
|
# This function unloads cargo if the car
|
|
# is the truck.
|
|
|
|
# Making sure it is a truck
|
|
if "Truck" not in car.name:
|
|
return
|
|
|
|
rig = car.children[car.get("rig","RIG")]
|
|
anim = "Papses_Truck_RigAction"
|
|
|
|
|
|
cargoray = car.childrenRecursive["TruckCargoRay"]
|
|
cargomover = car.childrenRecursive["TruckCargoMover"]
|
|
# 1.28613 m
|
|
|
|
if car.get("unloaded"):
|
|
rig.playAction(anim, 0, 50)
|
|
|
|
|
|
else:
|
|
rig.playAction(anim, 50, 101)
|
|
|
|
def UpdateCargo(car):
|
|
|
|
# This runs on trucks.
|
|
# The function updates the cargo area.
|
|
|
|
# Getting objects related to the operation
|
|
rig = car.children[car.get("rig","RIG")]
|
|
anim = "Papses_Truck_RigAction"
|
|
cargoray = car.childrenRecursive["TruckCargoRay"]
|
|
cargomover = rig.channels["Down_Part"]
|
|
chain = car.childrenRecursive["Chain"]
|
|
backdoor = car.childrenRecursive["BackDoor"]
|
|
bdt = -(math.pi / 3 * 2)
|
|
|
|
# Getting states of the operation
|
|
beUnloaded = car.get("be_unloaded", False)
|
|
isUnloaded = car.get("is_unloaded", False)
|
|
isExtended = rig.getActionFrame(0) == 50
|
|
isShrunk = rig.getActionFrame(0) in [0,100]
|
|
|
|
|
|
|
|
# Extending the opertations
|
|
if not isExtended and beUnloaded:
|
|
rig.playAction(anim, 0, 50)
|
|
|
|
if not isUnloaded and not isShrunk and not beUnloaded:
|
|
rig.playAction(anim, 50, 100)
|
|
|
|
if isExtended and not isUnloaded and beUnloaded:
|
|
|
|
# Finding the ground
|
|
to = RelativePoint(cargoray, (0,0,-1) )
|
|
fro = cargoray.position
|
|
ray = BeautyRayCast(car, "left", to, fro, dist=100)
|
|
if ray[1]: d = cargoray.getDistanceTo(ray[1])
|
|
else: d = 100
|
|
|
|
# Moving the bone down
|
|
loc = cargomover.location
|
|
if d > 0.5 and d != 100:
|
|
loc.x += 0.05
|
|
cargomover.location = loc
|
|
|
|
# Extending chain
|
|
chain.blenderObject.modifiers["Array"].fit_length += 0.05
|
|
|
|
# Unrolling the door
|
|
backdoor["locked"] = True
|
|
if loc.x > 0.4:
|
|
r = backdoor.localOrientation.to_euler()
|
|
if r.y > bdt: r.y -= 0.1
|
|
else: r.y = bdt
|
|
backdoor.localOrientation = r
|
|
|
|
# Telling the system that it's done.
|
|
else:
|
|
|
|
# Cargo
|
|
if car.get("cargo"):
|
|
c = car["cargo"]
|
|
c.removeParent()
|
|
c.restorePhysics()
|
|
c["isCargo"] = False
|
|
car["cargo"] = False
|
|
|
|
car.mass -= c.mass
|
|
|
|
c.position = cargoray.position
|
|
c.position.z += 1.5
|
|
c.orientation = car.orientation
|
|
|
|
for wheel in c["wheels"]:
|
|
wheel.visible = True
|
|
|
|
car["is_unloaded"] = True
|
|
|
|
r = backdoor.localOrientation.to_euler()
|
|
r.y = bdt
|
|
backdoor.localOrientation = r
|
|
|
|
if isExtended and isUnloaded and not beUnloaded:
|
|
|
|
# Getting a cargo
|
|
if not car.get("cargo"):
|
|
|
|
# Looking through all cars.
|
|
for c in bge.logic.globalDict["allcars"]:
|
|
if c.getDistanceTo(cargoray) < 2:
|
|
PackCargo(car, c)
|
|
|
|
|
|
# Moving the bone up
|
|
loc = cargomover.location
|
|
if loc.x > 0:
|
|
loc.x -= 0.05
|
|
cargomover.location = loc
|
|
|
|
# Retracting chain
|
|
chain.blenderObject.modifiers["Array"].fit_length -= 0.05
|
|
|
|
# Closing the door
|
|
backdoor["locked"] = True
|
|
r = backdoor.localOrientation.to_euler()
|
|
if r.y < 0: r.y += 0.1
|
|
else: r.y = 0
|
|
backdoor.localOrientation = r
|
|
|
|
# Telling the system that it's done.
|
|
else:
|
|
|
|
|
|
|
|
loc.x = 0
|
|
cargomover.location = loc
|
|
chain.blenderObject.modifiers["Array"].fit_length = 0
|
|
car["is_unloaded"] = False
|
|
|
|
r = backdoor.localOrientation.to_euler()
|
|
r.y = 0
|
|
backdoor.localOrientation = r
|
|
|
|
def PackCargo(car, c):
|
|
|
|
cargoray = car.childrenRecursive["TruckCargoRay"]
|
|
|
|
c["isCargo"] = True
|
|
car["cargo"] = c
|
|
car.mass += c.mass
|
|
|
|
c.position = cargoray.position
|
|
c.orientation = car.orientation
|
|
|
|
c.suspendPhysics()
|
|
c.setParent(cargoray, False, False)
|
|
|
|
for wheel in c["wheels"]:
|
|
wheel.visible = False
|
|
|
|
print("Packed", consoleForm(c), "into", consoleForm(car))
|
|
|
|
|
|
##### SPAWNING AND UNSPAWNING OF CARS ###
|
|
|
|
def RegisterSpawnPoint(object):
|
|
|
|
# Registering a car spawn object on Init.
|
|
|
|
spawnAtDistance = bge.logic.globalDict["spawnAtDistance"]
|
|
|
|
objectData = {"position": object.worldPosition.copy(),
|
|
"orientation": object.worldOrientation.copy(),
|
|
"spawn": object.get("spawn"),
|
|
"to_spawn": object["to_spawn"],
|
|
"race":object.get("race"),
|
|
"npc":object.get("npc"),
|
|
"priority":object.get("priority"),
|
|
"selected":object.blenderObject.select_get()}
|
|
|
|
if object.get("race"):
|
|
race = bge.logic.globalDict["races"][object.get("race")]
|
|
if "amount-racers" not in race:
|
|
race["amount-racers"] = 0
|
|
race["amount-racers"] += 1
|
|
|
|
addr = Opt.Address(objectData["position"], spawnAtDistance)
|
|
if addr not in bge.logic.globalDict["spawns"]:
|
|
bge.logic.globalDict["spawns"][addr] = []
|
|
|
|
bge.logic.globalDict["spawns"][addr].append(objectData)
|
|
|
|
# For testing we will ignore optimization for selected ones.
|
|
if objectData["selected"]:
|
|
|
|
if "selected" not in bge.logic.globalDict["spawns"]:
|
|
bge.logic.globalDict["spawns"]["selected"] = []
|
|
bge.logic.globalDict["spawns"]["selected"].append(objectData)
|
|
print("Selected", object)
|
|
|
|
object.endObject()
|
|
|
|
def SpawnLogic(camSurroundCars):
|
|
|
|
# Logic of autospanning cars.
|
|
# Challenges:
|
|
# Have enough cars.
|
|
# Have enough performace.
|
|
# Have enough variety.
|
|
|
|
settings = bge.logic.globalDict["settings"]
|
|
|
|
|
|
spawnedCarModels = bge.logic.globalDict["spawnedCarModels"]
|
|
spawnedCars = len( bge.logic.globalDict["allcars"] )
|
|
|
|
policeSpawnFactor = bge.logic.globalDict["policeSpawnFactor"]
|
|
notPoliceSpawnFactor = 1-policeSpawnFactor
|
|
|
|
maxCars = settings.get("maxcars", 4)
|
|
cam = bge.logic.getCurrentScene().active_camera
|
|
dani = bge.logic.getCurrentScene().objects["Dani_Box"]
|
|
|
|
# First lets see how many cars are on screen
|
|
# right now.
|
|
# notinframe = []
|
|
# inframe = []
|
|
# for car in bge.logic.globalDict["allcars"]:
|
|
# if cam.pointInsideFrustum(car.position) == cam.INSIDE:
|
|
# inframe.append(car)
|
|
# else:
|
|
# notinframe.append(car)
|
|
|
|
# Getting all of the points that are close enough.
|
|
spawns = []
|
|
for i in camSurroundCars:
|
|
spawns += bge.logic.globalDict["spawns"].get(i, [])
|
|
|
|
# For testing
|
|
if "selected" in bge.logic.globalDict["spawns"]:
|
|
spawns = bge.logic.globalDict["spawns"]["selected"]
|
|
break
|
|
|
|
|
|
# Itterating the spawn points
|
|
spawnSorted = []
|
|
|
|
for spawn in spawns:
|
|
|
|
# If the camera is pointed ( roughly ) at it
|
|
vect = cam.getVectTo(spawn["position"])
|
|
vectfactor = vect[0] / 300
|
|
score = vect[0]
|
|
spawnSorted.append((score, spawn))
|
|
|
|
spawnSorted = sorted(spawnSorted)
|
|
|
|
# Model of the car to spawn.
|
|
carModel = str(numpy.random.choice(NPC_cars, p=NPC_cars_probability))
|
|
|
|
|
|
for n, spawn in enumerate(spawnSorted):
|
|
|
|
distance, spawn = spawn
|
|
|
|
doPolice = numpy.random.choice([True,
|
|
False],
|
|
p=[policeSpawnFactor,
|
|
notPoliceSpawnFactor])
|
|
if doPolice:
|
|
try:
|
|
carModel = random.choice(NPC_type_cars[dani.get("driving", {}).get("type", "race-car")])
|
|
except Exception as e: pass
|
|
|
|
# If it's way too close, abort
|
|
if len(spawnSorted) > n+1 and distance < 100 \
|
|
and spawn.get("npc") != "racer"\
|
|
and not spawn.get("priority") == True:
|
|
continue
|
|
|
|
# Spawn timer ( to avoid, over spawning )
|
|
if spawn.get("to_spawn_timer", 0) + 15 > bge.logic.getRealTime():
|
|
continue
|
|
spawn["to_spawn_timer"] = bge.logic.getRealTime()
|
|
|
|
|
|
# Checking race
|
|
if spawn.get("race"):
|
|
|
|
# Getting the race data
|
|
|
|
racename = spawn.get("race")
|
|
race = Racing.Races().get(racename,{})
|
|
racetype = race.get("type", "race-car")
|
|
try:
|
|
carModel = random.choice(NPC_type_cars[racetype])
|
|
except Exception as e:
|
|
pass
|
|
|
|
racecheck = Racing.Available(race, forspawn=True)
|
|
|
|
else:
|
|
racecheck = False
|
|
|
|
racespawn = ( spawn.get("npc") == "racer" and racecheck )
|
|
|
|
# For NPC cars
|
|
if ( ( spawn.get("to_spawn", True)
|
|
and ( racespawn or spawn.get("npc") != "racer" ) )
|
|
or spawn.get("npc") == "npc" ):
|
|
|
|
# Spawning the car.
|
|
|
|
driver = "Man1Box"
|
|
|
|
if not spawn.get("npc"):
|
|
carModel = spawn.get("spawn")
|
|
driver = None
|
|
|
|
car = Spawn(
|
|
carModel,
|
|
spawn["position"],
|
|
spawn["orientation"],
|
|
True,
|
|
"pallete",
|
|
spawnanyway=False,
|
|
spawn=spawn,
|
|
driver=driver)
|
|
|
|
if not car and (spawnedCars < maxCars or spawn.get("npc") == "racer"\
|
|
or spawn.get("priority")):
|
|
|
|
car = Spawn(
|
|
carModel,
|
|
spawn["position"],
|
|
spawn["orientation"],
|
|
True,
|
|
"pallete",
|
|
spawnanyway=True,
|
|
spawn=spawn,
|
|
driver=driver)
|
|
|
|
|
|
if not car: return
|
|
|
|
try:
|
|
if spawn.get("race") and dani.get("driving", {}).get("NitroCan"):
|
|
AddNitroCan(car)
|
|
car["nitro"] = 10
|
|
except:pass
|
|
|
|
try:
|
|
if spawn.get("race") and dani.get("driving", {}).get("Spoiler"):
|
|
AddSpoiler(car)
|
|
except:pass
|
|
|
|
if spawn.get("npc") == "npc":
|
|
if doPolice:
|
|
Policify(car)
|
|
|
|
|
|
car.setLinearVelocity([0,-15,0], True)
|
|
spawn["to_spawn"] = False
|
|
car["spawn"] = spawn
|
|
|
|
|
|
return
|
|
|
|
def Spawn(carname, position, orientation,
|
|
fix=True, # Whether to fix the car
|
|
color=None, # What color to assign to the car
|
|
spawnanyway=True, # Spawn it no matter what.
|
|
anything=False, # Spawn anything at all.
|
|
spawn=None, # Spawn data from SpawnLogic()
|
|
driver=None):
|
|
|
|
#traceback.print_stack()
|
|
|
|
# This function is to simplify spawning
|
|
# of the car.
|
|
|
|
cam = bge.logic.getCurrentScene().active_camera
|
|
|
|
|
|
# If we recieved a name of a car and not an object.
|
|
if type(carname) == str:
|
|
|
|
# Trying to get the car from already spawned
|
|
car = SafeDespawn(bge.logic.globalDict["allcars"],
|
|
carname,
|
|
anything=anything)
|
|
|
|
spawnedmessage = clr["tdgr"]+" Spawned Reused!"
|
|
|
|
# Making the car anyway.
|
|
if spawnanyway and not car:
|
|
car = Reuse.Create(carname)
|
|
|
|
spawnedmessage = clr["tdyl"]+" Spawned New!"+clr["norm"]+" ( "+str(len(bge.logic.globalDict["allcars"])+1)+" )"
|
|
|
|
# Otherwice return nothing.
|
|
elif not car:
|
|
return False
|
|
|
|
else:
|
|
car = carname
|
|
|
|
spawnedmessage = clr["tdbu"]+" Spawned by object input!"
|
|
|
|
if car.get("active"):
|
|
return car
|
|
|
|
if "spawn" in car:
|
|
car["spawn"]["to_spawn"] = True
|
|
|
|
Reuse.Move(car, position)
|
|
#car.position = position
|
|
car.orientation = orientation
|
|
car.worldLinearVelocity = [0,0,0]
|
|
car.worldAngularVelocity = [0,0,0]
|
|
|
|
# Putting the car in the list of all cars
|
|
if car not in bge.logic.globalDict["allcars"]:
|
|
bge.logic.globalDict["allcars"].append(car)
|
|
if car not in bge.logic.globalDict["cars"]:
|
|
bge.logic.globalDict["cars"].append(car)
|
|
|
|
car["nodespawn"] = 100
|
|
car["stuck"] = 300
|
|
|
|
# Removing the car from races if it used to be in one
|
|
for racename in bge.logic.globalDict["races"]:
|
|
race = bge.logic.globalDict["races"][racename]
|
|
try:
|
|
race["racers"].remove(car)
|
|
except:
|
|
pass
|
|
|
|
|
|
# If a spawn point is given
|
|
if spawn:
|
|
car["npc"] = spawn.get("npc")
|
|
car["anger"] = spawn.get("anger", random.random())
|
|
if spawn.get("race"):
|
|
car["race"] = spawn.get("race")
|
|
car["racing"]= False
|
|
race = bge.logic.globalDict["races"][spawn.get("race")]
|
|
if car not in race["racers"]:
|
|
race["racers"].append(car)
|
|
else:
|
|
car["npc"] = ""
|
|
car["race"] = ""
|
|
car["racing"] = False
|
|
|
|
# Removing stuff on new cars.
|
|
|
|
RemoveNetId(car)
|
|
|
|
try:
|
|
RemoveNitroCan(car)
|
|
StopNitro(car)
|
|
except: pass
|
|
car["nitro"] = 0.0
|
|
|
|
|
|
try: RemoveSpoiler(car)
|
|
except: pass
|
|
|
|
try: RemovePoliceLights(car)
|
|
except: pass
|
|
car["police"] = False
|
|
if car in bge.logic.globalDict["policeCars"]:
|
|
bge.logic.globalDict["policeCars"].remove(car)
|
|
|
|
print(consoleForm(car),clr["bold"], spawnedmessage, clr["norm"])
|
|
|
|
|
|
# Updating the car, to make sure everything
|
|
# is configured.
|
|
|
|
|
|
try:
|
|
PhysicsUpdate(car)
|
|
SmartColor(car, color)
|
|
if fix: Fix(car)
|
|
except:
|
|
pass
|
|
|
|
|
|
# Adding a driver
|
|
if driver:
|
|
|
|
if not car.get("driver"):
|
|
|
|
AddDriver(car, driver)
|
|
|
|
|
|
elif car.get("driver"):
|
|
|
|
print(consoleForm(car),clr["tdrd"], "Driver Deleted", clr["norm"])
|
|
|
|
driver = car["driver"]
|
|
car["driver"] = ""
|
|
Character.getOutCar(driver)
|
|
Reuse.Delete(driver)
|
|
|
|
car["netId"] = Multiplayer_Shared.RandomString()
|
|
print(consoleForm(car), consoleForm(car.get("driver")), car.get("npc"))
|
|
|
|
return car
|
|
|
|
def SafeDespawn(cars, model=None, anything=False):
|
|
|
|
# This function returns a despawned car from a list
|
|
# of input cars.
|
|
|
|
for car in cars:
|
|
|
|
# If we need a specific model skip anything else
|
|
if model and car.name != model:
|
|
continue
|
|
|
|
# Logic to determen whether its safe to despawn.
|
|
|
|
if ( not car.get("racing") # Not a racing car
|
|
and not car.get("active") # Not one Dani's drivin
|
|
and not InView(car) # Not on screen
|
|
and car.get("nodespawn", 0) < 1 # Not just spawned
|
|
and car not in Script.Story.values() # Not a story related car
|
|
):
|
|
|
|
|
|
# Deleting the car in an inactive way.
|
|
Reuse.Delete(car, inactive=True)
|
|
|
|
# Making sure it can be respawned
|
|
if car.get("spawnPoint"):
|
|
car["spawnPoint"]["to_spawn"] = True
|
|
|
|
# Removing the car from races
|
|
races = Racing.Races()
|
|
for racename in races:
|
|
race = races.get(racename, {})
|
|
try: race["racers"].remove(car)
|
|
except: pass
|
|
|
|
# Removing driver if there one
|
|
if car.get("driver"):
|
|
driver = car["driver"]
|
|
Character.getOutCar(driver)
|
|
Reuse.Delete(driver)
|
|
car["driver"] = None
|
|
|
|
# Removing it from the list of all cars.
|
|
if car in bge.logic.globalDict["allcars"]:
|
|
bge.logic.globalDict["allcars"].remove(car)
|
|
|
|
# Declaring that the car is despawned.
|
|
print(consoleForm(car),clr["bold"]+clr["tdgr"],"Safely Despawned!", clr["norm"])
|
|
|
|
# Returning the result
|
|
return car
|
|
|
|
# If we need anything at all, but the model car
|
|
# was not found. We try again, without the model
|
|
# in the arguments.
|
|
|
|
if anything:
|
|
return SafeDespawn(cars)
|
|
|
|
# If nothing found, returning False.
|
|
return False
|
|
|
|
def AddDriver(car, driver="Man1Box"):
|
|
|
|
cam = bge.logic.getCurrentScene().active_camera
|
|
|
|
if "drivers" not in bge.logic.globalDict:
|
|
bge.logic.globalDict["drivers"] = []
|
|
|
|
driver = Reuse.Create(driver)
|
|
bge.logic.globalDict["drivers"].append(driver)
|
|
SmartColor(driver, "random")
|
|
print(consoleForm(driver),clr["tdrd"], "new driver", clr["norm"])
|
|
|
|
Character.getIntoCar(driver, car, immediately=True)
|
|
|
|
def DespawnLogic(car):
|
|
|
|
# This function will despans the car when the car
|
|
# is outside of a visible range, for respawning it
|
|
# in a different place.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
|
|
# No despawn timer
|
|
if car.get("nodespawn", 0) > 0:
|
|
car["nodespawn"] -= 1
|
|
return
|
|
|
|
dani = scene.objects["Dani_Box"]
|
|
camera = scene.active_camera
|
|
deleteDistance = 400
|
|
|
|
if car.getDistanceTo(camera) > deleteDistance \
|
|
and not car.get("racing") \
|
|
and car in bge.logic.globalDict["allcars"] :
|
|
|
|
|
|
# This function will be scheduled in Opt
|
|
def RemovingTheCar(car, dani):
|
|
|
|
|
|
|
|
# If car is waiting for a race to start, we don't delete it
|
|
if car.get("npc") == "racer":
|
|
racename = car["race"]
|
|
race = bge.logic.globalDict["races"][racename]
|
|
starter = race["starters"][0]
|
|
if dani.getDistanceTo(starter["location"]) < deleteDistance or car["racing"]:
|
|
return
|
|
|
|
# if "spawn" in car:
|
|
# car["spawn"]["to_spawn"] = True
|
|
|
|
print("Removing Car:", car)
|
|
|
|
# Making sure it can be respawned
|
|
if car.get("spawnPoint"):
|
|
car["spawnPoint"]["to_spawn"] = True
|
|
|
|
# Removing the car from races
|
|
for racename in bge.logic.globalDict["races"]:
|
|
race = bge.logic.globalDict["races"][racename]
|
|
try:
|
|
race["racers"].remove(car)
|
|
except:
|
|
pass
|
|
|
|
if car in bge.logic.globalDict["allcars"]:
|
|
bge.logic.globalDict["allcars"].remove(car)
|
|
|
|
# Removing from cache if too many cars in cache
|
|
|
|
# TODO make this removing of cars actually work and
|
|
# not sig-fault
|
|
|
|
#if Reuse.reuse.get(car.name) or len(bge.logic.globalDict["allcars"]) > bge.logic.globalDict["maxCars"]:
|
|
# for i in car["wheels"]:
|
|
# i.endObject()
|
|
# car.suspendPhysics(True)
|
|
# Reuse.EndObject(car)
|
|
# Otherwise storing it for later.
|
|
#else:
|
|
|
|
Reuse.Delete(car)
|
|
|
|
# Scheduling
|
|
RemovingTheCar(car, dani)
|
|
|
|
def UnspawnLogic():
|
|
|
|
for car in bge.logic.globalDict["allcars"]:
|
|
|
|
if car in Reuse.reuse:
|
|
bge.logic.globalDict["allcars"].remove(car)
|
|
continue
|
|
|
|
try:
|
|
car.position
|
|
except:
|
|
bge.logic.globalDict["allcars"].remove(car)
|
|
|
|
|
|
##### AI NPCS AND RACERS FUNCTIONS ####
|
|
|
|
def NormalNPC(car):
|
|
|
|
# Redouing the NPC shannanigans
|
|
vturn, mindist = VisionTurn(car, True)
|
|
vturn *= 2#max(2,v)
|
|
|
|
|
|
t = vturn
|
|
if t < 0: t *= -1
|
|
anger = car.get("anger", 0.2)
|
|
v = -car.localLinearVelocity[1]
|
|
|
|
|
|
maxv = mindist / (t+1) * 1.5
|
|
|
|
# Stopping in front of Dani, making the cars more stealable.
|
|
dani = bge.logic.getCurrentScene().objects["Dani_Box"]
|
|
if not dani.get("driving"):
|
|
danivect = car.getVectTo(dani)
|
|
if danivect[0] < 10 and danivect[2][1] < -0.3:
|
|
maxv = 0
|
|
HornSound(car)
|
|
|
|
#The unstuck protocol
|
|
if v < 1 and car.get("stuck",0) < 1:
|
|
car["unstuck"] = 100
|
|
car["stuck"] = 300
|
|
car["stuckturn"] = -vturn * 100
|
|
|
|
elif car.get("stuck",0) > 0:
|
|
car["stuck"] -= 1
|
|
|
|
# If everything is okay moving forward
|
|
nitro = False
|
|
if maxv > v / 3 and car.get("unstuck", 0) < 1:
|
|
Accelerate(car)
|
|
nitro = True
|
|
|
|
# If turing or needing to go back
|
|
else:
|
|
Accelerate(car, -1)
|
|
if car.get("unstuck", 0) > 0:
|
|
car["unstuck"] -= 1
|
|
|
|
if car.get("unstuck", 0) < 1:
|
|
car["turn"] = vturn
|
|
#SoftTurn(car, vturn)
|
|
else:
|
|
car["turn"] = -vturn * 100
|
|
#SoftTurn(car, -vturn * 100)
|
|
|
|
|
|
# if nitro:
|
|
# StartNitro(car)
|
|
# else:
|
|
# StopNitro(car)
|
|
|
|
# PURSUIT CHEAT
|
|
scene = bge.logic.getCurrentScene()
|
|
dani = scene.objects["Dani_Box"]
|
|
|
|
for thecar in bge.logic.globalDict["cars"]:
|
|
if (bge.logic.globalDict["pursuit-cheat"] and dani.get("driving") == thecar) \
|
|
or (car in bge.logic.globalDict["policeCars"] and IsChased(thecar)):
|
|
car["target"] = thecar
|
|
car["npc"] = "pursuit"
|
|
|
|
def AngryPursuit(car):
|
|
|
|
# This is the code for NPC that are pursuing
|
|
# you.
|
|
|
|
# Getting the pursuit target
|
|
try:
|
|
target = car["target"]
|
|
|
|
# Deactivate Argy Pursuit if the target car is blown
|
|
if target.get("blown"):
|
|
car["npc"] = "npc"
|
|
|
|
# Getting the target car
|
|
elif not target.get("driver") and car.get("driver") and target in bge.logic.globalDict["cars"]:
|
|
driver = car["driver"]
|
|
Character.getOutCar(driver)
|
|
driver["NPCcar"] = target
|
|
car["npc"] = ""
|
|
|
|
if car.get("blown"):
|
|
car["npc"] = ""
|
|
|
|
except:
|
|
car["npc"] = "npc"
|
|
return
|
|
|
|
|
|
|
|
# Showing on the map
|
|
scene, car = GetSceneAndCar(car)
|
|
dani = scene.objects["Dani_Box"]
|
|
if target == dani.get("driving"):
|
|
Map.Show(car)
|
|
|
|
if car in bge.logic.globalDict["policeCars"]:
|
|
PoliceSound(car)
|
|
|
|
# Vector to the target
|
|
|
|
if True:#car.getDistanceTo(target) > 50:
|
|
try:
|
|
adjust = RelativePoint(target, (0 , target.localLinearVelocity.y , 0)) - target.worldPosition
|
|
adjust.rotate(mathutils.Euler(target.worldAngularVelocity*0.5))
|
|
#adjust = adjust * ( 1 / math.dist((0,0,0), adjust))
|
|
#adjust = adjust * ( math.dist(car.worldPosition, target.worldPosition) / 2 )
|
|
trueTarget = target.worldPosition + adjust# - car.worldLinearVelocity
|
|
|
|
except Exception as e:
|
|
print("AngryPursuit Error", e)
|
|
trueTarget = target
|
|
else:
|
|
|
|
trueTarget = target
|
|
|
|
#Tools.Boxify(trueTarget, True)
|
|
|
|
# To maximise fun we gonna make the police touch the
|
|
# player and get the hell out of there, and then come
|
|
# back for another high speed kick.
|
|
|
|
|
|
if car.get("angry_swing"):
|
|
trueTarget = RelativePoint(car, (0,-10,0))
|
|
if car.getDistanceTo(target) > 40:
|
|
car["angry_swing"] = 0
|
|
car["angry_swing"] -= 1
|
|
|
|
vect = car.getVectTo(trueTarget)
|
|
distance = vect[0]
|
|
ontarget = -vect[2][1]
|
|
|
|
if ( not car.get("angry_swing") and distance < 5 ):
|
|
car["angry_swing"] = 60
|
|
|
|
|
|
|
|
|
|
if ontarget < 0:
|
|
trueTarget = target
|
|
vect = car.getVectTo(trueTarget)
|
|
distance = vect[0]
|
|
ontarget = -vect[2][1]
|
|
|
|
# if distance > 200:
|
|
# TargetedNPC(car)
|
|
# return
|
|
|
|
# If following Dani, don't despwant as much
|
|
if vect[0] < 300 and target == dani.get("driving"):
|
|
car["nodespawn"] = 300
|
|
|
|
# Turn
|
|
pturn = vect[2][0] * 4
|
|
vturn, mindist = VisionTurn(car, True)
|
|
if vect[0] > 50 and ontarget > 0.7:
|
|
turn = (pturn*(1-ontarget)) + (vturn*ontarget)
|
|
else:
|
|
turn = pturn
|
|
|
|
nitro = False
|
|
if ontarget > -0.3:
|
|
|
|
if car["specs"]["redline"] > car["rpm"]:
|
|
Accelerate(car, 1)
|
|
else:
|
|
Accelerate(car, -1)
|
|
|
|
SoftTurn(car, turn)
|
|
if OnGround(car):
|
|
nitro = True
|
|
|
|
else:
|
|
Accelerate(car, -1)
|
|
SoftTurn(car, -turn)
|
|
#car["turn"] = -turn
|
|
|
|
if nitro:
|
|
v = -car.localLinearVelocity[1]
|
|
tv = -car["target"].localLinearVelocity[1]
|
|
#if v < tv or vect[0] < 50:
|
|
car["nitro"] = 10.0
|
|
StartNitro(car)
|
|
else:
|
|
StopNitro(car)
|
|
|
|
def RacingAI(car):
|
|
|
|
# This is the code for NPC that are pursuing
|
|
# you.
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
dani = scene.objects["Dani_Box"]
|
|
cam = scene.active_camera
|
|
|
|
|
|
onground = OnGround(car)
|
|
|
|
|
|
# Getting race info
|
|
try:
|
|
racename = car["race"]
|
|
except:
|
|
car["npc"] = "npc"
|
|
return
|
|
|
|
race = bge.logic.globalDict["races"][racename]
|
|
|
|
# Race position
|
|
try: pos = race["positions"].index(car)
|
|
except: pos = 0
|
|
|
|
|
|
|
|
# Making sure car has lap andcheckpoint info
|
|
if "lap" not in car:
|
|
car["lap"] = 0
|
|
if "checkpoint" not in car:
|
|
car["checkpoint"] = 0
|
|
|
|
# Getting next checkpoint and the one after that
|
|
checknumber = car["checkpoint"]
|
|
|
|
|
|
# Raceline calculation
|
|
if "line" not in car: # Reading from file
|
|
try:
|
|
#fold = Settings.get_settings_folder()
|
|
fold = "//racedata"
|
|
f = fold+"/"+"raceline_"+racename+".json"
|
|
with open(bge.logic.expandPath(f)) as o:
|
|
car["line"] = json.load(o)
|
|
except:
|
|
car["line"] = []
|
|
|
|
# Choosing the raceline of the fastest car ( user
|
|
# might be the fastest, then imitate user ).
|
|
# try: raceline = race["positions"][0]["line"]
|
|
# except:
|
|
# try: raceline = race["positions"][0]["driving"]["line"]
|
|
# except:raceline = car["line"]
|
|
raceline = car["line"]
|
|
|
|
|
|
if True:#not car.get("ignore_line"):
|
|
try:
|
|
rlcp = raceline[checknumber]+raceline[checknumber+1]
|
|
search = -min(-20, car.localLinearVelocity.y)
|
|
mind = search
|
|
racelineTarget = None
|
|
for node in rlcp:
|
|
d = math.dist( RelativePoint(car, (0, -search, 0)) , node)
|
|
#d = math.dist( car.worldPosition + car.worldLinearVelocity, node )
|
|
if d < mind:
|
|
mind = d
|
|
racelineTarget = node
|
|
except:
|
|
racelineTarget = None
|
|
else: racelineTarget = None
|
|
|
|
|
|
checkpoint1 = race["checkpoints"][checknumber]
|
|
try: checkpoint2 = race["checkpoints"][ checknumber + 1 ]
|
|
except: checkpoint2 = race["checkpoints"][ 0 ]
|
|
|
|
target = checkpoint1["location"]
|
|
target2 = checkpoint2["location"]
|
|
|
|
# Vector to the first checkpoint
|
|
vect = car.getVectTo(target)
|
|
distance = vect[0]
|
|
ontarget = -vect[2][1]
|
|
|
|
# Vector to the second checkpoint
|
|
vect2 = car.getVectTo(target2)
|
|
distance2 = vect2[0]
|
|
ontarget2 = -vect2[2][1]
|
|
|
|
# Getting recording data
|
|
targets = GetRacingTargets(car, racename, checknumber)
|
|
pretargets = GetRacingTargets(car, racename, checknumber - 1)
|
|
|
|
# Going to the next checkpoint ( when car is close enough to
|
|
# the current one ).
|
|
if vect[0] < checkpoint1["radius"]:
|
|
car["checkpoint"] += 1
|
|
|
|
car["ignore_line"] = False
|
|
|
|
if checkpoint1.get("OnLoop"):
|
|
car["abort-resque"] = 200
|
|
|
|
# Testing how well the car is doing
|
|
if targets.get("time"):
|
|
currentRaceTime = bge.logic.getRealTime() - race.get("start-time", 0)
|
|
timediff = targets["time"] - currentRaceTime
|
|
car["racetd"] = timediff - car.get("timediff",0)
|
|
car["timediff"] = timediff
|
|
|
|
ptdiff = str(round(car["racetd"], 1))
|
|
if car["racetd"] > 0:
|
|
ptdiff = "+"+ptdiff
|
|
|
|
ActivePrint(car,
|
|
round(currentRaceTime, 1),
|
|
round(targets["time"], 1),
|
|
ptdiff)
|
|
|
|
|
|
# Same thing by for laps too.
|
|
if car["checkpoint"] == len(race["checkpoints"]):
|
|
car["checkpoint"] = 0
|
|
car["lap"] += 1
|
|
|
|
# Velocity of the car
|
|
v = -car.localLinearVelocity[1]
|
|
|
|
# Turn target based on checkpoint vectors
|
|
pturn = vect[2][0] * 5
|
|
pturn2 = vect2[2][0] * 5
|
|
|
|
# Raceline vector
|
|
if racelineTarget and ontarget > 0.7:
|
|
rlvect = car.getVectTo(racelineTarget)
|
|
pturn = rlvect[2][0] * 2
|
|
pturn2 = pturn
|
|
|
|
if "Dani" in str(car.get("driver")):
|
|
Tools.Boxify(racelineTarget, True)
|
|
|
|
# Distance to the first checkpoint ( 0 to 1 )
|
|
# This one is used for turning calclations
|
|
closeFrac = -(min(1, distance/max(0.1,v))-1)
|
|
|
|
# Distance to the first checkpoint from the last one.
|
|
# This one to calculate target speed.
|
|
try:
|
|
prevcheck = race["checkpoints"][ checknumber - 1 ]
|
|
distbetween = math.dist( target, prevcheck["location"] ) - (checkpoint1["radius"] + prevcheck["radius"])
|
|
speedFrac = min(1, max(0, 1 - ( (vect[0]-checkpoint1["radius"]) / distbetween )))
|
|
except:
|
|
speedFrac = closeFrac
|
|
|
|
|
|
|
|
|
|
|
|
# In certain cases we want to update the turning fraction
|
|
# closeFrac to act in strange ways for a better racing line.
|
|
orcloseFrac = closeFrac
|
|
if ( (pturn > 0) != (pturn2 > 0) ):
|
|
closeFrac = TargetingCurve(speedFrac)
|
|
|
|
# Rotation targeting ( from the recording data )
|
|
# if targets.get("rot"):
|
|
# trz = targets["rot"][2]
|
|
# rz = car.orientation.to_euler()[2]
|
|
# frac, diff = RotDiff(trz, rz)
|
|
# pturn = (pturn*(1-orcloseFrac)) + (diff*orcloseFrac)
|
|
|
|
# Vision based rotation data
|
|
vturn, mindist = VisionTurn(car, True)
|
|
vclose = -(min(1, mindist/10)-1)
|
|
|
|
# Calulating rotation between checkpoints
|
|
tturn = (pturn*(1-closeFrac)) + (pturn2*closeFrac)
|
|
|
|
# When there are elevations ( such on the loops in the
|
|
# Racetrack ) it messes up the vision a bit, and car
|
|
# go crazy. It's better for those to just use the checkpoints
|
|
# for navigation.
|
|
if not ( racelineTarget and v > 10 ) and not checkpoint1.get("OnLoop") and not checkpoint1.get("IgnoreVision"):
|
|
turn = (tturn*(1-vclose)) + (vturn*vclose)
|
|
ontarget = ontarget ** 2
|
|
else:
|
|
turn = pturn
|
|
|
|
# Anti-tumble turning ( if the car rotates sideways, it
|
|
# counteracts this by turing the other way to return the
|
|
# car back to the wheels ).
|
|
tumble = car.localOrientation.to_euler()[1]
|
|
turn += tumble / 2
|
|
|
|
# Getting how much the car is turning
|
|
t = turn
|
|
if t < 0: t *= -1
|
|
|
|
# Calculating target acceleration
|
|
if not pretargets.get("speed"):
|
|
accel = 100
|
|
else:
|
|
accel = pretargets["speed"]
|
|
|
|
|
|
# Calculating target speed by interpelating target speed
|
|
# between two sets of data. Of previous and next checkpoints.
|
|
|
|
if targets.get("time"):
|
|
|
|
# but first we need to find the original acceleration between
|
|
# this to next checkpoint so the time matches.
|
|
|
|
try:
|
|
pt = pretargets.get("time", 0)
|
|
nt = targets.get("time", 0)
|
|
timeIs = nt - pt + ( car.get("racetd", 0) * 2 )
|
|
|
|
|
|
accel = max( distbetween / timeIs, accel )
|
|
except:
|
|
pass
|
|
|
|
|
|
if targets.get("speed"):
|
|
|
|
accel = (accel*(1-(speedFrac))) + ( targets["speed"] * (speedFrac) )
|
|
if ontarget > 0.8:
|
|
accel = max( accel , targets["speed"] )
|
|
|
|
|
|
|
|
|
|
# Targeting the timing of the race.
|
|
# Accelerating faster if not on time with the
|
|
# recording.
|
|
#if car.get("racetd",0) < 0 and ontarget > 0.9:
|
|
# accel *= min(1-(max(0,car.get("racetd",0))/5), 3)
|
|
|
|
# If the car is not on target, slow it down accordingly.
|
|
if ontarget < 0.7:
|
|
accel *= ontarget
|
|
accel *= 1.2
|
|
|
|
# Draw beam
|
|
try:
|
|
RacerBeam(car)
|
|
except:pass
|
|
|
|
# If the car is stuck, resque it.
|
|
if (0.5 > v and -0.5 < v ) or ( UpSideDown(car) and not onground and
|
|
not checkpoint1.get("OnLoop") ) or (ontarget < 0.3
|
|
and not checkpoint1.get("OnLoop")):
|
|
if ResqueRacer(car):
|
|
#car.localLinearVelocity[1] = -accel
|
|
car.setLinearVelocity([0,-pretargets.get("speed", accel), 0], True)
|
|
car["nitro"] = 10.0
|
|
|
|
# Cheating resque, so that to give more challenge to
|
|
# the player. If the player is too far ahead, the cars
|
|
# will resque not too far away from him.
|
|
# if pos != 0 and race["positions"].index(dani) == pos - 1\
|
|
# and dani.get("checkpoint") not in [checknumber, checknumber + 1]:
|
|
|
|
# # But we don't want it to be noticeable, so more checks.
|
|
# tc = race["checkpoints"][dani.get("checkpoint", 0) - 1]
|
|
# if cam.pointInsideFrustum(tc["location"]) != cam.INSIDE \
|
|
# and cam.pointInsideFrustum(car.position) != cam.INSIDE \
|
|
# and not tc.get("OnLoop")\
|
|
# and dani.getDistanceTo(tc["location"]) > tc["radius"]*2:
|
|
|
|
# # The resquing itself.
|
|
# car["checkpoint"] = dani.get("checkpoint", 0)
|
|
# car["lap"] = dani.get("lap",0)
|
|
# car["abort-resque"] = 0
|
|
# ResqueRacer(car, visible=False)
|
|
# daniv = dani["driving"].localLinearVelocity[1]
|
|
# car.localLinearVelocity[1] = daniv
|
|
|
|
# Showing on the map
|
|
#if pos < race["positions"].index(dani):
|
|
Map.Show(car, color=GetColor(car))
|
|
|
|
|
|
# If there is a nessesity, use handbrake.
|
|
if ontarget < 0.7 and v > accel*0.9 and v > 20 and onground:
|
|
#ActivateBreaks(car, v)
|
|
if not car.get("handbraking"):
|
|
car["handbraking"] = 30 #int(t*10)
|
|
else:
|
|
car["handbraking"] = 0
|
|
|
|
|
|
# Applying acceleration. ( also ignore the accel if you see that the road is straight )
|
|
if v < accel or (ontarget > 0.8
|
|
and ( ontarget2 > 0.9 or vect[0] < 100)
|
|
and not checkpoint1.get("OnLoop")):
|
|
if car["specs"]["redline"] > car["rpm"]:
|
|
Accelerate(car, 1)
|
|
|
|
else:
|
|
Accelerate(car, -1)
|
|
|
|
|
|
#SoftTurn(car, turn)
|
|
car["turn"] = turn
|
|
|
|
|
|
# Nitro
|
|
|
|
# A little bit of a cheat. If the AI racer is not first
|
|
# it gets free nitro.
|
|
if pos != 0:
|
|
car["nitro"] = 10.0
|
|
|
|
# Nitro logic.
|
|
if (v < max(accel, targets.get("speed", 0)) and ontarget > 0.5 )\
|
|
and onground:
|
|
StartNitro(car)
|
|
else:
|
|
StopNitro(car)
|
|
|
|
def RacingTeacher(car):
|
|
|
|
# This will be executed when a car is driven by
|
|
# Dani and is currenly racing. To record human
|
|
# benchmarks. Which AI racers could reference.
|
|
|
|
# Data about the race
|
|
|
|
checkpoint = car.get("driver",{}).get("checkpoint", 0)
|
|
lap = car.get("driver",{}).get("lap", 0)
|
|
racename = car.get("driver",{}).get("race", "Race")
|
|
race = bge.logic.globalDict["races"][racename]
|
|
racetime = bge.logic.getRealTime() - race["start-time"]
|
|
|
|
# Before we do the racing teacher stuff, we actually
|
|
# want to know if the current driver is good at all.
|
|
|
|
if "record" not in car:
|
|
car["record"] = {
|
|
"time":0,
|
|
"laps":[]
|
|
}
|
|
try:
|
|
fold = Settings.get_settings_folder()
|
|
f = fold+"/"+"record_"+racename+".json"
|
|
with open(f) as o:
|
|
car["oldrecord"] = json.load(o)
|
|
except:
|
|
car["oldrecord"] = {"time":1000000000,
|
|
"laps":[]}
|
|
|
|
car["record"]["time"] = racetime
|
|
if lap == len(car["record"]["laps"]):
|
|
car["record"]["laps"].append({
|
|
"time":0,
|
|
"checkpoints":[]
|
|
})
|
|
|
|
car["record"]["laps"][-1]["time"] = racetime
|
|
if checkpoint-1 == len(car["record"]["laps"][-1]["checkpoints"]):
|
|
car["record"]["laps"][-1]["checkpoints"].append(racetime)
|
|
try:
|
|
oldtime = car["oldrecord"]["laps"][lap]["checkpoints"][checkpoint-1]
|
|
if racetime < oldtime:
|
|
bge.logic.globalDict["print"] = "+"+str(round(oldtime-racetime, 2))
|
|
else:
|
|
bge.logic.globalDict["print"] = str(round(oldtime-racetime, 2))
|
|
except:
|
|
pass
|
|
|
|
|
|
if "line" not in car:
|
|
car["line"] = []
|
|
car["linesaved"] = None
|
|
|
|
# Adding the checkpoint
|
|
if len(car["line"]) == checkpoint:
|
|
car["line"].append([])
|
|
|
|
if ( not car["line"][checkpoint] ) or math.dist(car.worldPosition, car["line"][checkpoint][-1]) > 10:
|
|
car["line"][checkpoint].append(list(car.worldPosition))
|
|
|
|
# saving
|
|
|
|
oldlaptime = 10000000
|
|
try:
|
|
if len(car["oldrecord"]["laps"][lap]["checkpoints"]) == len(car["record"]["laps"][lap]["checkpoints"]):
|
|
oldlaptime = car["oldrecord"]["laps"][lap]["time"]
|
|
except:
|
|
pass
|
|
|
|
if lap and lap != car["linesaved"] and not car["linesaved"] and racetime < oldlaptime:
|
|
fold = Settings.get_settings_folder()
|
|
|
|
f = fold+"/"+"record_"+racename+".json"
|
|
try:
|
|
with open(f, "w") as save:
|
|
json.dump(car["record"], save, indent=4, sort_keys=True)
|
|
print(clr["bold"]+clr["tdgr"]+"Race record saved!"+clr["norm"], f)
|
|
|
|
except Exception as e:
|
|
print(clr["bold"]+clr["tdrd"]+"Failed to save race record:"+clr["norm"], e)
|
|
|
|
f = fold+"/"+"raceline_"+racename+".json"
|
|
try:
|
|
with open(f, "w") as save:
|
|
json.dump(car["line"], save, indent=4, sort_keys=True)
|
|
print(clr["bold"]+clr["tdgr"]+"Race line saved!"+clr["norm"], f)
|
|
|
|
except Exception as e:
|
|
print(clr["bold"]+clr["tdrd"]+"Failed to save raceline:"+clr["norm"], e)
|
|
|
|
car["linesaved"] = lap
|
|
|
|
def RotDiff(rot1, rot2):
|
|
|
|
# This function compares 2 rotations values
|
|
|
|
# 360 degress in radiance is 2 pi
|
|
turn = 2 * math.pi
|
|
diff = rot1 - rot2
|
|
if diff > math.pi:
|
|
diff -= math.pi * 2
|
|
elif diff < -math.pi:
|
|
diff += math.pi * 2
|
|
|
|
frac = diff
|
|
if frac < 0: frac *= -1
|
|
frac = 1 - ( frac / ( math.pi * 2 ) )
|
|
|
|
return frac, diff
|
|
|
|
def TargetingCurve(value):
|
|
|
|
# To make the car direct it's momentum
|
|
# properly toward the checkpoint, it needs
|
|
# to first turn away from the next checkpoint
|
|
# and the at it. To alight the checkpoint with
|
|
# the next checkpoint.
|
|
|
|
return -math.sin(1.2*((value)*math.pi)) * ( value**5 ) * 1
|
|
|
|
def RacerBeam(car):
|
|
|
|
scene = bge.logic.getCurrentScene()
|
|
dani = scene.objects["Dani_Box"]
|
|
cam = scene.active_camera
|
|
|
|
if not car.get("beam"):
|
|
return
|
|
|
|
# Moving the indicator
|
|
car["beam"].position = car.position
|
|
car["beam"].position[2] += 1
|
|
tocam = car["beam"].getVectTo(cam)
|
|
car["beam"].alignAxisToVect(tocam[1], 1, 1.0)
|
|
car["beam"].alignAxisToVect( (0,0,1), 2, 1.0 )
|
|
beamscale = max( 0.2, car.getDistanceTo(dani) / 100 )
|
|
car["beam"].scaling = [beamscale, beamscale, beamscale]
|
|
|
|
def ResqueRacer(car, visible=True):
|
|
|
|
# Resque system for racing cars.
|
|
|
|
|
|
|
|
# Getting race data
|
|
|
|
try:
|
|
racename = car["driver"]["race"]
|
|
|
|
except:
|
|
try: racename = car["race"]
|
|
except: return False
|
|
|
|
|
|
|
|
|
|
# Abort system for not overresqueing
|
|
if "abort-resque" not in car:
|
|
car["abort-resque"] = 50
|
|
if car["abort-resque"] > 0:
|
|
car["abort-resque"] -= 1
|
|
return False
|
|
|
|
car["abort-resque"] = 50
|
|
|
|
# Remove the teaching data from the last checkpoint.
|
|
if car.get("active"):
|
|
checkpoint = car.get("driver",{}).get("checkpoint", 0)
|
|
try: car["line"][checkpoint] = []
|
|
except:pass
|
|
|
|
# camera smooth transition
|
|
|
|
car.children["CarCamTarget"].orientation = [0.2,0,0]
|
|
zoom = car["specs"].get("cameraZoom", 3)
|
|
Character.ChangeCameraTarget(car.children["CarCamTarget"], 20, [zoom,zoom,zoom])
|
|
|
|
else:
|
|
car["ignore_line"] = True
|
|
|
|
race = bge.logic.globalDict["races"][racename]
|
|
|
|
# Getting checkpoints
|
|
try: checknumber = car["checkpoint"]
|
|
except: checknumber = car["driver"]["checkpoint"]
|
|
|
|
try: checkpoint1 = race["checkpoints"][ checknumber - 1 ]
|
|
except: checkpoint1 = race["checkpoints"][ 0 ]
|
|
|
|
try: checkpoint0 = race["checkpoints"][ checknumber - 2 ]
|
|
except: checkpoint0 = race["checkpoints"][ 0 ]
|
|
|
|
|
|
while checkpoint1.get("OnLoop"):
|
|
print(car, checkpoint1.get("OnLoop"), checknumber)
|
|
checknumber -= 1
|
|
try: checkpoint1 = race["checkpoints"][ checknumber - 1 ]
|
|
except: break
|
|
|
|
if not car.get("active"):
|
|
#car["driver"]["checkpoint"] = checknumber
|
|
car["checkpoint"] = checknumber
|
|
|
|
checkpoint2 = race["checkpoints"][ checknumber ]
|
|
|
|
# Fixing the car ( but not fully )
|
|
car["health"] = max( car["health"], 0.5 ) # Only upto 50% health restored
|
|
car["blown"] = False # Making car blowup-able
|
|
car["underwater"] = False
|
|
|
|
# Fixing wheels
|
|
for wheel in car.get("wheels", []):
|
|
wheel["health"] = max(wheel["health"], 0.5)
|
|
wheel.visible = True
|
|
|
|
# Nothing is done to restore the look of the car, so the user will be
|
|
# reminded that the car is broken.
|
|
|
|
if visible and (car.get("active") or Opt.GoodFPS("particles")):
|
|
# Sparkles on disappearing position
|
|
Destruction.particles("Sparkle", car.position.copy(),
|
|
despawn = random.randint(30, 100),
|
|
amount = 30,
|
|
max = 30,
|
|
spread = 2,
|
|
normal = 0.05,
|
|
scale = 7)
|
|
|
|
car.position = checkpoint1["location"]
|
|
car.position.z += 1
|
|
|
|
car.setLinearVelocity([0,0,0], True)
|
|
|
|
# Rotation
|
|
# targets = GetRacingTargets(car, racename, checknumber)
|
|
|
|
# if targets.get("rot"):
|
|
|
|
# car.orientation = targets.get("rot")
|
|
|
|
# else:
|
|
# tocheck = car.getVectTo(checkpoint0["location"])
|
|
# car.alignAxisToVect(tocheck[1], 1, 0.5)
|
|
car.alignAxisToVect( (0,0,1), 2, 1.0 )
|
|
tocheck = car.getVectTo(checkpoint2["location"])
|
|
car.alignAxisToVect(-tocheck[1], 1, 1)
|
|
|
|
|
|
if visible and (car.get("active") or Opt.GoodFPS("particles")):
|
|
# Sparkles on the new position
|
|
Destruction.particles("Sparkle", car.position,
|
|
despawn = random.randint(30, 100),
|
|
amount = 50,
|
|
max = 100,
|
|
spread = 2,
|
|
normal = 0.05,
|
|
scale = 5)
|
|
|
|
|
|
return True
|
|
|
|
def GetRacingTargets(car, racename, checkpoint):
|
|
|
|
# This function gives racing targets
|
|
# such as target speed and such.
|
|
|
|
race = bge.logic.globalDict["races"][racename]
|
|
|
|
|
|
# Choosing the recoring
|
|
if "race-recording" not in car:
|
|
try:
|
|
car["race-recording"] = random.choice(list(race.get("raceData", {}).keys()))
|
|
except:
|
|
return {}
|
|
|
|
rr = car["race-recording"]
|
|
|
|
answer = {}
|
|
|
|
|
|
|
|
# Speed
|
|
try: answer["speed"] = race["raceData"][rr]["speed"][checkpoint]
|
|
except: pass
|
|
|
|
# Rotation
|
|
try: answer["rot"] = race["raceData"][rr]["rot"][checkpoint]
|
|
except: pass
|
|
|
|
# Time
|
|
try: answer["time"] = race["raceData"][rr]["time"][checkpoint]
|
|
except: pass
|
|
|
|
return answer
|
|
|
|
def TargetedNPC(car):
|
|
|
|
# Making sure that the NPC is
|
|
# going somewhere
|
|
|
|
if not car.get("target"):
|
|
RandomTarget(car)
|
|
|
|
if not car.get("path"):
|
|
car["path"] = FindPathTo(car, car["target"])
|
|
|
|
|
|
# Getting the path
|
|
path = car.get("path", [])
|
|
|
|
# Pointing the car at the next node on the path
|
|
# or at the target.
|
|
if len(path) > 1:
|
|
nexttarget = path[-2]
|
|
else:
|
|
nexttarget = car["target"]
|
|
|
|
# Finiding vector to the nexttarget
|
|
vect = car.getVectTo(nexttarget)
|
|
distance = vect[0]
|
|
ontarget = -vect[2][1]
|
|
|
|
# Finding a path to the target.
|
|
if distance < 15:
|
|
car["path"] = FindPathTo(car, car["target"])
|
|
|
|
|
|
# Turning calculation
|
|
pturn = vect[2][0]
|
|
vturn, mindist = VisionTurn(car, True)
|
|
vturn *= 2
|
|
|
|
maxv = 10
|
|
if car.get("npc") == "pursuit":
|
|
maxv = 100
|
|
|
|
if vect[0] > 50:
|
|
turn = (pturn*(1-ontarget)) + (vturn*ontarget)
|
|
else:
|
|
turn = pturn
|
|
|
|
# Cant's velocity
|
|
v = -car.localLinearVelocity[1]
|
|
|
|
maxv *= max(0.2, ontarget)
|
|
|
|
if maxv > v and ontarget > -0.3:
|
|
Accelerate(car, 1)
|
|
car["turn"] = turn
|
|
#SoftTurn(car, turn)
|
|
if OnGround(car):
|
|
nitro = True
|
|
|
|
else:
|
|
Accelerate(car, -1)
|
|
#SoftTurn(car, -turn)
|
|
car["turn"] = -turn
|
|
|
|
|
|
# else:
|
|
# car["stuck"] = True
|
|
|
|
# if car.get("stuck") and ontarget < 0:
|
|
# if 0.5 > -v:
|
|
# Accelerate(car, -1)
|
|
# SoftTurn(car, -turn*10)
|
|
|
|
# else:
|
|
# car["stuck"] = False
|
|
|
|
# Is the car already at the target place?
|
|
#finished = closeFrac > 0.5 and car["target"] == nexttarget
|
|
|
|
#if finished: IdleBraking(car)
|
|
|
|
# Termination of NPC logic
|
|
if car.get("underwater")\
|
|
or car.getDistanceTo(car.get("target")) < 5:
|
|
car["npc"] = False
|
|
car["target"] = []
|
|
|
|
def RandomTarget(car):
|
|
|
|
Navigation = bge.logic.globalDict["Navigation"]
|
|
car["target"] = random.choice(Navigation["parking"])["position"]
|
|
|
|
def VisionTurn(car, return_min=False):
|
|
|
|
# Calculating turn depending on what the
|
|
# car car see.
|
|
|
|
|
|
# There is this object used for separating
|
|
# the road sides.
|
|
sep = "Separation"
|
|
|
|
# There is also a material for the road which
|
|
# we will ignore
|
|
road = "ROAD"
|
|
|
|
vo = car.get("visionOffset", 0)
|
|
vs = car["specs"].get("visionSize", 1.0)
|
|
|
|
# Making sure we scan a wider range
|
|
height = -3*vs#random.uniform(-6, -3)
|
|
width = -10#random.uniform(-15, -10)
|
|
|
|
# Getting the rays
|
|
toright = RelativePoint(car, (-5, width, height))
|
|
fromright = RelativePoint(car, (-0.8*vs,0+vo,0.5*vs))
|
|
rightray = BeautyRayCast(car, "right", toright, fromright, dist=50, poly=True)
|
|
try: rightm = str(rightray[3].material).upper()
|
|
except:rightm = ""
|
|
|
|
# And for the left too
|
|
toleft = RelativePoint(car, (5, width, height))
|
|
fromleft = RelativePoint(car, (0.8*vs,0+vo,0.5*vs))
|
|
leftray = BeautyRayCast(car, "left", toleft, fromleft, dist=50, poly=True)
|
|
try: leftm = str(leftray[3].material).upper()
|
|
except:leftm = ""
|
|
|
|
# Upper Forward rays
|
|
|
|
toupright = RelativePoint(car, (-5, width, 0.5))
|
|
fromupright = RelativePoint(car, (-0.8*vs,0+vo,0.5*vs))
|
|
uprightray = BeautyRayCast(car, "upright", toupright, fromupright, dist=50, poly=True)
|
|
try: uprightm = str(uprightray[3].material).upper()
|
|
except:uprightm = ""
|
|
|
|
|
|
toupleft = RelativePoint(car, (5, width, 0.5))
|
|
fromupleft = RelativePoint(car, (0.8*vs,0+vo,0.5*vs))
|
|
upleftray = BeautyRayCast(car, "upleft", toupleft, fromupleft, dist=50, poly=True)
|
|
try: upleftm = str(upleftray[3].material).upper()
|
|
except:upleftm = ""
|
|
|
|
|
|
|
|
|
|
# Forward ray
|
|
#if return_min:
|
|
toforward = RelativePoint(car, (0, -10, -1))
|
|
fromforward = RelativePoint(car, (0,-2+vo,0.5 *vs))
|
|
forwardray = BeautyRayCast(car, "forward", toforward, fromforward, dist=50, poly=True)
|
|
try: forwardm = str(forwardray[3].material).upper()
|
|
except:forwardm = ""
|
|
|
|
toupforward = RelativePoint(car, (0, -10, 0.5))
|
|
fromupforward = RelativePoint(car, (0,-2+vo,0.5*vs))
|
|
upforwardray = BeautyRayCast(car, "upforward", toupforward, fromupforward, dist=50, poly=True)
|
|
try: upforwardm = str(upforwardray[3].material).upper()
|
|
except:upforwardm = ""
|
|
|
|
|
|
right = 50
|
|
left = 50
|
|
upright = 50
|
|
upleft = 50
|
|
forward = 50
|
|
upforward = 50 * 4
|
|
|
|
LINE = "MATRACK_LIGHT.002"
|
|
|
|
|
|
if forwardray[1] and road not in forwardm:
|
|
forward = car.getDistanceTo(forwardray[1])
|
|
|
|
if upforwardray[1] and road not in upforwardm:
|
|
upforward = car.getDistanceTo(upforwardray[1])
|
|
|
|
if leftray[1] and road not in leftm:
|
|
left = car.getDistanceTo(leftray[1])
|
|
|
|
if rightray[1] and road not in rightm:
|
|
right = car.getDistanceTo(rightray[1])
|
|
|
|
if upleftray[1] and road not in upleftm:
|
|
upleft = car.getDistanceTo(upleftray[1])
|
|
|
|
if uprightray[1] and road not in uprightm:
|
|
upright = car.getDistanceTo(uprightray[1])
|
|
|
|
left = min(left, upleft)
|
|
right = min(right, upright)
|
|
forward = min(forward, upforward / 4)
|
|
|
|
# Anti britishing department
|
|
if uprightm == LINE and not car.get("race"):
|
|
left *= 0.9
|
|
right = max(left*1.01, right)
|
|
|
|
# Race ignoring line
|
|
if car.get("racing"):
|
|
if uprightm == LINE or rightm == LINE:
|
|
right = 10
|
|
if upleftm == LINE or rightm == LINE:
|
|
left = 10
|
|
|
|
forward /= 2
|
|
mindist = min(left, right, forward, upright, upleft)
|
|
#maxdist = max(left, right, forward, upright, upleft)
|
|
|
|
st = 5 / vs
|
|
turn = max(-0.8, min(0.8, (( max(0, min(1, ( right / st ))) - max(0, min(1, ( left / st ))) ) *-2 )))
|
|
turn /= ( vs ** 2 )
|
|
|
|
# Another version of the turn calculation
|
|
|
|
# minturn = min(left, right)
|
|
# maxturn = max(left, right)
|
|
|
|
# turn = ( ( left / maxturn ) - ( right / maxturn ) ) * ( maxturn / minturn / 4 )
|
|
|
|
# Avoiding cars and stuff
|
|
if upforwardray[0]:
|
|
obj = upforwardray[0]
|
|
if "speed" in obj and obj != car.get("target"):
|
|
car["avoid"] = obj
|
|
HornSound(car)
|
|
|
|
if car.get("avoid"):
|
|
obj = car["avoid"]
|
|
vect = car.getVectTo(obj)
|
|
v = -car.localLinearVelocity[1]
|
|
closeFrac = -(min(1, vect[0]/(v*3))-1)
|
|
if vect[2][0] > 0: pturn = -1
|
|
else: pturn = 1
|
|
turn = (turn*(1-closeFrac)) + (pturn*closeFrac)
|
|
|
|
onobj = -vect[2][1]
|
|
if onobj < 1-closeFrac:
|
|
car["avoid"] = None
|
|
|
|
if not return_min:
|
|
return turn
|
|
else:
|
|
return turn, mindist
|
|
|
|
def SoftTurn(car, turn):
|
|
|
|
if turn < 0 and car["turn"] > turn:
|
|
TurnRight(car)
|
|
elif turn > 0 and car["turn"] < turn:
|
|
TurnLeft(car)
|
|
|
|
def FindPathTo(car, location):
|
|
|
|
# Since this could be an object,
|
|
# we will try to unlcoking the location
|
|
|
|
try: location = location.position
|
|
except: pass
|
|
location = mathutils.Vector(location)
|
|
|
|
path = []
|
|
|
|
Navigation = bge.logic.globalDict["Navigation"]
|
|
|
|
def findClosest(car, location, points, exclude):
|
|
|
|
|
|
cd = 1000
|
|
nd = None
|
|
|
|
for node in points:
|
|
|
|
if location == None: location = node
|
|
distance = math.dist(location, node )
|
|
distance += car.getDistanceTo(node) / 4
|
|
|
|
if distance < cd and node not in exclude:
|
|
cd = distance
|
|
nd = node
|
|
|
|
return nd
|
|
|
|
|
|
points = list(n["position"] for n in Navigation["road"]) + [car.position]
|
|
|
|
while not path or path[-1] != car.position:
|
|
|
|
if not path:
|
|
path.append(findClosest(car, location, points, path))
|
|
else:
|
|
path.append(findClosest(car, path[-1], points, path))
|
|
|
|
for n, node in enumerate(path):
|
|
if n == 0:
|
|
DrawLine(car, "Main", node, location)
|
|
else:
|
|
DrawLine(car, n, node, path[n-1])
|
|
|
|
return path
|
|
|
|
def StraightLine(car, point1, point2):
|
|
|
|
ray = car.rayCast(point1, point2)
|
|
if ray[1]:
|
|
cd = math.dist(point1, point2)
|
|
ad = math.dist(point2, ray[1])
|
|
if round(cd, 1) == round(ad, 1):
|
|
return True
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
|
|
def IsChased(car):
|
|
|
|
# Returns a list of cars that are chasing this car.
|
|
|
|
l = []
|
|
|
|
for thecar in bge.logic.globalDict["cars"]:
|
|
if thecar.get("inview") and thecar.get("npc") == "pursuit" and thecar.get("target") == car:
|
|
l.append(thecar)
|
|
|
|
return l
|
|
|
|
|
|
##### POLICE FUNCTIONS ####
|
|
|
|
def Policify(car):
|
|
|
|
# Function turns car into police car.
|
|
|
|
# Making sure the car can get Police Lights.
|
|
if "PoliceLightProxy" not in car.childrenRecursive:
|
|
return
|
|
|
|
print("Policifing:", consoleForm(car))
|
|
|
|
AddPoliceLights(car)
|
|
AddNitroCan(car)
|
|
car["nitro"] = 10.0
|
|
|
|
Material(car, (0,0,0), "MainColor")
|
|
Material(car, (0.1,0.6,1), "SecondaryColor")
|
|
|
|
for door in car.get("doors"):
|
|
door.blenderObject["MainColor"] = (1,1,1)
|
|
door.blenderObject["SecondaryColor"] = (1,0.09,0)
|
|
|
|
car["police"] = True
|
|
if car not in bge.logic.globalDict["policeCars"]:
|
|
bge.logic.globalDict["policeCars"].append(car)
|
|
|
|
def AddPoliceLights(car):
|
|
|
|
# Making sure the car can get Police Lights.
|
|
if "PoliceLightProxy" not in car.childrenRecursive:
|
|
return
|
|
plproxy = car.childrenRecursive["PoliceLightProxy"]
|
|
|
|
if "policelights" not in car:
|
|
car["policelights"] = Reuse.Create("PoliceLight")
|
|
car["policelights"].worldPosition = plproxy.worldPosition
|
|
car["policelights"].worldOrientation = plproxy.worldOrientation
|
|
car["policelights"].scaling = plproxy.scaling
|
|
car["policelights"].setParent(plproxy)
|
|
|
|
def RemovePoliceLights(car):
|
|
|
|
if "policelights" not in car:
|
|
return
|
|
|
|
car["policelights"].removeParent()
|
|
Reuse.Delete(car["policelights"])
|
|
del car["policelights"]
|
|
|
|
def PolicePursuit(car):
|
|
|
|
if car in bge.logic.globalDict["policeCars"]:
|
|
return
|
|
|
|
for police in bge.logic.globalDict["policeCars"]:
|
|
if police.getDistanceTo(car) < 150:
|
|
police["npc"] = "pursuit"
|
|
police["target"] = car
|
|
|
|
|
|
##### SOUND RELATED FUNCTIONS ####
|
|
|
|
def EngineSound(car):
|
|
|
|
# Engine Sound
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = car.get("engine_sound","//sfx/engines/neonspeedster.ogg")
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
sound["play"].location = car.worldPosition
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
|
|
pitch = RPMWithIdle(car, normalize=True) / car["specs"]["maxrpm"]
|
|
|
|
sound["play"].pitch = pitch * 3
|
|
sound["play"].volume = car.get("health", 1)
|
|
|
|
# Grind sound
|
|
v = -car.localLinearVelocity[1]
|
|
if v < 0: v *= -1
|
|
|
|
s = "//sfx/grind.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
if v > 0.01:
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
|
|
|
|
sound["play"].location = car.worldPosition
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
sound["play"].pitch = max(0.3, car.get("health", 1)) * 2
|
|
sound["play"].volume = (1-car.get("health", 1)) * min(1.5, v *8) * 1
|
|
|
|
else:
|
|
|
|
try:
|
|
bge.logic.globalDict["sounds"][code]["play"].stop()
|
|
except:
|
|
pass
|
|
|
|
def GearShiftSound(car):
|
|
|
|
# This is the sound of the gear
|
|
# being shifted.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/gear_shit.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
sound["play"] = device.play(sound["sound"])
|
|
sound["play"].location = car.worldPosition
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
sound["play"].volume = 3
|
|
|
|
def DriftSound(car):
|
|
|
|
# This is a sound of rubber skidding.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/drift.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
slide = car.localLinearVelocity[0]
|
|
|
|
# Adding nitro
|
|
AddNitro(car, slide / 5000)
|
|
|
|
if car.get("braking"):
|
|
slide = car.localLinearVelocity[1]
|
|
if slide < 0:
|
|
slide *= -1
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
if slide > 1 and OnGround(car):
|
|
|
|
#car["nitro"] += 2
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
sound["play"].location = car.worldPosition
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
#sound["play"].pitch = 0.8
|
|
sound["play"].volume = min(2, slide / 5) / 5
|
|
|
|
|
|
else:
|
|
|
|
try:
|
|
bge.logic.globalDict["sounds"][code]["play"].stop()
|
|
except:
|
|
pass
|
|
|
|
def ScrapeSound(car, position=None, volume=3):
|
|
|
|
# This function produces
|
|
# a sound of scraping metal.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/scrape.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
if not position:
|
|
sound["play"].location = car.worldPosition
|
|
else:
|
|
sound["play"].location = position
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
sound["play"].pitch = random.uniform(0.6, 1.4)
|
|
sound["play"].volume = volume
|
|
|
|
def RedlineSound(car, position=None, volume=3):
|
|
|
|
# This function produces
|
|
# a sound of scraping metal.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/redline.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
if not position:
|
|
sound["play"].location = car.worldPosition
|
|
else:
|
|
sound["play"].location = position
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
#sound["play"].pitch = random.uniform(0.6, 1.4)
|
|
sound["play"].volume = volume
|
|
|
|
def HitSound(car, position=None, volume=3):
|
|
|
|
# This function produces
|
|
# a sound of scraping metal.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/hit.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
if not position:
|
|
sound["play"].location = car.worldPosition
|
|
else:
|
|
sound["play"].location = position
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
sound["play"].pitch = random.uniform(0.6, 1.4)
|
|
sound["play"].volume = volume
|
|
|
|
def GlassSound(car, position=None, volume=3):
|
|
|
|
# This function produces
|
|
# a sound of scraping metal.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/glass.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
if not position:
|
|
sound["play"].location = car.worldPosition
|
|
else:
|
|
sound["play"].location = position
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
sound["play"].pitch = random.uniform(0.6, 1.4)
|
|
sound["play"].volume = volume
|
|
|
|
def WheelPopSound(car, position=None, volume=3):
|
|
|
|
# This function produces
|
|
# a sound of scraping metal.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/tire_pop.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
if not position:
|
|
sound["play"].location = car.worldPosition
|
|
else:
|
|
sound["play"].location = position
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
sound["play"].pitch = random.uniform(0.6, 1.4)
|
|
sound["play"].volume = volume
|
|
|
|
def NitroSound(car):
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/nitro.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
sound["play"].location = car.position
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 100
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
sound["play"].pitch = 1
|
|
sound["play"].volume = 5
|
|
|
|
def NitroSoundStop(car):
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/nitro.ogg"
|
|
code = s+str(car["cid"])
|
|
if code in bge.logic.globalDict["sounds"]:
|
|
|
|
try:
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
if sound["play"].volume > 0:
|
|
sound["play"].volume -= 0.5
|
|
else:
|
|
sound["play"].stop()
|
|
except:
|
|
pass
|
|
|
|
def DaniOhGodSound(car):
|
|
|
|
# Dani saying, oh no!
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
dani = scene.objects["Dani_Box"]
|
|
|
|
# Dani animation of opening mouth
|
|
Character.ApplyAnimation(dani, "Talk")
|
|
|
|
# The sound itself.
|
|
device.play(bge.logic.globalDict["sounds"]["dani_ohgod"]["sound"])
|
|
|
|
def StartEnemySounds(car):
|
|
|
|
# Angry voice when player hits somebody
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
sound = random.choice(bge.logic.globalDict["sounds_angry_start"])
|
|
sound = bge.logic.globalDict["sounds"][sound]
|
|
sound["play"] = device.play(sound["sound"])
|
|
sound["play"].location = car.position
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 50
|
|
sound["play"].distance_reference = 5
|
|
sound["play"].attenuation = 1
|
|
sound["play"].volume = 1.5
|
|
|
|
|
|
car["enemysound"] = sound["play"]
|
|
|
|
def HitEnemySound(car):
|
|
|
|
# Angy voice when the enemy hits player
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
if not car.get("enemysound") or not car["enemysound"].status:
|
|
|
|
sound = random.choice(bge.logic.globalDict["sounds_angry_hit"])
|
|
sound = bge.logic.globalDict["sounds"][sound]
|
|
sound["play"] = device.play(sound["sound"])
|
|
sound["play"].location = car.position
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 50
|
|
sound["play"].distance_reference = 5
|
|
sound["play"].attenuation = 1
|
|
sound["play"].volume = 1.5
|
|
|
|
car["enemysound"] = sound["play"]
|
|
|
|
# Making police more interesting
|
|
car["angry_swing"] = 60
|
|
#car["health"] = car.get("health", 1) / 2
|
|
|
|
def HornSound(car):
|
|
|
|
# This function produces
|
|
# a sound of scraping metal.
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/horn.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
sound["play"].location = car.worldPosition
|
|
if not car.get("active"):
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 1
|
|
def PoliceSound(car):
|
|
|
|
# Police Sound
|
|
|
|
device = bge.logic.globalDict["SoundDevice"]
|
|
|
|
s = "//sfx/police.ogg"
|
|
code = s+str(car["cid"])
|
|
if code not in bge.logic.globalDict["sounds"]:
|
|
bge.logic.globalDict["sounds"][code] = {"sound":aud.Sound(bge.logic.expandPath(s)),
|
|
"play":None}
|
|
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
|
|
if not sound["play"] or not sound["play"].status:
|
|
sound["play"] = device.play(sound["sound"])
|
|
|
|
sound["play"].location = car.worldPosition
|
|
if not car.get("active"):
|
|
sound["play"].velocity = car.getLinearVelocity()
|
|
sound["play"].relative = False
|
|
sound["play"].distance_maximum = 200
|
|
sound["play"].distance_reference = 1
|
|
sound["play"].attenuation = 0.1
|
|
sound["play"].volume = 5
|
|
|
|
def StopPoliceSound(car):
|
|
|
|
s = "//sfx/police.ogg"
|
|
code = s+str(car["cid"])
|
|
try:
|
|
sound = bge.logic.globalDict["sounds"][code]
|
|
sound["play"].stop()
|
|
except:
|
|
pass
|
|
|
|
|
|
|
|
#### SAVE / MULTIPLAYER FUNCTIONS #####
|
|
|
|
def EncodeRotation(car):
|
|
|
|
rot = list(car.worldOrientation)
|
|
for n, i in enumerate(rot):
|
|
rot[n] = list(i)
|
|
return rot
|
|
|
|
def Encode(car):
|
|
|
|
# This function will generate a copy of the car's
|
|
# data that is possible to save into a Json file.
|
|
|
|
# General data
|
|
cardata = {
|
|
"ID" : id(car),
|
|
"type" : "veh",
|
|
"name" : car.name,
|
|
"cid" : int(car["cid"]),
|
|
"netId" : car.get("netId"),
|
|
"position" : list(car.position),
|
|
"orientation" : EncodeRotation(car),
|
|
"linvelocity" : list(car.worldLinearVelocity),
|
|
"angvelocity" : list(car.worldAngularVelocity),
|
|
"npc" : car.get("npc", ""),
|
|
"rpm" : car.get("rpm", 0),
|
|
"gear" : car.get("gear", 0),
|
|
"turn" : car.get("turn", 0),
|
|
"police" : car.get("police", False)
|
|
|
|
}
|
|
|
|
|
|
|
|
# Health data
|
|
cardata["health"] = car.get("health" , 1.0)
|
|
cardata["nitro"] = car.get("nitro" , 0.0)
|
|
cardata["NitroCan"] = car.get("NitroCan" , False)
|
|
cardata["blown"] = car.get("blown" , False)
|
|
cardata["Spoiler"] = car.get("Spoiler", False)
|
|
|
|
cardata["doors"] = []
|
|
for door in car.get("doors",[]):
|
|
doordata = {
|
|
"health" : door["health"],
|
|
"locked" : door["locked"],
|
|
"visible" : door.visible
|
|
}
|
|
cardata["doors"].append(doordata)
|
|
|
|
cardata["wheels"] = []
|
|
for wheel in car.get("wheels", []):
|
|
wheeldata = {
|
|
"health" : wheel["health"],
|
|
"visible" : wheel.visible
|
|
}
|
|
cardata["wheels"].append(wheeldata)
|
|
|
|
# Color data
|
|
cardata["colors"] = car.get("colors", {}).copy()
|
|
for colorname in cardata["colors"]:
|
|
color = cardata["colors"][colorname]
|
|
color = list(color)
|
|
cardata["colors"][colorname] = color
|
|
|
|
if not car.get("ownerId"):
|
|
car["ownerId"] = bge.logic.globalDict.get("userId")
|
|
|
|
netObjects = bge.logic.globalDict["netObjects"]
|
|
if cardata["ID"] not in netObjects["pythonId"]:
|
|
netObjects["pythonId"][cardata["ID"]] = car
|
|
|
|
return cardata
|
|
|
|
def Decode(cardata, new=False, network=False):
|
|
|
|
# This function will restore the car
|
|
# from the encoded state.
|
|
|
|
# Making the car
|
|
carModel = cardata.get("name" , "NeonSpeedsterBox")
|
|
position = cardata.get("position" , [0,0,0])
|
|
orientation = cardata.get("orientation", [0,0,0])
|
|
|
|
# Network related recognizing
|
|
netId = cardata.get("netId")
|
|
|
|
netObjects = bge.logic.globalDict["netObjects"]
|
|
|
|
returnNew = False
|
|
|
|
if netId and netId not in netObjects["netId"] or new:
|
|
|
|
car = Spawn(carModel, position, orientation)
|
|
|
|
for attribute in cardata.get("colors", {}):
|
|
value = cardata["colors"][attribute]
|
|
Material(car, value, attribute)
|
|
|
|
UpdateCar(car)
|
|
|
|
print("Spawned cause", netId, "netId isn't in", list(netObjects["netId"].keys()))
|
|
|
|
netObjects["netId"][netId] = car
|
|
returnNew = True
|
|
|
|
|
|
|
|
car = netObjects["netId"][netId]
|
|
car["netId"] = netId
|
|
car["ownerId"] = cardata.get("ownerId")
|
|
|
|
if network:
|
|
latency = Multiplayer_Client.Latency(car)
|
|
else:
|
|
latency = 0
|
|
|
|
|
|
car["npc"] = cardata.get("npc", "")
|
|
car["rpm"] = cardata.get("rpm", 0)
|
|
car["gear"] = cardata.get("gear", 0)
|
|
car["turn"] = cardata.get("turn", 0)
|
|
|
|
|
|
|
|
car.worldLinearVelocity = cardata.get("linvelocity", car.localLinearVelocity)
|
|
car.worldAngularVelocity = cardata.get("angvelocity", car.localAngularVelocity)
|
|
|
|
#position = mathutils.Vector(position)+(car.worldLinearVelocity*latency)
|
|
|
|
if math.dist(car.position, position) > 2:
|
|
|
|
Reuse.Move(car, position, 0.5)
|
|
#car["netPosition"] = position
|
|
#car.position = position
|
|
car.worldOrientation = orientation
|
|
#car.applyRotation(car.worldAngularVelocity*latency,False)
|
|
|
|
|
|
# Health stuff
|
|
|
|
car["health"] = cardata.get("health", 1.0)
|
|
car["nitro"] = cardata.get("nitro", 0.0)
|
|
|
|
if cardata.get("NitroCan" , False):
|
|
AddNitroCan(car)
|
|
|
|
if cardata.get("Spoiler" , False):
|
|
AddSpoiler(car, cardata["Spoiler"])
|
|
|
|
car["blown"] = cardata.get("blown", False)
|
|
if car["health"] < 0.3:
|
|
ChangeBody(car, good=False)
|
|
|
|
for n, door in enumerate(car.get("doors",[])):
|
|
|
|
# Getting door data
|
|
try:
|
|
doordata = cardata.get("doors",[])[n]
|
|
except Exception as e:
|
|
print("Failed to load door",n,"for", car, ":", e)
|
|
break
|
|
|
|
# Restoring the door
|
|
door["health"] = doordata.get("health", 1.0)
|
|
door["locked"] = doordata.get("locked", True)
|
|
if door.visible != doordata.get("visible", True):
|
|
door.visible = doordata.get("visible", True)
|
|
|
|
if door["health"] < 1.0:
|
|
if "Borked" in door and door.get("mesh", "Good") != "Borked":
|
|
door.replaceMesh(door["Borked"])
|
|
door["mesh"] = "Borked"
|
|
|
|
for n, wheel in enumerate(car.get("wheels", [])):
|
|
|
|
# Getting wheel data
|
|
try:
|
|
wheeldata = cardata.get("wheels",[])[n]
|
|
except Exception as e:
|
|
print("Failed to load wheel",n,"for", car, ":", e)
|
|
break
|
|
|
|
wheel["health"] = wheeldata.get("health", 1.0)
|
|
wheel.visible = wheeldata.get("visible", True)
|
|
|
|
# Police
|
|
if cardata.get("police") and not car.get("police"):
|
|
Policify(car)
|
|
# Colors
|
|
#for attribute in cardata.get("colors", {}):
|
|
# value = cardata["colors"][attribute]
|
|
# Material(car, value, attribute)
|
|
#pass # for emacs colapase function
|
|
|
|
return returnNew
|
|
|
|
def RemoveNetId(car):
|
|
|
|
# Removing NetId from the car
|
|
# for when the play has taken it
|
|
# or when it's despawned.
|
|
|
|
if "netId" in car and "netCars" in bge.logic.globalDict:
|
|
|
|
netCars = bge.logic.globalDict["netCars"]
|
|
netId = car["netId"]
|
|
|
|
if netId in netCars:
|
|
del car["netId"]
|
|
del netCars[netId]
|
|
|
|
def NetworkControlled(car):
|
|
|
|
return car.get("netId") in bge.logic.globalDict.get("netObjects", {}).get("netId", {})\
|
|
and car.get("onwerId") != bge.logic.globalDict.get("userId")
|
|
|
|
def NetworkControl(car):
|
|
return
|
|
|
|
|
|
#### DEBUGGIN FUNCTIONS ######
|
|
|
|
def DrawLine(car, name, point1, point2):
|
|
# To deactivate this function.
|
|
settings = bge.logic.globalDict["settings"]
|
|
if not settings.get("dev-rays"): return
|
|
|
|
if "lines" not in car:
|
|
car["lines"] = {}
|
|
if name not in car["lines"]:
|
|
car["lines"][name] = Reuse.Create("Redline")
|
|
|
|
line = car["lines"][name]
|
|
line.position = point1
|
|
vect = line.getVectTo(point2)
|
|
line.alignAxisToVect(vect[1], 2, 1)
|
|
line.scaling.z = vect[0]
|
|
|
|
scale = 2
|
|
line.scaling.x = scale
|
|
line.scaling.y = scale
|
|
|
|
def BeautyRayCast(car, name, to, fro, *args, **kwargs):
|
|
|
|
# This will make a KX_Object.rayCast() function be more
|
|
# pretty utilizing DrawLine() function.
|
|
|
|
ray = car.rayCast(to, fro, *args, **kwargs)
|
|
|
|
if ray[1]:
|
|
DrawLine(car, name, fro, ray[1])
|
|
else:
|
|
DrawLine(car, name, fro, to)
|
|
|
|
return ray
|
|
|
|
def ActivePrint(car, *text):
|
|
|
|
# Prints something only from the car
|
|
# that is driven by the player ( or
|
|
# one that dani is in the driver's
|
|
# seat of, in the case of AI debugging ).
|
|
|
|
scene, car = GetSceneAndCar(car)
|
|
dani = scene.objects["Dani_Box"]
|
|
|
|
if car.get("active") or dani.get("driving") == car:
|
|
print(*text)
|
|
|