using SimpleFileBrowser; using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using Unity.Netcode; using UnityEngine; using UnityEngine.XR.Interaction.Toolkit; //this script implements save logic for a room on any client public class RoomSaver : MonoBehaviour { //culture info for float parsing private CultureInfo ci = CultureInfo.InvariantCulture; [SerializeField] private ObjectSync objSyn; [SerializeField] private OBJLoader objLoader; private GameObject spawnedObject; [SerializeField] private Material defaultMat; private string osPath; // a class that can store all object data in one place (keep in mind DIFFERENT TO OTHER OBJData structs in other structs !!!) public class OBJData { public string objectName; public List vertices; public List triangles; public Vector3 position; public Quaternion rotation; public Vector3 scale; } //if a clients requests to create a save file, the server will call this function on the client [ClientRpc] public void GetAllRoomDataClientRPC(ulong clientID, ObjectSync.OBJData[] objData) { //only the requesting client should save the file if (clientID != NetworkManager.Singleton.LocalClientId) { return; } //generate a random string for save (save file can be renamed by user in their system file explorer) string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; char[] stringChars = new char[8]; System.Random random = new System.Random(); for (int i = 0; i < stringChars.Length; i++) { stringChars[i] = chars[random.Next(chars.Length)]; } //add the current date to the save file string curDate = new String(DateTime.Today.ToString("d")); string finalString = new String(stringChars) + "_" + curDate; //check if the saves directory does already existist -not-> create it if (!Directory.Exists(osPath + "vrcsaves")) { Directory.CreateDirectory(osPath + "vrcsaves"); } //the final path to the saves destination (please keep os specific file paths in mind "c:" for windows "/ etc." for unix based systems) string filePath = osPath + "vrcsaves/" + finalString.Replace("/", "_") + ".vrc"; StreamWriter writer = new StreamWriter(filePath); //enumerate all objects sent by the server and write into the savefile foreach (ObjectSync.OBJData o in objData) { writer.WriteLine("o"); foreach (Vector3 v in o.vertices) { writer.WriteLine("v$" + v.x + "$" + v.y + "$" + v.z); } foreach (int t in o.triangles) { writer.WriteLine("t$" + t); } writer.WriteLine("p$" + o.positionVector.x + "$" + o.positionVector.y + "$" + o.positionVector.z); writer.WriteLine("r$" + o.rotation.x + "$" + o.rotation.y + "$" + o.rotation.z + "$" + o.rotation.w); writer.WriteLine("s$" + o.scale.x + "$" + o.scale.y + "$" + o.scale.z); } //"save file changes" writer.Flush(); } //a client requesting to save will call this function with its id [ServerRpc] public void GetAllRoomDataServerRPC(ulong clientID) { GetAllRoomDataClientRPC(clientID, objSyn.objDataList.ToArray()); } //this function will be called from the menu bar if a player wants to save public void SaveScene() { //ask the server for the current data in the room GetAllRoomDataServerRPC(NetworkManager.Singleton.LocalClientId); } private void Start() { //set default save paths on windows and linux if (Application.platform == RuntimePlatform.LinuxEditor || Application.platform == RuntimePlatform.LinuxServer || Application.platform == RuntimePlatform.LinuxPlayer) { osPath = "/home/" + Environment.UserName + "/"; } else { osPath = "c:/"; } try { //try to get a filepath to load a a save file //if there is no save file a blank classroom will be created if (PlayerPrefs.GetString("path") != "") { string path = PlayerPrefs.GetString("path"); ReadEvenMoreObjDataFromFile(path); PlayerPrefs.SetString("path", ""); } } catch { Debug.Log("Fehler beim Lesen der Datei"); } } //This is a meme <-- XD //this function does read and load a save file private void ReadEvenMoreObjDataFromFile(string path) { List objDataList = new List(); //read the file data and split each line string[] lines = FileBrowserHelpers.ReadTextFromFile(path).Split("\n"); OBJData dataStruct = new OBJData(); string curString; foreach (string line in lines) { line.Trim(); curString = line.Replace("$", " "); //if there is a "o" the data block of a new object will begin if (curString.StartsWith("o")) { dataStruct = new OBJData(); dataStruct.vertices = new List(); dataStruct.triangles = new List(); objDataList.Add(dataStruct); } //if there is a "v" at the start of the line there is a vertex stored else if (curString.StartsWith("v")) { dataStruct.vertices.Add(ComeBackOfTheObjVector3StringToVector3(curString)); } //if there is a "t" at the start of the line there is a triangle index stored else if (curString.StartsWith("t")) { int curInt = int.Parse(curString.Trim().Replace("t", "")); dataStruct.triangles.Add((curInt)); } //if there is a "p" at the start of the line position of the gameobject is stored here else if (curString.StartsWith("p")) { dataStruct.position = ComeBackOfTheObjVector3StringToVector3(curString); } //if there is a "r" at the start of the line rotation of the gameobject is stored here else if (curString.StartsWith("r")) { dataStruct.rotation = ObjQuanternion(curString); } //if there is a "s" at the start of the line scale of the gameobject is stored here else if (curString.StartsWith("s")) { dataStruct.scale = ComeBackOfTheObjVector3StringToVector3(curString); } } //create a mesh/gameobject of each loaded object data for (int i = 0; i < objDataList.Count; i++) { GameObject finishedObj = objLoader.CreateMesh(objLoader.buildObjData(objDataList[i].vertices.ToArray(), objDataList[i].triangles.ToArray()), objDataList[i].position); //apply rotation and scale to the gameobject finishedObj.transform.rotation = objDataList[i].rotation; finishedObj.transform.localScale = objDataList[i].scale; //create info to keep track of the object in servers object list etc. ObjectSync.OBJData objsyncData = new ObjectSync.OBJData(); objsyncData.vertices = objDataList[i].vertices.ToArray(); objsyncData.triangles = objDataList[i].triangles.ToArray(); objsyncData.positionVector = objDataList[i].position; objsyncData.rotation = objDataList[i].rotation; Debug.Log(objDataList[i].scale); //add the object to the sync objsyncData.scale = objDataList[i].scale; objSyn.objDataList.Add(objsyncData); objSyn.spawnedObjects.Add(finishedObj); } } //This is another meme //this function creates a vector3 object from a vector stored in vrc file private Vector3 ComeBackOfTheObjVector3StringToVector3(string vecString) { vecString = vecString.Replace(",", "."); string[] lnSplit = vecString.Trim().Split(" "); return new Vector3(float.Parse(lnSplit[1], NumberStyles.Float, ci), float.Parse(lnSplit[2], NumberStyles.Float, ci), float.Parse(lnSplit[3], NumberStyles.Float, ci)); } //this function creates a quaternion object from a quaternion stored in vrc file private Quaternion ObjQuanternion(string quanString) { quanString = quanString.Replace(",", "."); string[] lnSplit = quanString.Trim().Split(" "); return new Quaternion(float.Parse(lnSplit[1], NumberStyles.Float, ci), float.Parse(lnSplit[2], NumberStyles.Float, ci), float.Parse(lnSplit[3], NumberStyles.Float, ci), float.Parse(lnSplit[4], NumberStyles.Float, ci)); } }