DanisRace/Scripts/Vehicle.py

4565 lines
129 KiB
Python
Raw Permalink Normal View History

2024-07-13 15:15:50 +02:00
# 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
2024-08-27 14:34:26 +02:00
import traceback
2024-07-13 15:15:50 +02:00
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):
2024-08-27 14:34:26 +02:00
if car in Reuse.reuse.get(car.name, []):
2024-07-13 15:15:50 +02:00
Reuse.reuse[car.name].remove(car)
2024-08-27 14:34:26 +02:00
if car.get("driver"):
driver = car.get("driver")
Character_Controll.getIntoCar(driver, car, immediately=True)
2024-07-13 15:15:50 +02:00
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)
2024-08-27 14:34:26 +02:00
elif NetworkControlled(car):
pass
2024-07-13 15:15:50 +02:00
# If nobody controls the car.
else:
IdleBraking(car)
2024-08-27 14:34:26 +02:00
# Network control
if NetworkControlled(car):
NetworkControl(car)
2024-07-13 15:15:50 +02:00
# 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)
2024-08-27 14:34:26 +02:00
#if not car.get("underwater"):
# car.applyTorque([-z/2*(car.localLinearVelocity[1] < 0)
# , 0,0], True)
2024-07-13 15:15:50 +02:00
2024-08-27 14:34:26 +02:00
if car.get("handbraking"):
HandBraking(car)
2024-07-13 15:15:50 +02:00
# 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.
2024-08-27 14:34:26 +02:00
# if not car.get("braking"):
# car["driftturn"] = 0.0
2024-07-13 15:15:50 +02:00
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)
2024-08-27 14:34:26 +02:00
# Centering the cursor when pressing RMB
mouseinput = bge.logic.globalDict["mouse"]
if mousecodes["RMB"] in mouseinput:
Mouse.CenterCursor()
# LMB brakes
if mousecodes["LMB"] in mouseinput:
v = -car.localLinearVelocity[1]
ActivateBreaks(car, v, True)
2024-07-13 15:15:50 +02:00
# 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
2024-08-27 14:34:26 +02:00
pos[1] = ( ( ( pos[1] * p1 ) ** 2 ) * p1)
2024-07-13 15:15:50 +02:00
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)
2024-08-27 14:34:26 +02:00
car["turn"] = -( ( ( pos[0] * p0 ) ** 2 ) * p0)
2024-07-13 15:15:50 +02:00
# 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.
2024-08-27 14:34:26 +02:00
strenght = factor
if strenght < 0: strenght *= -1
2024-07-13 15:15:50 +02:00
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"] ):
2024-08-27 14:34:26 +02:00
getCarPhysics(car).applyBraking(Abs*health*strenght, n)
2024-07-13 15:15:50 +02:00
else:
getCarPhysics(car).applyBraking(0, n)
# Stablizing the brakes
2024-08-27 14:34:26 +02:00
if stable and car["specs"].get("stableBrake", True):
2024-07-13 15:15:50 +02:00
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]
2024-08-27 14:34:26 +02:00
health = (wheelObject["health"] + car["health"]) / 2
2024-07-13 15:15:50 +02:00
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
2024-08-27 14:34:26 +02:00
if not car.get("accelerating") and not NetworkControlled(car):
2024-07-13 15:15:50 +02:00
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
2024-08-27 14:34:26 +02:00
if not car.get("handbraking", 0):
S = car["specs"].get("turnFake", 20)
2024-07-13 15:15:50 +02:00
2024-08-27 14:34:26 +02:00
if v > 0.01 and vo < 0:
car.applyTorque([0,0,car["turn"] * S * car.mass],True)
elif v > 0.01 and vo > 0:
car.applyTorque([0,0,car["turn"] * -S * car.mass],True)
2024-07-13 15:15:50 +02:00
# 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()
2024-08-27 14:34:26 +02:00
settings = bge.logic.globalDict.get("settings", {})
2024-07-13 15:15:50 +02:00
if car["turn"] > 0: car["turn"] *= -0.5
2024-08-27 14:34:26 +02:00
car["turn"] -= 0.005 * toFPS * settings.get("turn", 1.0)
2024-07-13 15:15:50 +02:00
car["turning"]= True
def TurnLeft(car):
toFPS = Opt.ToFPS()
2024-08-27 14:34:26 +02:00
settings = bge.logic.globalDict.get("settings", {})
2024-07-13 15:15:50 +02:00
if car["turn"] < 0: car["turn"] *= -0.5
2024-08-27 14:34:26 +02:00
car["turn"] += 0.005 * toFPS * settings.get("turn", 1.0)
2024-07-13 15:15:50 +02:00
car["turning"]= True
def HandBrake(car):
2024-08-27 14:34:26 +02:00
car["handbraking"] = 30
def HandBraking(car):
2024-07-13 15:15:50 +02:00
# 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.
2024-08-27 14:34:26 +02:00
cv = car.localLinearVelocity
v = cv.y
if v <0: v *= -1
car["handbraking"] = car.get("handbraking", 30) - 1
if v < 10:
car["handbraking"] = 0
if car.get("accelerating"):
return
turning = Turning(car)
if turning and not car["handbraking"]:
car["handbraking"] = 2
hbf = car["handbraking"] / 30
if car["handbraking"] <= 0:
car["driftturn"] = 0.0
if not OnGround(car):
return
2024-07-13 15:15:50 +02:00
factor = car.localLinearVelocity[1]
2024-08-27 14:34:26 +02:00
if factor > 0: factor = 1.0
else : factor = -1.0
if car.get("accelerating"):
factor /= 20
ActivateBreaks(car, factor*hbf)
2024-07-13 15:15:50 +02:00
# 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
2024-08-27 14:34:26 +02:00
if ( dt < turning and ( ( car.get("driftturn",0 ) < 0 ) == ( car["turn"] < 0 ) ) )\
or not car.get("driftturn",0 ):
2024-07-13 15:15:50 +02:00
2024-08-27 14:34:26 +02:00
car["driftturn"] = float(car["turn"])
2024-07-13 15:15:50 +02:00
# Adding fake turn to the car
2024-08-27 14:34:26 +02:00
car.applyTorque([0,
2024-07-13 15:15:50 +02:00
0,
2024-08-27 14:34:26 +02:00
((car["driftturn"])+(car["turn"]/2))*car.mass*5*hbf
2024-07-13 15:15:50 +02:00
], True)
2024-08-27 14:34:26 +02:00
# Instead of reducing friction on the wheels, this
# will simulate that, but with more control.
if car.get("accelerating"):
cv.x += -car["driftturn"] * v / 50 *hbf
cv.y += turning * v / 20 * hbf
car.applyForce(cv * car.mass * 0.5 * hbf, True)
2024-07-13 15:15:50 +02:00
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.
2024-08-27 14:34:26 +02:00
if "water" in str(material).lower() or "water" in str(obj).lower():
2024-07-13 15:15:50 +02:00
# 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
2024-08-27 14:34:26 +02:00
if force < 0.1:
ScrapeSound(car, position=point.worldPoint, volume=max(1,min(3,force*200)))
2024-07-13 15:15:50 +02:00
else:
2024-08-27 14:34:26 +02:00
HitSound(car, position=point.worldPoint, volume=min(3,force/2))
2024-07-13 15:15:50 +02:00
# 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 ) :
2024-08-27 14:34:26 +02:00
wheelObject["health"] = max(0, wheelObject["health"] - ( force / 50 ))
2024-07-13 15:15:50 +02:00
# 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]
2024-08-27 14:34:26 +02:00
character["carblewup"] = car
character["target"] = car.get("target")
2024-07-13 15:15:50 +02:00
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)
2024-08-27 14:34:26 +02:00
for wheel in car.get("wheels", []):
2024-07-13 15:15:50 +02:00
ColorPart(car, wheel)
# Neon
2024-08-27 14:34:26 +02:00
if attribute == "SecondaryColor" and car in bge.logic.globalDict["cars"]:
2024-07-13 15:15:50 +02:00
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":
2024-08-27 14:34:26 +02:00
for attribute in ["MainColor", "SecondaryColor"]:
2024-07-13 15:15:50 +02:00
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
2024-08-27 14:34:26 +02:00
if not body.get("mesh", "Good") == "Borked":
body.replaceMesh(car.get("borked", "Borked"))
GlassSound(car, position=car.position, volume=3)
body["mesh"] = "Borked"
2024-07-13 15:15:50 +02:00
if "Neon" in car:
car["Neon"].blenderObject.data.energy = 0
# Otherwise restore the car
else:
try:
2024-08-27 14:34:26 +02:00
if not body.get("mesh", "Good") == "Good":
body.replaceMesh(body["good"])
body["mesh"] = "Good"
2024-07-13 15:15:50 +02:00
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"]
2024-08-27 14:34:26 +02:00
Destruction.Smoke(smokeemiter.position, smokeCoefitient, smokeemiter.scaling.x)
2024-07-13 15:15:50 +02:00
Destruction.Fire(smokeemiter.position, fireCoefitient)
if "FireBall" not in car and health < 0.3:
car["FireBall"] = Destruction.AttatchFireBall(smokeemiter.position,
smokeemiter,
1.5)
2024-08-27 14:34:26 +02:00
car["FireBall"].scaling = smokeemiter.scaling
2024-07-13 15:15:50 +02:00
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 \
2024-08-27 14:34:26 +02:00
and spawn.get("npc") != "racer"\
and not spawn.get("priority") == True:
2024-07-13 15:15:50 +02:00
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
2024-08-27 14:34:26 +02:00
if ( spawn.get("to_spawn", True) or spawn.get("npc") == "npc" )\
2024-07-13 15:15:50 +02:00
or ( spawn.get("npc") == "racer" and aftercheck
and spawn.get("to_spawn") and ( not dani.get("race") or duringcheck )):
# Spawning the car.
2024-08-27 14:34:26 +02:00
driver = "Man1Box"
if not spawn.get("npc"):
carModel = spawn.get("spawn")
driver = None
2024-07-13 15:15:50 +02:00
car = Spawn(
carModel,
spawn["position"],
spawn["orientation"],
True,
"pallete",
spawnanyway=False,
2024-08-27 14:34:26 +02:00
spawn=spawn,
driver=driver)
2024-07-13 15:15:50 +02:00
2024-08-27 14:34:26 +02:00
if not car and (spawnedCars < maxCars or spawn.get("npc") == "racer"\
or spawn.get("priority")):
2024-07-13 15:15:50 +02:00
car = Spawn(
carModel,
spawn["position"],
spawn["orientation"],
True,
"pallete",
spawnanyway=True,
2024-08-27 14:34:26 +02:00
spawn=spawn,
driver=driver)
2024-07-13 15:15:50 +02:00
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
2024-08-27 14:34:26 +02:00
2024-07-13 15:15:50 +02:00
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.
2024-08-27 14:34:26 +02:00
spawn=None, # Spawn data from SpawnLogic()
driver=None):
2024-07-13 15:15:50 +02:00
2024-08-27 14:34:26 +02:00
#traceback.print_stack()
# This function is to simplify spawning
2024-07-13 15:15:50 +02:00
# of the car.
2024-08-27 14:34:26 +02:00
cam = bge.logic.getCurrentScene().active_camera
2024-07-13 15:15:50 +02:00
# 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
2024-08-27 14:34:26 +02:00
# Adding a driver
if driver:
if not car.get("driver"):
if "drivers" not in bge.logic.globalDict:
bge.logic.globalDict["drivers"] = []
found = False
for d in bge.logic.globalDict["drivers"]:
if d.name == driver and d.getDistanceTo(cam) > 200\
and not d.get("driving"):
driver = d
Character_Controll.getOutCar(driver)
found = True
print(consoleForm(driver),clr["tdgr"], "reusing driver", clr["norm"])
if not found:
driver = Reuse.Create(driver)
bge.logic.globalDict["drivers"].append(driver)
SmartColor(driver, "random")
print(consoleForm(driver),clr["tdrd"], "new driver", clr["norm"])
Character_Controll.getIntoCar(driver, car, immediately=True)
elif car.get("driver"):
print(consoleForm(car),clr["tdrd"], "Driver Deleted", clr["norm"])
driver = car["driver"]
car["driver"] = ""
Character_Controll.getOutCar(driver)
Reuse.Delete(driver)
print(consoleForm(car), consoleForm(car.get("driver")), car.get("npc"))
2024-07-13 15:15:50 +02:00
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"
2024-08-27 14:34:26 +02:00
# Getting the target car
elif not target.get("driver") and car.get("driver") and target in bge.logic.globalDict["cars"]:
driver = car["driver"]
Character_Controll.getOutCar(driver)
driver["NPCcar"] = target
car["npc"] = ""
2024-07-13 15:15:50 +02:00
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:
2024-08-27 14:34:26 +02:00
if vect[0] > 5:
Accelerate(car, 1)
else:
Accelerate(car, -1)
2024-07-13 15:15:50 +02:00
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:
2024-08-27 14:34:26 +02:00
ActivateBreaks(car, v)
2024-07-13 15:15:50 +02:00
# 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 ]
2024-08-27 14:34:26 +02:00
try: checkpoint0 = race["checkpoints"][ checknumber - 2 ]
except: checkpoint0 = race["checkpoints"][ 0 ]
2024-07-13 15:15:50 +02:00
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", []):
2024-08-27 14:34:26 +02:00
wheel["health"] = max(wheel["health"], 0.5)
2024-07-13 15:15:50 +02:00
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:
2024-08-27 14:34:26 +02:00
# tocheck = car.getVectTo(checkpoint0["location"])
# car.alignAxisToVect(tocheck[1], 1, 0.5)
2024-07-13 15:15:50 +02:00
car.alignAxisToVect( (0,0,1), 2, 1.0 )
2024-08-27 14:34:26 +02:00
tocheck = car.getVectTo(checkpoint2["location"])
car.alignAxisToVect(-tocheck[1], 1, 1)
2024-07-13 15:15:50 +02:00
#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)
2024-08-27 14:34:26 +02:00
vs = car["specs"].get("visionSize", 1.0)
2024-07-13 15:15:50 +02:00
# Making sure we scan a wider range
2024-08-27 14:34:26 +02:00
height = -3*vs#random.uniform(-6, -3)
2024-07-13 15:15:50 +02:00
width = -10#random.uniform(-15, -10)
# Getting the rays
toright = RelativePoint(car, (-5, width, height))
2024-08-27 14:34:26 +02:00
fromright = RelativePoint(car, (-0.8*vs,0+vo,0.5*vs))
2024-07-13 15:15:50 +02:00
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))
2024-08-27 14:34:26 +02:00
fromleft = RelativePoint(car, (0.8*vs,0+vo,0.5*vs))
2024-07-13 15:15:50 +02:00
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))
2024-08-27 14:34:26 +02:00
fromupright = RelativePoint(car, (-0.8*vs,0+vo,0.5*vs))
2024-07-13 15:15:50 +02:00
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))
2024-08-27 14:34:26 +02:00
fromupleft = RelativePoint(car, (0.8*vs,0+vo,0.5*vs))
2024-07-13 15:15:50 +02:00
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))
2024-08-27 14:34:26 +02:00
fromforward = RelativePoint(car, (0,-2+vo,0.5 *vs))
2024-07-13 15:15:50 +02:00
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))
2024-08-27 14:34:26 +02:00
fromupforward = RelativePoint(car, (0,-2+vo,0.5*vs))
2024-07-13 15:15:50 +02:00
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)
2024-08-27 14:34:26 +02:00
st = 5 / vs
2024-07-13 15:15:50 +02:00
turn = max(-0.8, min(0.8, (( max(0, min(1, ( right / st ))) - max(0, min(1, ( left / st ))) ) *-2 )))
2024-08-27 14:34:26 +02:00
turn /= ( vs ** 2 )
2024-07-13 15:15:50 +02:00
# 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 #####
2024-08-27 14:34:26 +02:00
def EncodeRotation(car):
rot = list(car.worldOrientation)
for n, i in enumerate(rot):
rot[n] = list(i)
return rot
2024-07-13 15:15:50 +02:00
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),
2024-08-27 14:34:26 +02:00
"orientation" : EncodeRotation(car),
"linvelocity" : list(car.worldLinearVelocity),
"angvelocity" : list(car.worldAngularVelocity),
"npc" : car.get("npc", ""),
"rpm" : car.get("rpm", 0),
"gear" : car.get("gear", 0),
"turn" : car.get("turn", 0)
2024-07-13 15:15:50 +02:00
}
2024-08-27 14:34:26 +02:00
2024-07-13 15:15:50 +02:00
# 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)
2024-08-27 14:34:26 +02:00
for attribute in cardata.get("colors", {}):
value = cardata["colors"][attribute]
Material(car, value, attribute)
2024-07-13 15:15:50 +02:00
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")
2024-08-27 14:34:26 +02:00
if network:
latency = Multiplayer_Client.Latency(car)
else:
latency = 0
car["npc"] = cardata.get("npc", "")
car["rpm"] = cardata.get("rpm", 0)
car["gear"] = cardata.get("gear", 0)
car["turn"] = cardata.get("turn", 0)
car.worldLinearVelocity = cardata.get("linvelocity", car.localLinearVelocity)
car.worldAngularVelocity = cardata.get("angvelocity", car.localAngularVelocity)
position = mathutils.Vector(position)+(car.worldLinearVelocity*latency)
if math.dist(car.position, position) > 5:
car.position = position
car.worldOrientation = orientation
#car.applyRotation(car.worldAngularVelocity*latency,False)
2024-07-13 15:15:50 +02:00
# 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)
2024-08-27 14:34:26 +02:00
if door.visible != doordata.get("visible", True):
door.visible = doordata.get("visible", True)
2024-07-13 15:15:50 +02:00
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
2024-08-27 14:34:26 +02:00
#for attribute in cardata.get("colors", {}):
# value = cardata["colors"][attribute]
# Material(car, value, attribute)
2024-07-13 15:15:50 +02:00
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]
2024-08-27 14:34:26 +02:00
def NetworkControlled(car):
return car.get("netId") in bge.logic.globalDict.get("netObjects", {}).get("netId", {})\
and car.get("onwerId") != bge.logic.globalDict.get("userId")
def NetworkControl(car):
return
2024-07-13 15:15:50 +02:00
#### 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)