/// <summary> /// Update entire view and alert session of changes. /// </summary> public bool Update(Vector2 MouseFloorPos) { // This is the only time that we lock the world on the primary thread (Except for rare debugging cases in CGameSession). // All state information is copied from the world from the perspective of a player (_viewPlayerIndex). // As little work as possible should be done during the state copy. We want to unlock the world ASAP. CGame.PrimaryThreadProfiler.Push(CProfiler.EID.I_RENDER_TICK); lock (_world) { // TODO: Check for a console var to remain on crash. if (_world.mCrashed) { CGame.PrimaryThreadProfiler.Pop(); return(false); } System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); CGame.PrimaryThreadProfiler.Push(CProfiler.EID.I_RENDER_TICK); // Copy door segments for visibility _world.mMap.TrimStaticVisSegments(); for (int i = 0; i < _world.mItems.Count; ++i) { CItem item = _world.mItems[i]; if (item.mType == CEntity.EType.ITEM_DOOR && !item.mBluerprint && !(item as CItemDoor).mOpen) { _world.mMap.mStaticVisSegments.Add(new CVisibilityBlockingSegment(item.ItemToWorldSpacePosition(new Vector2(0, 1)), item.ItemToWorldSpacePosition(new Vector2(1, 1)))); } } // Copy all the state!!!!! mTransientEvents = _world.SwapAndClearTransientEvents(); mPaydayTimerNormalised = 1.0f - _world.GetPaydayTimeNormalize(); mGameTick = _world.mGameTick; // Company Data // ... // Player Data for (int i = 0; i < mPlayerViews.Length; ++i) { CPlayer player = _world.mPlayers[i]; CPlayerView view = mPlayerViews[i]; view.mIndex = player.mID; view.mColor = player.mColor; view.mMoney = player.mMoney; view.mDebt = player.mDebt; view.mSpawnPos = player.GetSpawnPos(); view.mCanInteractWithWorld = player.mCanInteractWithWorld; view.mCanInteractWithUI = player.mCanInteractWithUI; view.mCanControlCamera = player.mCanControlCamera; view.mMusicTrack = player.mMusicTrack; view.mShowUI = player.mShowUI; view.mCamPosition = player.mCamPosition; view.mCamSpeed = player.mCamSpeed; view.mAvailableItems = player.mAvailableItems.ToArray(); } // TODO: Should just iterate all entities and ask them if they are valid for state copy. // Unit Data // TODO: Surely the sim knows what units are visible to the player already? for (int i = 0; i < _world.mUnits.Count; ++i) { if (_world.mUnits[i].IsVisibleToPlayer(_viewPlayerIndex)) { if (_world.mUnits[i].mStateView == null) { CUnitView view = new CUnitView(); view.mState = CStateView.EState.NEW; mStateViews.Add(view); view.CopyInitialState(_world.mUnits[i]); view.Init(_viewPlayerIndex, this); _world.mUnits[i].mStateView = view; } else { _world.mUnits[i].mStateView.mState = CStateView.EState.UPDATING; } _world.mUnits[i].mStateView.CopyState(_world.mUnits[i]); } else { _world.mUnits[i].mStateView = null; } } // Pickups for (int i = 0; i < _world.mPickups.Count; ++i) { if (_world.mPickups[i].IsVisibleToPlayer(_viewPlayerIndex)) { if (_world.mPickups[i].mStateView == null) { CPickupView view = new CPickupView(); view.mState = CStateView.EState.NEW; mStateViews.Add(view); view.CopyInitialState(_world.mPickups[i]); view.Init(_viewPlayerIndex, this); _world.mPickups[i].mStateView = view; } else { _world.mPickups[i].mStateView.mState = CStateView.EState.UPDATING; } _world.mPickups[i].mStateView.CopyState(_world.mPickups[i]); } else { _world.mPickups[i].mStateView = null; } } // Missiles for (int i = 0; i < _world.mMissiles.Count; ++i) { if (_world.mMissiles[i].IsVisibleToPlayer(_viewPlayerIndex)) { if (_world.mMissiles[i].mStateView == null) { CMissileView view = new CMissileView(); view.mState = CStateView.EState.NEW; mStateViews.Add(view); view.CopyInitialState(_world.mMissiles[i]); view.Init(_viewPlayerIndex, this); _world.mMissiles[i].mStateView = view; } else { _world.mMissiles[i].mStateView.mState = CStateView.EState.UPDATING; } _world.mMissiles[i].mStateView.CopyState(_world.mMissiles[i]); } else { _world.mMissiles[i].mStateView = null; } } // Item View Data for (int i = 0; i < _world.mItemProxies[_viewPlayerIndex].Count; ++i) { CItemProxy proxy = _world.mItemProxies[_viewPlayerIndex][i]; if (proxy.mVisuals == null) { // new CItemView view = new CItemView(); view.mState = CStateView.EState.NEW; mStateViews.Add(view); view.CopyInitialState(proxy); view.Init(_viewPlayerIndex, this); proxy.mVisuals = view; } else { proxy.mVisuals.mState = CStateView.EState.UPDATING; } proxy.mVisuals.CopyState(proxy); } // Contract Data for (int i = 0; i < _world.mContracts.Count; ++i) { CContract contract = _world.mContracts[i]; // TODO: All contracts always part of user view state? //if (contract.mOwner == -1 || contract.mOwner == _viewPlayerIndex) if (true) { if (contract.mStateView == null) { // new CContractView view = new CContractView(); view.mState = CStateView.EState.NEW; mStateViews.Add(view); view.CopyInitialState(contract); view.Init(_viewPlayerIndex, this); contract.mStateView = view; } else { contract.mStateView.mState = CStateView.EState.UPDATING; } contract.mStateView.CopyState(contract); } else { contract.mStateView = null; } } // Resume View Data for (int i = 0; i < _world.mResumes.Count; ++i) { CResume resume = _world.mResumes[i]; if (resume.mOwner != -1) { if (resume.mStateView == null) { // new CResumeView view = new CResumeView(); view.mState = CStateView.EState.NEW; mStateViews.Add(view); view.CopyInitialState(resume); view.Init(_viewPlayerIndex, this); resume.mStateView = view; } else { resume.mStateView.mState = CStateView.EState.UPDATING; } resume.mStateView.CopyState(resume); } else { resume.mStateView = null; } } // Decal View Data for (int i = 0; i < _world.mDecals.Count; ++i) { CDecal decal = _world.mDecals[i]; // TODO: Allow decals to be visible to certain players only. if (decal.mStateView == null) { // new CDecalView view = new CDecalView(); view.mState = CStateView.EState.NEW; mStateViews.Add(view); view.CopyInitialState(decal); view.Init(_viewPlayerIndex, this); decal.mStateView = view; } else { decal.mStateView.mState = CStateView.EState.UPDATING; } decal.mStateView.CopyState(decal); } sw.Stop(); //Debug.Log("Copy " + sw.Elapsed.TotalMilliseconds + "ms"); CGame.PrimaryThreadProfiler.Pop(); } CGame.PrimaryThreadProfiler.Pop(); // NOTE: Now that the state has been copied from the simulation and we have released the thread lock, we can // process the new state and make appropriate changes to the Unity scene. CGame.UIManager.PlayMusic(mPlayerViews[_viewPlayerIndex].mMusicTrack); CGame.UIManager.mShowUI = mPlayerViews[_viewPlayerIndex].mShowUI; _userSession.mUserInteraction = mPlayerViews[_viewPlayerIndex].mCanInteractWithWorld; CGame.CameraManager.SetInteractable(mPlayerViews[_viewPlayerIndex].mCanControlCamera); CGame.CameraManager.SetLerpSpeed(mPlayerViews[_viewPlayerIndex].mCamSpeed); // TODO: Different bool to control tracked camera? if (!mPlayerViews[_viewPlayerIndex].mCanControlCamera) { // TODO: Immediate of on different cam step. CGame.CameraManager.SetTargetPosition(mPlayerViews[_viewPlayerIndex].mCamPosition, false, mPlayerViews[_viewPlayerIndex].mCamPosition.w); } for (int i = 0; i < mTransientEvents.Count; ++i) { CTransientEvent tev = mTransientEvents[i]; // Ignore events that occured over 1 second ago. if (mGameTick > tev.mGameTick + 20) { continue; } if ((tev.mViewerFlags & (1 << _viewPlayerIndex)) == 0) { continue; } if (tev.mType == CTransientEvent.EType.SOUND) { //AudioSource.PlayClipAtPoint(CGame.PrimaryResources.AudioClips[tev.mData], tev.mPosition, 1.0f); AudioClip clip = CGame.PrimaryResources.AudioClips[tev.mData]; GameObject source = new GameObject("AudioPosOneShot"); source.transform.SetParent(_userSession.mPrimaryScene.transform); source.transform.position = tev.mPosition; AudioSource audio = source.AddComponent <AudioSource>(); audio.clip = clip; audio.outputAudioMixerGroup = CGame.UIResources.SoundsMixer; audio.spatialBlend = 1.0f; audio.Play(); GameObject.Destroy(source, clip.length); } else if (tev.mType == CTransientEvent.EType.UI_SOUND) { CGame.UIManager.PlaySound(CGame.PrimaryResources.AudioClips[tev.mData]); } else if (tev.mType == CTransientEvent.EType.EFFECT) { GameObject.Instantiate(CGame.PrimaryResources.Particles, tev.mPosition, Quaternion.identity); //CGame.CameraManager.Shake(); } else if (tev.mType == CTransientEvent.EType.PAYDAY) { CGame.UIManager.PlaySound(CGame.PrimaryResources.AudioClips[17]); } else if (tev.mType == CTransientEvent.EType.NOTIFY) { Debug.Log("Got notify: " + tev.mMessage); _userSession.CreateNotifyStackIcon(2, null, CGame.AssetManager.GetAsset <CItemAsset>(tev.mMessage)); } } for (int i = 0; i < mStateViews.Count; ++i) { if (!mStateViews[i].Update(_userSession)) { mStateViews.RemoveAt(i); --i; } else { mStateViews[i].mState = CStateView.EState.WAITING; mStateViews[i].DrawDebugPrims(); } } /* * for (int i = 0; i < _stateViews.Count; ++i) * { * _stateViews[i].DrawDebugPrims(); * } */ if (CGame.VarShowFlowField.mValue) { DrawFlowField(); } if (CGame.VarShowNavMesh.mValue) { mNavMesh.DebugDraw(); } if (CGame.VarShowNavRect.mValue > 0) { _world.mMap.mNavMeshes[CGame.VarShowNavRect.mValue - 1].DebugDraw(); } return(true); }