# GPLv3 or later # ( C ) J.Y.Amihud 2024 # This module is to deal with racing. import os import bge import bpy import json import datetime from Scripts import Script from Scripts import Money from Scripts import Map from Scripts import Reuse from Scripts.Common import * def intime(race): # Checks if the current ingame corresponds # to the time at which this race is available. TimeRange = race.get("TimeRange") timeis = bge.logic.globalDict.get("time", 0) if not TimeRange: return True # Standard ranges, like 12 -> 14, or 6 -> 12 elif TimeRange[0] < TimeRange[1]: return TimeRange[0] <= timeis <= TimeRange[1] # Other ranges, like 21 -> 6, 17 -> 12 else: return timeis < TimeRange[1] or timeis > TimeRange[0] def renderTimeRange(race): # Renders a time range of the race. TimeRange = race.get("TimeRange") min0 = str(int(60*(TimeRange[0]-int(TimeRange[0])))) if len(min0) < 2: min0 = "0"+min0 min1 = str(int(60*(TimeRange[1]-int(TimeRange[1])))) if len(min1) < 2: min1 ="0"+min1 hrs0 = str(int(TimeRange[0])) if len(hrs0) < 2: hrs0 = "0"+hrs0 hrs1 = str(int(TimeRange[1])) if len(hrs1) < 2: hrs1 = "0"+hrs1 r = "between "+hrs0+":"+min0+" and "+hrs1+":"+min1 return r def Races(): return bge.logic.globalDict["races"] def MainLoop(spawnAtDistance=250): # This runs in main() on every frame # to make sure races work. races = Races() scene = bge.logic.getCurrentScene() keys = bge.logic.globalDict["keys"] dani = scene.objects["Dani_Box"] cam = scene.active_camera for racename in races: race = races[racename] # Show the race on the map if Available(race, formap=True): Map.Show(race["starters"][0]["location"], icon="Map_Circle", color=[0.01,0.01,1], ID=racename) # Starting the race! if Available(race): for starter in race["starters"]: # Blue cylinder if dani.getDistanceTo(starter["location"]) < spawnAtDistance: if not starter["cylinder"]: AddCylinder("Starter.Cylinder", starter) elif starter["cylinder"]: DeleteCylinder(starter) if dani.getDistanceTo(starter["location"]) < starter["radius"]: if CanStartRace(race, dani): # Start the race if keycodes["R"] in keys: PayBid(race, dani) StartRace(racename, dani) # When the race is going! elif dani.get("race") == racename: # Next checkpoint in the race nextcheck = race["checkpoints"][dani["checkpoint"]] nextcheckpoint = race["checkpoints"][ ( dani.get("checkpoint") + 1 ) % len(race["checkpoints"]) ] # UI objects pointer = scene.objects["PointingArrow"] posindicator = scene.objects["Dani_Pos_Indicator"] timeindicator = scene.objects["Dani_Time_Indicator"] lapindicator = scene.objects["Dani_Lap_Indicator"] # Alight the arrow to the checkpoint tocheck = pointer.getVectTo(nextcheck["location"]) pointer.alignAxisToVect(tocheck[1], 1, 0.1) if cam.pointInsideFrustum(nextcheck["location"]) == cam.INSIDE: tonextcheck = pointer.getVectTo(nextcheckpoint["location"]) pointer.alignAxisToVect(tonextcheck[1], 1, min(1, max(0, 1-(tocheck[0]/500))) ** 5 ) pointer.alignAxisToVect( (0,0,1), 2, 1.0 ) # Time of the race currentRaceTime = InRaceTime(race) formattedTime = FormatInRaceTime(currentRaceTime) timeindicator["Text"] = formattedTime # Current lap lapindicator["Text"] = str(dani["lap"]+1)+"/"+str(race["laps"]) # Adding the next cylinder if not nextcheck["cylinder"] and dani.getDistanceTo(nextcheck["location"]) < spawnAtDistance: if dani["checkpoint"]+1 == len(race["checkpoints"]): cylinderType = "Starter.Cylinder" else: cylinderType = "Tag.Cylinder" AddCylinder(cylinderType, nextcheck) # Show two next cylinders on the map Map.Show(nextcheck["location"], icon="Map_Circle", color=[1,0.3,0], ID="RaceCheckpoint") Map.Show(nextcheckpoint["location"], icon="Map_Circle", color=[1,0.1,0], ID="RaceCheckpoint2") # Next checkpoint if dani.getDistanceTo(nextcheck["location"]) < nextcheck["radius"]*1.5: # Recording dani's resque point if not nextcheck.get("OnLoop"): dani["prevrescue"] = dani.get("rescue") dani["rescue"] = nextcheck["location"] dani["rescueTo"] = dani["checkpoint"] DeleteCylinder(nextcheck, ignoreTimer=True) # Logging player data for NPC training try: nextcheck["speed"] = -dani["driving"].localLinearVelocity[1] nextcheck["time"] = currentRaceTime nextcheck["rot"] = list(dani["driving"].orientation.to_euler()) except:pass # Advancing a checkpoint dani["checkpoint"] += 1 # Playing a Ding bge.logic.globalDict["SoundDevice"].play(bge.logic.globalDict["sounds"]["checkpoint"]["sound"]) # Next lap if dani["checkpoint"] == len(race["checkpoints"]): dani["checkpoint"] = 0 dani["lap"] += 1 # Finishing the race if dani["lap"] == race["laps"]: FinishRace(racename, dani) DeleteCylinder(nextcheck, ignoreTimer=False) continue UpdatePositions(race, dani) def StartRace(racename, dani=None): race = Races()[racename] race["started"] = True race["start-time"] = bge.logic.getRealTime() # Deleting starters for starter in race["starters"]: if starter["cylinder"]: DeleteCylinder(starter, ignoreTimer=True) # Making the racers race for racer in race["racers"]: racer["racing"] = True racer["launchtime"] = 100 racer["race"] = racename # Testing racer["npc"] = "racer" racer["active"] = False # Beam of energy racer["beam"] = Reuse.Create("Racer.Indicator") # If the race includes dani. if dani: dani["race"] = racename dani["checkpoint"] = 0 dani["lap"] = 0 # Make the racing UI visible scene = bge.logic.getCurrentScene() pointer = scene.objects["PointingArrow"] posindicator = scene.objects["Dani_Pos_Indicator"] timeindicator = scene.objects["Dani_Time_Indicator"] lapindicator = scene.objects["Dani_Lap_Indicator"] posindicator.visible = True pointer.visible = True timeindicator.visible = True lapindicator.visible = True # Play the start the race sound. bge.logic.globalDict["SoundDevice"].play(bge.logic.globalDict["sounds"]["active"]["sound"]) # Play a Ding def FinishRace(racename, dani=None): race = Races()[racename] race["started"] = False # Removing racers from the race race["racer_spawns"] = [] for racer in race["racers"]: racer["npc"] = "npc" racer["beam"].endObject() race["racers"] = [] if dani: # Make the racing UI invisible scene = bge.logic.getCurrentScene() pointer = scene.objects["PointingArrow"] posindicator = scene.objects["Dani_Pos_Indicator"] timeindicator = scene.objects["Dani_Time_Indicator"] lapindicator = scene.objects["Dani_Lap_Indicator"] posindicator.visible = False timeindicator.visible = False pointer.visible = False lapindicator.visible = False dani["race"] = None currentRaceTime = InRaceTime(race) formattedTime = FormatInRaceTime(currentRaceTime) # If won if race["positions"].index(dani) == 0: bge.logic.globalDict["print"] = "You've Won This Race!\nTime: "+formattedTime+"\nReward: $"+str(int(race.get("reward", 0))) Money.Recieve(race.get("reward", 0)) # If finished else: bge.logic.globalDict["print"] = "You finished number "+str(race["positions"].index(dani)+1) + " Time: "+formattedTime Script.StatusText(" ") def UpdatePositions(race, dani=None): positions = [] for racer in race["racers"]: racer["into_race"] = [race["laps"] - racer["lap"], len(race["checkpoints"]) - racer["checkpoint"], racer.getDistanceTo(race["checkpoints"][racer["checkpoint"]]["location"])] positions.append([racer["into_race"], racer]) if dani: dani["into_race"] = [race["laps"] - dani["lap"], len(race["checkpoints"]) - dani["checkpoint"], dani.getDistanceTo(race["checkpoints"][dani["checkpoint"]]["location"])] positions.append([dani["into_race"], dani]) positions = sorted(positions) race["positions"] = [] for position, racer in positions: race["positions"].append(racer) # Update the UI if dani: scene = bge.logic.getCurrentScene() posindicator = scene.objects["Dani_Pos_Indicator"] posindicator["Text"] = str(race["positions"].index(dani)+1) # The positions text ( with Dani's car being Bold and yellow ) pt = "" bold = [] for n, p in enumerate(race["positions"]): n += 1 if p == dani: try: t = dani.get("driving", "On Foot")["specs"]["name"]+" | "+str(n)+"\n" except: t = str(dani.get("driving", "On Foot"))+" | "+str(n)+"\n" bold = range(len(pt), len(pt)+len(t)) else: try: t = p["specs"]["name"]+" | "+str(n)+"\n" except: t = str(p)+" | "+str(n)+"\n" pt = pt + t Script.StatusText(pt, bold) def Available(race, formap=False, forspawn=False): # This function checks if the race is available. scene = bge.logic.getCurrentScene() dani = scene.objects["Dani_Box"] # Variable needed to check if the race is avaible # If the race is available at this time INTIME = intime(race) # If the race has any reward we check that dani # has the money to bid on the race. reward = race.get("reward", 1000) bid = reward / (race.get("amount-racers", 1)+1) HASBID = Money.Have(bid) # If the race is unlocked after = race.get("after") HASAFTER = after in Script.Story["passed"] or not after # If could be a during-mission race. during = race.get("during") ISDURING = dani.get("race") == after and Script.Story.get(during) # If the cars have spawned # We ignore this for if we want to spawn the cars havespawned = len(race["racers"]) >= race.get("amount-racers", 1) SPAWNEDCHECK = ( havespawned or ( forspawn and not havespawned ) or formap ) and INTIME # If the race already started NOTSTARTED = not race.get("started") and ( not dani.get("race") or ISDURING ) available = ( NOTSTARTED and SPAWNEDCHECK and ( HASAFTER or ISDURING ) and ( HASBID or ISDURING ) ) return available def InRaceTime(race): return bge.logic.getRealTime() - race["start-time"] def FormatInRaceTime(currentRaceTime): formattedTime = str(datetime.timedelta(seconds=currentRaceTime))[:-4] while formattedTime.startswith("0") or formattedTime.startswith(":"): formattedTime = formattedTime[1:] if formattedTime.startswith("."): formattedTime = "0"+formattedTime return formattedTime def CanStartRace(race, dani, printback=False): # Checks whether can start a race. # Based on a few things, like the type # of a car, or whether he is even with # a car. # Checking car type: NPC_type_cars = bge.logic.globalDict.get("NPC_type_cars", {}) cartype = race.get("type") if dani["driving"] and dani["driving"].name in NPC_type_cars.get(cartype,[]): # Checking whether the if not intime(race): if printback: bge.logic.globalDict["print"] = "Come here "+Racing.renderTimeRange(race) return False # Checking if this is during a mission if printback: DURING = dani.get("race") == race.get("after") and Script.Story.get(during) reward = race.get("reward", 1000) bid = reward / (race.get("amount-racers", 1)+1) if not DURING: bge.logic.globalDict["print"] = "Press R to start the race.\nEverybody bids $"+str(int(bid))+"\nWinner gets $"+str(int(reward)) else: bge.logic.globalDict["print"] = "Press R to start the race." return True elif printback: bge.logic.globalDict["print"] = "Come here with a "+cartype+" to race." return False def PayBid(race, dani): during = race.get("during") DURING = dani.get("race") == race.get("after") and Script.Story.get(during) reward = race.get("reward", 1000) bid = reward / (race.get("amount-racers", 1)+1) if not DURING: Money.Pay(bid) def AddCylinder(cylinderType, starter): # Adds a race cylinder into the game world. # Timer to prevent over, spawning / dispawning. if starter.get("make_delete_timer", 0) + 15 > bge.logic.getRealTime(): return starter["make_delete_timer"] = bge.logic.getRealTime() starter["cylinder"] = Reuse.Create(cylinderType) starter["cylinder"].position = starter["location"] starter["cylinder"].orientation = starter["rotation"] starter["cylinder"].scaling[0] = starter["radius"] starter["cylinder"].scaling[1] = starter["radius"] def DeleteCylinder(starter, ignoreTimer=False): # Timer to prevent over, spawning / dispawning. if not ignoreTimer: if starter.get("make_delete_timer", 0) + 15 > bge.logic.getRealTime(): return starter["make_delete_timer"] = bge.logic.getRealTime() Reuse.Delete(starter["cylinder"]) starter["cylinder"] = None def Precalculate(): # Precalculates racing data. bge.logic.globalDict["races"] = {} for collection in bpy.data.collections['Races'].children: race = {"starters":[], "checkpoints":[], "racers":[], "racer_spawns": [], "started":False } # Getting racing data rdf = os.listdir(bge.logic.expandPath("//racedata")) if collection.name in rdf: fol = "//racedata/"+collection.name race["raceData"] = {} for f in os.listdir(bge.logic.expandPath(fol)): if f.endswith(".json"): with open(bge.logic.expandPath(fol+"/"+f)) as jf: rd = json.load(jf) race["raceData"][f.replace(".json", "")] = rd for object in collection.objects: tag = {"location":object.location, "rotation":object.rotation_euler, "radius":object.scale[0], "OnLoop":object.get("OnLoop"), "IgnoreVision":object.get("IgnoreVision"), "Uturn":object.get("Uturn"), "cylinder":None} # Race starters ( the blue cylinder ) if object.name.startswith("Starter"): race["starters"].append(tag) race["laps"] = object["laps"] race["after"] = object.get("after") race["during"] = object.get("during") race["type"] = object.get("type", "race-car") race["reward"] = object.get("reward", 1000) race["TimeRange"] = list(object.get("TimeRange",[])) # Race checkpoints ( the yellow cylinder ) else: race["checkpoints"].append(tag) bge.logic.globalDict["races"][collection.name] = race print(collection.name, race["TimeRange"])