//------------------------------------------------------------------------------------------------------- // CTsetstate wrapper private void setState(String objectID, CTobject ctobject) { if (!CTlist.TryGetValue(objectID, out GameObject ct)) { return; } if (ct != null) { setState(objectID, ctobject, ct); } }
//---------------------------------------------------------------------------------------------------------------- // setState called from CTunity, works on active/inactive public void setState(CTobject cto, Boolean ireplay, Boolean iplayPaused) { if (ctunity == null) { return; // async } if (isLocalControl()) { return; // no updates for local player } ctobject = cto; // for public ref // set baseline for Lerp going forward to next step oldPos = transform.localPosition; oldRot = transform.localRotation; oldScale = transform.localScale; // following TrackSpeed tries to estimate average update-interval, but it varies too much to be smooth (mjm 1/18/19) // if (stopWatch > 0F) TrackSpeed = (3F * TrackSpeed + (1F / stopWatch)) / 4F; // weighted moving average // update targets stopWatch = 0F; myPos = cto.pos; myRot = cto.rot; myScale = cto.scale; // Debug.Log("myScale: " + myScale); myColor = cto.color; replayMode = ireplay; playPaused = iplayPaused; // playPaused used by smooth replay logic newCustom(cto.custom); // locals for immediate action: if (replayMode || !isLocalObject()) { // gameObject.SetActive(cto.state); // need to activate here (vs Update callback) // setColor(cto.color); // set color for non-local objects } if (rb == null) { rb = GetComponent <Rigidbody>(); // try again; async issue? } setGravity(); // Debug.Log(name+", ilc: "+isLocalControl()); startup = false; }
private void setState(String objectID, CTobject ctobject, GameObject ct) { if (ct == null || !ct.activeSelf) { return; } CTclient ctp = ct.GetComponent <CTclient>(); if (ctp != null) { ctp.setState(ctobject, replayActive, playPaused); if (newSession) { ctp.jumpState(); // do it now (don't wait for next ctclient.Update cycle) } } }
private IEnumerator deployInventoryItem(String deploy, String objID) { if (newSession) { Debug.Log("OOPS can't create new object during newSession setup!"); yield break; } String myPlayer = String.Copy(Player); // Debug.Log("deployInventory: " + deploy+", id: "+objID); WaitForSeconds delay = new WaitForSeconds(pollInterval); while (true) { yield return(delay); yield return(null); // and wait for next full Update // form HTTP GET URL string url1 = Server + "/CT/" + Session + "/" + Inventory + "/" + deploy + "/" + CTchannel + "?f=d"; UnityWebRequest www1 = UnityWebRequest.Get(url1); www1.SetRequestHeader("AUTHORIZATION", CTauthorization()); yield return(www1.SendWebRequest()); // proceed with parsing CTstates.txt if (!string.IsNullOrEmpty(www1.error) || www1.downloadHandler.text.Length < 10) { Debug.Log("Inventory Error: " + www1.error + ": " + url1); yield break; } CTdebug(null); // clear error // parse to class structure... List <CTworld> worlds = CTserdes.deserialize(this, www1.downloadHandler.text); if (worlds == null) { Debug.Log("Null worlds found for: " + url1); yield break; } if (!myPlayer.Equals(Player)) { Debug.Log("Player switch on mid-deploy: " + myPlayer + " -> " + Player); yield break; // player switched on us! } // instantiate World objects here (vs waiting for CT GET loop) foreach (CTworld world in worlds) { foreach (KeyValuePair <String, CTobject> ctpair in world.objects) { CTobject ctobject = ctpair.Value; if (objID != null) { ctobject.id = objID; } if (!ctobject.id.StartsWith(Player + "/")) { ctobject.id = Player + "/" + ctobject.id; // auto-prepend Player name to object } newGameObject(ctobject, true); // require parent in-place } } yield break; } // end while(true) }
private GameObject newGameObject(CTobject ctobject, Boolean requireParent) { // define parameters String objID = ctobject.id; String prefab = ctobject.model; Vector3 position = ctobject.pos; Quaternion rotation = ctobject.rot; Vector3 scale = ctobject.scale; Color color = ctobject.color; // Debug.Log("newGameObject: " + objID+", prefab: "+prefab+", custom: "+ctobject.custom+", prefab: "+prefab+", color: "+color); if (prefab.Equals("")) { return(null); // in-game player without prefab } // already exists? if (CTlist.ContainsKey(objID)) { GameObject go = CTlist[objID]; if (go != null) { go.SetActive(true); // let setState activate? CTclient ctc = go.gameObject.transform.GetComponent <CTclient>(); string ctpf = (ctc == null) ? "" : CTlist[objID].gameObject.transform.GetComponent <CTclient>().prefab; if (!ctpf.Equals(prefab) || scale == Vector3.zero) // prefab changed! { // Debug.Log(objID+": new prefab: " + prefab +", old pf: "+ctpf); position = go.transform.localPosition; // rebuild to new prefab (in-place) rotation = go.transform.localRotation; clearObject(objID); } else { // Debug.Log("newGameObject, replacing existing object: " + objID); clearObject(objID, true); // replace vs: // return CTlist[objID]; // maintain if already there } } } GameObject tgo = GameObject.Find(objID); if (tgo != null) { Debug.Log("Warning, existing object: " + objID); // unregistered prefabs hit this check // clearObject(tgo); // clear vs leave in place? return(tgo); } // load prefab GameObject pfgo = ((GameObject)getPrefab(prefab)); if (pfgo == null) { Debug.Log("NULL prefab: " + prefab); return(null); } // pfgo.SetActive(isactive); // build hierarchical path to new object: Players/<Player>/object-path String parent = "Players"; String fullpath = parent + "/" + objID; char[] delims = { '/', ':' }; String[] pathparts = fullpath.Split(delims); GameObject pgo = GameObject.Find(parent); // "Players" at this point // Debug.Log("create objID: " + objID+", fullpath: "+fullpath); for (int i = 1; i < pathparts.Length - 1; i++) { parent = parent + "/" + pathparts[i]; GameObject cgo = GameObject.Find(parent); if (cgo == null) { if (requireParent && i > 1) // sorry clugey. can happen with async deployInventory (e.g. Launcher) { Debug.Log("Null parent! " + objID); // this can strand empty grandparent (benign) return(null); } // Debug.Log("Create empty parent: " + pathparts[i]); cgo = new GameObject(); // empty gameObject; trunk-node in hierarchy cgo.name = pathparts[i]; cgo.transform.SetParent(pgo.transform, true); } pgo = cgo; } pgo = GameObject.Find(parent); if (pgo == null) // parent missing { Debug.Log("Missing parent: " + parent + ", child: " + objID); // init issue, catch it next update... return(null); } Transform tparent = pgo.transform; Transform pf = pfgo.transform; // Instantiate! // Transform newp = Instantiate(pf, position, rotation * pf.rotation, tparent); // rez prefab with set parent Transform newp = Instantiate(pf, tparent, false); // rez prefab with set parent newp.localPosition = position /* + pf.position */; // offset by prefab built-in position? newp.localRotation = rotation * pf.rotation; // Debug.Log(objID + ": instantiate child at: " + position+", parent: "+tparent.name); if (scale.Equals(Vector3.zero)) { newp.localScale = pf.localScale; } else { newp.localScale = scale; // zero scale means use initial prefab scale } newp.name = pathparts[pathparts.Length - 1]; CTclient myctc = newp.GetComponent <CTclient>(); if (myctc != null) { myctc.newCustom(ctobject.custom); // set this now vs waiting for setState myctc.prefab = prefab; myctc.setColor(color); } // make sure in CTlist (inactive objects won't call CTregister...) CTregister(newp.gameObject); return(newp.gameObject); }
//------------------------------------------------------------------------------------------------------- // newGameObject: create and instantiate new CTobject private GameObject newGameObject(CTobject ctobject) { return(newGameObject(ctobject, false)); }
//------------------------------------------------------------------------------------------------------- // mergeCTworlds: consolidate CTworlds from each player into local world-state CTworld mergeCTworlds(List <CTworld> worlds) { if (worlds == null) { // CTdebug("Warning: null CTworlds!"); return(null); // nobody home } double masterTime = ServerTime(); double updateTime = 0F; // build CTWorld structure from consolidated objects as owned (prefix) by each world CTworld CTW = new CTworld(); CTW.player = masterWorldName; // nominal CTW header values (not actually used) CTW.time = masterTime; CTW.objects = new Dictionary <String, CTobject>(); List <CTworld> tworldList = new List <CTworld>(); // second pass, screen masterTime, consolidate masterWorld foreach (CTworld world in worlds) { if (world.time > updateTime && !world.player.Equals(Player)) { updateTime = world.time; // keep track of most-recent CTW time } double delta = Math.Abs(world.time - masterTime); // masterTime NG on Remote... ??? if ((SyncTime > 0) && (delta > SyncTime)) // reject stale times { if (!world.player.Equals(Player) /* || observerFlag */) { clearWorld(world.player); // inefficient? } continue; } // if (world.objects.Count > 0) // skip empty place-holder worlds { world.active = (delta < LinkDeadTime); tworldList.Add(world); // build list of active worlds } // Debug.Log("mergeWorlds player: " + world.player+", objects: "+world.objects.Count); foreach (KeyValuePair <String, CTobject> ctpair in world.objects) { CTobject ctobject = ctpair.Value; // if (!ctobject.id.StartsWith(world.player + "/")) ctobject.id = world.player + "/" + ctobject.id; // auto-prepend world name to object // accumulate objects owned by each world try { CTW.objects.Add(ctobject.id, ctobject); } catch (Exception e) { Debug.Log("CTW exception on object: " + ctobject.id + ", e: " + e); } // set object state with CTclient GameObject mygo; Boolean isOnList = CTlist.TryGetValue(ctobject.id, out mygo); if (isOnList && mygo != null) // object exists { if ((isReplayMode() || !world.player.Equals(Player))) // check for change of prefab { string pf = mygo.transform.GetComponent <CTclient>().prefab; if (!pf.Equals(ctobject.model)) // recheck showMenu for async newPlayer { // Debug.Log("change prefab: " + ctobject.model + " --> " + pf); newGameObject(ctobject); } } setState(ctobject.id, ctobject, mygo); // use pre-fetched mygo if possible } else // instantiate new players and objects { if ((newSession || replayActive || !world.player.Equals(Player))) // mjm 12/3/18 { newGameObject(ctobject); } setState(ctobject.id, ctobject); } } } // scan for missing objects clearMissing(CTW); deltaWorldTime = updateTime - latestWorldTime; latestWorldTime = updateTime; // for replay reference WorldList = tworldList; // update set of worlds // Debug.Log("new Worldlist!"); foreach(CTworld ctw in WorldList) Debug.Log("ctw: " + ctw.player+", time: "+ctw.time+", nobjects: "+ctw.objects.Count); return(CTW); // consolidated CTworld }
/// <summary> /// Deserialize the given JSON string into a List of CTworld objects. /// The given string may contain one or a concatenation of several "world" objects. /// /// JSON example: /// {"mode":"Live","time":1.536844452785E9,"name":"Blue","objects":[{"id":"Blue","prefab":"Ball","state":true,"pos":[-8.380356788635254,0.25,3.8628578186035156],"rot":[0.0,0.0,0.0],"custom":""}]} /// /// </summary> /// <param name="strI">The JSON serialized world objects.</param> /// <returns>A List of CTworlds, parsed from the given string.</returns> private static List <CTworld> deserialize_json(CTunity ctunityI, string strI) { if ((strI == null) || (strI.Length < 10)) { return(null); } List <CTworld> worlds = new List <CTworld>(); // Break up the given string into different "world" objects // NOTE: This assumes that "player" is the first field in the JSON string List <int> indexes = AllIndexesOf(strI, @"{""player"":"""); if (indexes.Count == 0) { return(null); } for (int i = 0; i < indexes.Count; ++i) { int startIdx = indexes[i]; int endIdx = strI.Length - 1; if (i < (indexes.Count - 1)) { endIdx = indexes[i + 1]; } string nextWorldStr = strI.Substring(startIdx, endIdx - startIdx); CTworldJson dataFromJson = null; try { dataFromJson = JsonUtility.FromJson <CTworldJson>(nextWorldStr); } catch (Exception e) { UnityEngine.Debug.Log("Exception deserializing JSON: " + e.Message); continue; } if (dataFromJson == null || dataFromJson.objects == null) { continue; } // skip active local player ( save the effort here and in MergeWorlds() ) // ?? seems to have issues PB <--> RT ?? // if(ctunityI.activePlayer(dataFromJson.player)) continue; // Create CTworld object from CTworldJson (these classes are very similar but there are differences, see definitions above) CTworld jCTW = new CTworld(); jCTW.player = dataFromJson.player; jCTW.time = dataFromJson.time; // jCTW.mode = dataFromJson.mode; jCTW.objects = new Dictionary <String, CTobject>(); foreach (CTobjectJson ctobject in dataFromJson.objects) { CTobject cto = new CTobject(); cto.id = ctobject.id; // legacy format conversion: // if (cto.id.Equals(jCTW.player)) cto.id = cto.id + "/" + cto.id; // convert old top-level player // else if (cto.id.StartsWith(jCTW.player + ".")) cto.id = cto.id.Remove(0, jCTW.player.Length + 1); // Debug.Log("deserialize id: " + cto.id); cto.model = ctobject.model; // cto.state = ctobject.state; // cto.link = ctobject.link; cto.pos = new Vector3((float)ctobject.pos[0], (float)ctobject.pos[1], (float)ctobject.pos[2]); cto.rot = Quaternion.Euler((float)ctobject.rot[0], (float)ctobject.rot[1], (float)ctobject.rot[2]); cto.scale = new Vector3((float)ctobject.scale[0], (float)ctobject.scale[1], (float)ctobject.scale[2]); cto.color = new Color((float)ctobject.color[0], (float)ctobject.color[1], (float)ctobject.color[2], (float)ctobject.color[3]); cto.custom = ctobject.custom; jCTW.objects.Add(cto.id, cto); } worlds.Add(jCTW); } if (worlds.Count == 0) { return(null); } return(worlds); }
/// <summary> /// Deserialize the given txt/csv string into a List of CTworld objects. /// /// CSV example: /// #Live:1536843969.4578:Red /// Red;Ball;1;(2.5764, 0.2600, 8.5905);(0.0277, 60.7938, -0.0043) /// Red.Ground;Ground;1;(0.0000, 0.0000, 20.0000);(0.0000, 0.0000, 0.0000) /// Red.Pickup0;Pickup;1;(0.2000, 0.4000, 23.0000);(45.0800, 20.8186, 21.9459) /// Red.Pickup1;Pickup;1;(8.1000, 0.4000, 27.4000);(45.0800, 20.8186, 21.9459) /// Red.Pickup2;Pickup;1;(-1.3000, 0.4000, 13.3000);(45.0800, 20.8186, 21.9459) /// Red.Pickup3;Pickup;1;(2.0000, 0.4000, 23.6000);(45.0800, 20.8186, 21.9459) /// Red.Pickup4;Pickup;1;(0.9000, 0.4000, 22.7000);(45.0800, 20.8186, 21.9459) /// /// </summary> /// <param name="strI">The csv serialized world objects.</param> /// <returns>A List of CTworlds, parsed from the given string.</returns> private static List <CTworld> deserialize_csv(string strI) { List <CTworld> worlds = new List <CTworld>(); String[] sworlds = strI.Split('#'); // parse world-by-world // consolidate objects for each world foreach (String world in sworlds) { // Debug.Log("world: " + world); if (world.Length < 2) { continue; } String[] lines = world.Split('\n'); String[] header = lines[0].Split(':'); if (header.Length != 3) { continue; } CTworld CTW = new CTworld(); CTW.objects = new Dictionary <String, CTobject>(); // CTW.mode = header[0]; CTW.time = Double.Parse(header[1]); CTW.player = header[2]; foreach (String line in lines) { // sanity checks: if (line.Length < 2 || line.StartsWith("<")) { continue; // skip empty & html lines } String[] parts = line.Split(';'); if (parts.Length < 3) { continue; } CTobject ctobject = new CTobject(); ctobject.id = parts[0]; ctobject.model = parts[1]; // ctobject.state = !parts[2].Equals("0"); // parse ctobject.pos string pstate = parts[3].Substring(1, parts[3].Length - 2); // drop parens string[] pvec = pstate.Split(','); ctobject.pos = new Vector3(float.Parse(pvec[0]), float.Parse(pvec[1]), float.Parse(pvec[2])); // parse ctobject.rot pstate = parts[4].Substring(1, parts[4].Length - 2); // drop parens pvec = pstate.Split(','); ctobject.rot = Quaternion.Euler(float.Parse(pvec[0]), float.Parse(pvec[1]), float.Parse(pvec[2])); // custom field (used for indirect URL): // if (parts.Length > 5) ctobject.link = parts[5]; CTW.objects.Add(ctobject.id, ctobject); } worlds.Add(CTW); } return(worlds); }