forked from BlenderDumbass/DanisRace
4357 lines
123 KiB
Python
4357 lines
123 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
|
||
|
|
||
|
|
||
|
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 Multiplayer_Shared
|
||
|
from Scripts import Multiplayer_Client
|
||
|
|
||
|
from Scripts import Character_Controll
|
||
|
|
||
|
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"],
|
||
|
|
||
|
# 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)
|
||
|
|
||
|
|
||
|
|
||
|
# 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)
|
||
|
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)
|
||
|
|
||
|
# If nobody controls the car.
|
||
|
else:
|
||
|
IdleBraking(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)
|
||
|
|
||
|
# Give car the sound
|
||
|
EngineSound(car)
|
||
|
DriftSound(car)
|
||
|
|
||
|
# Smoke and fire
|
||
|
SmokeAndFire(car)
|
||
|
GroundPuffsOfSmoke(car)
|
||
|
|
||
|
# Manual fixing.
|
||
|
# Moved to Tools.py
|
||
|
|
||
|
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["wheels"]):
|
||
|
|
||
|
wheelObject = car["wheels"][n]
|
||
|
health = wheelObject["health"] ** 2
|
||
|
|
||
|
# Damping
|
||
|
suspension = wheel["suspension"] * health
|
||
|
k = car["specs"]["suspentionDamping"]
|
||
|
suspensionDamping = 2 * k * math.sqrt(suspension)
|
||
|
|
||
|
|
||
|
|
||
|
getCarPhysics(car).setSuspensionStiffness(suspension, n)
|
||
|
getCarPhysics(car).setSuspensionDamping(suspensionDamping, n)
|
||
|
getCarPhysics(car).setTyreFriction(car["specs"]["grip"]*math.sqrt(car.mass), 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["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)
|
||
|
|
||
|
|
||
|
UpdateTachometer(car)
|
||
|
UpdateSpeedometer(car)
|
||
|
UpdateGearUI(car)
|
||
|
UpdateRedline(car)
|
||
|
UpdateHealthGauge(car)
|
||
|
UpdateNitroMeter(car)
|
||
|
UpdateFixMap(car)
|
||
|
|
||
|
# Various things that happen only to the user's car
|
||
|
CloseCall(car)
|
||
|
|
||
|
# Grabbing control when on multiplayer
|
||
|
if settings.get("multiplayer"):
|
||
|
Multiplayer_Client.GrabOwnership(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)
|
||
|
|
||
|
|
||
|
# 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 ) ** 3 ) * 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 ) ** 3 ) * 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.
|
||
|
|
||
|
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, n)
|
||
|
else:
|
||
|
getCarPhysics(car).applyBraking(0, n)
|
||
|
|
||
|
# Stablizing the brakes
|
||
|
if stable:
|
||
|
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,-30*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
|
||
|
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.Force(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
|
||
|
elif 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"] ** 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"):
|
||
|
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 v > 0.01 and vo < 0:
|
||
|
car.applyTorque([0,0,car["turn"] * 20 * car.mass],True)
|
||
|
elif v > 0.01 and vo > 0:
|
||
|
car.applyTorque([0,0,car["turn"] * -20 * 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()
|
||
|
|
||
|
if car["turn"] > 0: car["turn"] *= -0.5
|
||
|
car["turn"] -= 0.005 * toFPS
|
||
|
car["turning"]= True
|
||
|
|
||
|
def TurnLeft(car):
|
||
|
|
||
|
toFPS = Opt.ToFPS()
|
||
|
|
||
|
if car["turn"] < 0: car["turn"] *= -0.5
|
||
|
car["turn"] += 0.005 * toFPS
|
||
|
car["turning"]= True
|
||
|
|
||
|
def HandBrake(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.
|
||
|
|
||
|
factor = car.localLinearVelocity[1]
|
||
|
ActivateBreaks(car, factor)
|
||
|
|
||
|
|
||
|
# 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(car) and ( ( car.get("driftturn",0 ) < 0 ) == ( car["turn"] < 0 ) ):
|
||
|
car["driftturn"] = float(car["turn"])
|
||
|
|
||
|
# Adding fake turn to the car
|
||
|
car.applyTorque([-car.mass*5,
|
||
|
0,
|
||
|
car["driftturn"]*car.mass*10
|
||
|
], 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)
|
||
|
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_Controll.ChangeCameraTarget(camtarget,
|
||
|
0,
|
||
|
rotate=False)
|
||
|
car["return-cam"] = True
|
||
|
|
||
|
|
||
|
if onground and car.get("return-cam"):
|
||
|
|
||
|
car["return-cam"] = False
|
||
|
|
||
|
Character_Controll.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
|
||
|
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):
|
||
|
|
||
|
# Returns whether the car
|
||
|
# in on the ground or not.
|
||
|
|
||
|
# Getting the relative position of the ground
|
||
|
down = RelativePoint(car, [0,0,-1])
|
||
|
|
||
|
# 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
|
||
|
|
||
|
|
||
|
# 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():
|
||
|
|
||
|
# 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 [points[0]]:
|
||
|
|
||
|
force = point.appliedImpulse / car.mass
|
||
|
|
||
|
if UpSideDown(car) and not OnGround(car):
|
||
|
force = 3
|
||
|
|
||
|
# Under a low enough value we ignore everything
|
||
|
if force < 0.1:
|
||
|
|
||
|
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.
|
||
|
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 < 2:
|
||
|
ScrapeSound(car, position=point.worldPoint, volume=max(1,min(3,force)))
|
||
|
else:
|
||
|
HitSound(car, position=point.worldPoint, volume=min(3,force/20))
|
||
|
|
||
|
# 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 / 100 ))
|
||
|
|
||
|
# 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:
|
||
|
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"):
|
||
|
Destruction.Explosion(point.worldPoint, mass=50, size=30)
|
||
|
car["blown"] = True
|
||
|
|
||
|
RemoveNitroCan(car)
|
||
|
RemoveFireBall(car)
|
||
|
|
||
|
# Push everybody out of the car
|
||
|
if car.get("driver"):
|
||
|
character = car["driver"]
|
||
|
Character_Controll.getOutCar(character)
|
||
|
car.worldLinearVelocity = [0,0,10]
|
||
|
car.localAngularVelocity = [10,0,0]
|
||
|
|
||
|
|
||
|
for p in car.get("passangers", []):
|
||
|
Character_Controll.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
|
||
|
|
||
|
# Camera shake
|
||
|
car["shake"] = car.get("shake",0) + force
|
||
|
car["crash"] = force
|
||
|
|
||
|
# 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 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["wheels"]:
|
||
|
ColorPart(car, wheel)
|
||
|
|
||
|
# Neon
|
||
|
if attribute == "SecondaryColor":
|
||
|
|
||
|
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 car["specs"].get("material", []):
|
||
|
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 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
|
||
|
body.replaceMesh(car.get("borked", "Borked"))
|
||
|
GlassSound(car, position=car.position, volume=3)
|
||
|
|
||
|
if "Neon" in car:
|
||
|
car["Neon"].blenderObject.data.energy = 0
|
||
|
|
||
|
# Otherwise restore the car
|
||
|
else:
|
||
|
try:
|
||
|
body.replaceMesh(body["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)
|
||
|
Destruction.Fire(smokeemiter.position, fireCoefitient)
|
||
|
|
||
|
if "FireBall" not in car and health < 0.3:
|
||
|
car["FireBall"] = Destruction.AttatchFireBall(smokeemiter.position,
|
||
|
smokeemiter,
|
||
|
1.5)
|
||
|
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)
|
||
|
|
||
|
camera = scene.active_camera
|
||
|
deleteDistance = 500
|
||
|
|
||
|
# 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
|
||
|
|
||
|
# 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
|
||
|
|
||
|
if score > 1:
|
||
|
bge.logic.globalDict["print"] = "Oh god"+("!"*int(round(score)))
|
||
|
DaniOhGodSound(car)
|
||
|
Money.Recieve(score*25)
|
||
|
|
||
|
car["shake"] = score
|
||
|
car["near"] = score
|
||
|
|
||
|
|
||
|
|
||
|
else:
|
||
|
bge.logic.globalDict["closeCallIgnore"] = False
|
||
|
|
||
|
|
||
|
##### 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", c, "into", car)
|
||
|
|
||
|
|
||
|
##### SPAWNING AND UNSPAWNING OF CARS ###
|
||
|
|
||
|
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"] )
|
||
|
|
||
|
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
|
||
|
|
||
|
# If it's way too close, abort
|
||
|
if len(spawnSorted) > n+1 and distance < 100 \
|
||
|
and spawn.get("npc") != "racer":
|
||
|
continue
|
||
|
|
||
|
# Spawn timer ( to avoid, over spawning )
|
||
|
if spawn.get("to_spawn_timer", 0) > 0:
|
||
|
spawn["to_spawn_timer"] -= 1
|
||
|
continue
|
||
|
spawn["to_spawn_timer"] = 100
|
||
|
|
||
|
|
||
|
# Checking race
|
||
|
if spawn.get("race"):
|
||
|
|
||
|
# Getting the race data
|
||
|
|
||
|
racename = spawn.get("race")
|
||
|
race = bge.logic.globalDict["races"][racename]
|
||
|
after = race.get("after")
|
||
|
during = race.get("during")
|
||
|
duringcheck = dani.get("race") == after and Script.Story.get(during)
|
||
|
aftercheck = after in Script.Story["passed"] or not after or duringcheck
|
||
|
|
||
|
racetype = race.get("type", "race-car")
|
||
|
try:
|
||
|
carModel = random.choice(NPC_type_cars[racetype])
|
||
|
except Exception as e:
|
||
|
print(e)
|
||
|
pass
|
||
|
|
||
|
else:
|
||
|
aftercheck = False
|
||
|
|
||
|
# For NPC cars
|
||
|
if ( spawn.get("npc") == "npc" )\
|
||
|
or ( spawn.get("npc") == "racer" and aftercheck
|
||
|
and spawn.get("to_spawn") and ( not dani.get("race") or duringcheck )):
|
||
|
|
||
|
# Spawning the car.
|
||
|
|
||
|
car = Spawn(
|
||
|
carModel,
|
||
|
spawn["position"],
|
||
|
spawn["orientation"],
|
||
|
True,
|
||
|
"pallete",
|
||
|
spawnanyway=False,
|
||
|
spawn=spawn)
|
||
|
|
||
|
if not car and (spawnedCars < maxCars or spawn.get("npc") == "racer"):
|
||
|
|
||
|
car = Spawn(
|
||
|
carModel,
|
||
|
spawn["position"],
|
||
|
spawn["orientation"],
|
||
|
True,
|
||
|
"pallete",
|
||
|
spawnanyway=True,
|
||
|
spawn=spawn)
|
||
|
|
||
|
|
||
|
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
|
||
|
|
||
|
|
||
|
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()
|
||
|
|
||
|
# This function is simplify spawning
|
||
|
# of the car.
|
||
|
|
||
|
# 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
|
||
|
|
||
|
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)
|
||
|
except: pass
|
||
|
car["nitro"] = 0.0
|
||
|
|
||
|
try: RemoveSpoiler(car)
|
||
|
except: pass
|
||
|
|
||
|
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
|
||
|
|
||
|
|
||
|
|
||
|
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)
|
||
|
|
||
|
# 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 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
|
||
|
|
||
|
#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"]
|
||
|
|
||
|
if bge.logic.globalDict["pursuit-cheat"] and dani.get("driving"):
|
||
|
|
||
|
car["target"] = dani["driving"]
|
||
|
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"
|
||
|
|
||
|
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)
|
||
|
|
||
|
# Vector to the target
|
||
|
vect = car.getVectTo(target)
|
||
|
distance = vect[0]
|
||
|
ontarget = -vect[2][1]
|
||
|
|
||
|
# 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:
|
||
|
turn = (pturn*(1-ontarget)) + (vturn*ontarget)
|
||
|
else:
|
||
|
turn = pturn
|
||
|
|
||
|
nitro = False
|
||
|
if 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
|
||
|
|
||
|
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"]
|
||
|
|
||
|
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
|
||
|
|
||
|
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
|
||
|
|
||
|
# 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 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 = 20
|
||
|
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.9:
|
||
|
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.
|
||
|
accel *= ontarget
|
||
|
|
||
|
# Draw beam
|
||
|
RacerBeam(car)
|
||
|
|
||
|
# If the car is stuck, resque it.
|
||
|
if 1 > 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 t > 0.3 and v > 7 and onground:
|
||
|
HandBrake(car)
|
||
|
|
||
|
# 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")):
|
||
|
Accelerate(car, 1)
|
||
|
|
||
|
else:
|
||
|
Accelerate(car, -1)
|
||
|
|
||
|
|
||
|
SoftTurn(car, 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 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
|
||
|
|
||
|
|
||
|
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 ]
|
||
|
|
||
|
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.7)
|
||
|
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:
|
||
|
# 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(checkpoint2["location"])
|
||
|
car.alignAxisToVect( (0,0,1), 2, 1.0 )
|
||
|
car.alignAxisToVect(-tocheck[1], 1, 1.0)
|
||
|
#if UpSideDown(car):
|
||
|
|
||
|
|
||
|
if visible:
|
||
|
# 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 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)
|
||
|
|
||
|
# Making sure we scan a wider range
|
||
|
height = -3#random.uniform(-6, -3)
|
||
|
width = -10#random.uniform(-15, -10)
|
||
|
|
||
|
# Getting the rays
|
||
|
toright = RelativePoint(car, (-5, width, height))
|
||
|
fromright = RelativePoint(car, (-0.8,0+vo,0.5))
|
||
|
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,0+vo,0.5))
|
||
|
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,0+vo,0.5))
|
||
|
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,0+vo,0.5))
|
||
|
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))
|
||
|
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))
|
||
|
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
|
||
|
turn = max(-0.8, min(0.8, (( max(0, min(1, ( right / st ))) - max(0, min(1, ( left / st ))) ) *-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 car and obj != car.get("target"):
|
||
|
car["avoid"] = obj
|
||
|
|
||
|
if car.get("avoid"):
|
||
|
obj = car["avoid"]
|
||
|
vect = car.getVectTo(obj)
|
||
|
v = -car.localLinearVelocity[1]
|
||
|
closeFrac = -(min(1, vect[0]/(v*5))-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
|
||
|
|
||
|
|
||
|
##### 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"]:
|
||
|
sound = bge.logic.globalDict["sounds"][code]
|
||
|
try:
|
||
|
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_Controll.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"]
|
||
|
|
||
|
|
||
|
#### SAVE / MULTIPLAYER FUNCTIONS #####
|
||
|
|
||
|
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" : list(car.orientation.to_euler())
|
||
|
}
|
||
|
|
||
|
# 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"]
|
||
|
|
||
|
if netId and netId not in netObjects["netId"] or new:
|
||
|
|
||
|
car = Spawn(carModel, position, orientation)
|
||
|
UpdateCar(car)
|
||
|
|
||
|
print("Spawned cause", netId, "netId isn't in", list(netObjects["netId"].keys()))
|
||
|
|
||
|
netObjects["netId"][netId] = car
|
||
|
|
||
|
|
||
|
|
||
|
car = netObjects["netId"][netId]
|
||
|
car["netId"] = netId
|
||
|
car["ownerId"] = cardata.get("ownerId")
|
||
|
|
||
|
if math.dist(car.position, position) > 0.1:
|
||
|
car.position = position
|
||
|
if math.dist(car.orientation.to_euler(), orientation) > 0.1:
|
||
|
car.orientation = orientation
|
||
|
|
||
|
|
||
|
# 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)
|
||
|
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)
|
||
|
|
||
|
# Colors
|
||
|
for attribute in cardata.get("colors", {}):
|
||
|
value = cardata["colors"][attribute]
|
||
|
Material(car, value, attribute)
|
||
|
|
||
|
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]
|
||
|
|
||
|
|
||
|
#### 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)
|
||
|
|