# GPLv3 or later # (C) J.Y.Amihud ( blenderdumbass ) 2024 # Multiplayer client functions import bge import time import json import zlib import traceback from Scripts import Vehicle from Scripts import Character_Controll from Scripts import Opt from Scripts import Settings from Scripts.Multiplayer_Shared import * from Scripts import Reuse from Scripts.Common import * settings = Settings.load_settings() host = settings.get("mp-host", "") def DescribeScene(): scene = bge.logic.getCurrentScene() dani = scene.objects["Dani_Box"] cam = scene.active_camera garage = scene.objects["GarageColider"] garageDistance = 31 chunksize = 250 data = {} addr = Opt.Address(dani.position, chunksize) if addr not in data: data[addr] = [] danidata = Character_Controll.Encode(dani) data[addr].append(danidata) userId = bge.logic.globalDict.get("userId") for car in bge.logic.globalDict["allcars"]: if car.get("inview") and car.getDistanceTo(garage) > garageDistance: if car.get("ownerId") and car.get("ownerId") != userId: continue addr = Opt.Address(car.position, chunksize) cardata = Vehicle.Encode(car) if addr not in data: data[addr] = [] data[addr].append(cardata) return data def MainLoop(): scene = bge.logic.getCurrentScene() dani = scene.objects["Dani_Box"] cam = scene.active_camera chunksize = 250 bge.logic.globalDict["networkQueue"] = {} while True: try: # A bit of delay, to not owerwhelm the server time.sleep(0.1) # Testing if the game is still runing. # it will fail when the game engine stops. try: bge.logic.getRealTime() except: return if bge.logic.globalDict.get("restore-physics-timer"): continue if bge.logic.globalDict.get("network-scene"): continue ######### SENDING THE DATA TO SERVER ############ data = {} # Login LoginEncode(data) # Scene data["scene"] = DescribeScene() data["vision"] = Opt.Surround(cam.position, chunksize, cam.orientation.to_euler()) # Queue networkQueue = bge.logic.globalDict["networkQueue"] for key in networkQueue: if networkQueue[key]: data[key] = networkQueue[key].pop(0) ######### DEALING WITH RESPONSE ############ beforeSend = time.time() data = Send(host, data) afterSend = time.time() PING = afterSend - beforeSend bge.logic.globalDict["ping"] = PING for key in data: payload = data[key] if key == "login": LoginDecode(payload) elif key == "scene": bge.logic.globalDict["network-scene"] = payload elif key == "message": bge.logic.globalDict["print"] = payload elif key == "timing": bge.logic.globalDict["netTiming"] = payload elif key == "serverTime": bge.logic.globalDict["serverTime"] = payload bge.logic.globalDict["recievedTime"] = time.time() else: print(key, payload) except Exception: print("\n\nNETWORK CLIENT ERROR:", clr["bold"]+clr["tdrd"], traceback.format_exc(), clr["bold"]) def SecondaryLoop(): # This loop runs in the game loop, not the separate thread. # And it deals with updates sent by the other thread. if bge.logic.globalDict.get("network-scene"): data = bge.logic.globalDict["network-scene"] SceneDecode(data) bge.logic.globalDict["network-scene"] = None def LoginEncode(data): data["login"] = {} data["login"]["userId"] = bge.logic.globalDict.get("userId") data["login"]["username"] = settings.get("mp-name") data["login"]["room"] = settings.get("mp-room") data["login"]["ping"] = bge.logic.globalDict.get("ping", 0) def LoginDecode(data): if data.get("userId"): bge.logic.globalDict["userId"] = data["userId"] if data.get("room"): settings["mp-room"] = data["room"] def SceneDecode(data): netObjects = bge.logic.globalDict["netObjects"] if not data: return rIds = [] # Recieved netIds for addr in data: chunk = data[addr] for obj in chunk: # update = True # if obj.get("netId") in netObjects["netId"]: # if obj.get("ownerId"): # netObjects["netId"][obj.get("netId")]["ownerId"] = obj.get("ownerId") # obj["ID"] = id(netObjects["netId"][obj.get("netId")]) # Sometimes we want to update some things like netId. if obj.get("ID") in netObjects["pythonId"]\ and obj.get("name") == netObjects["pythonId"][obj["ID"]].name: netObjects["pythonId"][obj["ID"]]["netId"] = obj.get("netId") netObjects["netId"][obj.get("netId")] = netObjects["pythonId"][obj["ID"]] if obj.get("type") == "veh": Vehicle.Decode(obj, network=True) elif obj.get("type") == "chr": Character_Controll.Decode(obj, network=True) rIds.append(obj.get("netId")) # Cleaning objects that disappeared delete = [] for netId in netObjects["netId"]: if netId not in rIds and netObjects["netId"][netId].get("ownerId") and netObjects["netId"][netId].get("ownerId") != bge.logic.globalDict.get("userId"): delete.append(netId) for netId in delete: netObjects["netId"][netId]["netId"] = None Reuse.Delete(netObjects["netId"][netId]) del netObjects["netId"][netId] def QueueToSend(key, data): networkQueue = bge.logic.globalDict["networkQueue"] if key not in networkQueue: networkQueue[key] = [] networkQueue[key].append(data) def GrabOwnership(obj): # This function grabs ownership of an object. # Like when a player steals a car spawned by # another player. userId = bge.logic.globalDict["userId"] ownerId = obj.get("ownerId") netId = obj.get("netId") # First we will check that the ownership isn't ours. if userId != ownerId and netId: QueueToSend("change-ownership",{ "netId" :netId, "userId":userId }) obj["ownerId"] = userId def Latency(obj): # Calculates latency of the object. ping = bge.logic.globalDict.get("ping", 0) ownerId = obj.get("ownerId", "") netTiming = bge.logic.globalDict.get("netTiming", 0) serverTime = bge.logic.globalDict.get("serverTime", 0) recievedTime = bge.logic.globalDict.get("recievedTime") owner = netTiming.get(ownerId, {}) ownerTimeStamp = owner.get("timestamp",0) ownerPing = owner.get("ping",0) serverLatency = ( serverTime - ownerTimeStamp ) clientLatency = ( time.time() - recievedTime ) latency = ping + ownerPing + serverLatency + clientLatency return latency