using System.Collections.Generic; using UnityEngine; using asap.core; using UnityEngine.AddressableAssets; using GameCore; using cfg; using UniRx; using UnityEngine.ResourceManagement.AsyncOperations; using Game; public class InfiniteBuildingAct : AGameAct { private PlayerData _playerData; private InfiniteBuildingData _data; private InfiniteBuildingConf _cfg; private class FSM : AFSM { } private IFSM _sm; private FSMHandler, EInfiniteBuildingState> _smHandler; [SerializeField] private CraneCtrl _crane; [SerializeField] private BlockManager _blockManager; private Tables _tables; [SerializeField] private CameraCtrl _cam; private const int blockDepth = 20; private CampSkyscraperPanel _mainPanel; [SerializeField] private Transform _focus; [SerializeField] private BlockDeathNotifier _deathWall; private PlayerItemData _playerItemData; private readonly EventAggregator _eventAggregator = new EventAggregator(); private float BlockRestingHeight => _cfg.blockRestingOffset + _focus.position.y; [SerializeField] private MeshRenderer _skyLoop; private HashSet _collisionSounds; private int _EVConstructionState, _EVRetryType, _EVHookRewardGot, _EVCashRewardGot, _EVRating, _EVCombo; private ulong _EVCost, _EVCashBack; [SerializeField] GameObject _light; GameObject Building999Main; private CompositeDisposable _disposables = new CompositeDisposable(); //IDisposable disposable; private const string ComboKey = "InfiniteBuildingCombo"; public override async System.Threading.Tasks.Task StartAsync() { Building999Main = transform.GetChild(0).gameObject; //Debug.Log($"Main thread id: {Thread.CurrentThread.ManagedThreadId}"); _collisionSounds = new HashSet(); _data = new InfiniteBuildingData(); _sm = new FSM(); _smHandler = new FSMHandler, EInfiniteBuildingState>(this, _sm); _cfg = (InfiniteBuildingConf)await Addressables.LoadAssetAsync("InfiniteBuildingConf").Task; _playerData = GContext.container.Resolve(); _tables = GContext.container.Resolve(); _playerItemData = GContext.container.Resolve(); _disposables.Add(GContext.OnEvent().Subscribe(OnInAquariumEnevt)); _disposables.Add(GContext.OnEvent().Subscribe(OnEventMainBGM)); GContext.OnEvent().Subscribe(e => { SetMapShow(e.isShow); }).AddTo(_disposables); _data.DownloadData(_playerData.InfiniteBuildingData); // Debug.Log($"Debug in progress!!!!!!!!!!!!!!!!"); // _data.DownloadData("0,0,1,-1"); //_data.DownloadData("1199,0,1,-1," + // "209,-0.9679986,1191.69006," + // "212,-0.9312674,1192.70402," + // "1201,-0.1499511,1193.71932," + // "103,-0.288533,1195.73021," + // "211,-0.03252038,1196.74632," + // "203,-0.06627272,1197.76019," + // "201,0.08355299,1198.77346," + // "101,-0.3572269,1199.79065"); _crane.Init(_cfg, _focus, _data, _eventAggregator); _eventAggregator.GetEvent() .Subscribe(async _ => await TryAlignBlocks()).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(_ => { _EVRetryType = 1; _sm.SetState(EInfiniteBuildingState.InitLoop); UIManager.Instance.DestroyUI(UITypes.CampSettlementPanel); }).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(e => { _sm.SetState(EInfiniteBuildingState.BlockFall); _EVCost = e.ConstructionCost; }).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(e => HandleBlockDeath(e)).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(_ => _sm.SetState(EInfiniteBuildingState.Failure)).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(_ => _sm.SetState(EInfiniteBuildingState.Success)).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(async e => await PlayAudio(e.AudioName)).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(e => OnCollisionSound(e.Hash)).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(e => HandleFirstCollision(e.block)).AddTo(_disposables); _eventAggregator.GetEvent() .Subscribe(e => HandleReceiveReward(e)).AddTo(_disposables); await LoadAudioClips(); PlayBGM(); //Debug.Log($"{_pd.InfiniteBuildingData}"); _mainPanel = (await UIManager.Instance.ShowUI(UITypes.CampSkyscraperPanel)) .GetComponent(); _mainPanel.Init(_data, _eventAggregator); BoxCollider2D initBC = transform.Find("Building999Main/static/B999_initial/Collision2D").GetComponent(); _focus.position = initBC.transform.position + new Vector3(initBC.offset.x, initBC.offset.y) + initBC.size.y / 2f * Vector3.up; _blockManager.Init(_cfg, _data, _eventAggregator); await _blockManager.RebuildAllBlocks(); _deathWall.Init(_eventAggregator); GetFX(); _sm.SetState(EInfiniteBuildingState.Init); return await base.StartAsync(); } void SetMapShow(bool isShow) { if (Building999Main != null) { Building999Main.SetActive(isShow); } } void OnInAquariumEnevt(InGradeAquariumEvent e) { Debug.Log("OnInAquariumEnevt"); if (_light != null) { _light.SetActive(e.type == 1); } } void OnEventMainBGM(EventMainBGM e) { PlayBGM(); } public override async System.Threading.Tasks.Task StopAsync() { UIManager.Instance.DestroyUI(UITypes.CampSkyscraperPanel); await base.StopAsync(); } private void FixedUpdate() { if (_sm == null) return; if (_sm.State == EInfiniteBuildingState.PlayerCtrl #if UNITY_EDITOR && !_cfg.FreezeCrane #endif ) { _crane.PlayerCtrlFixedUpdate(); } #if UNITY_EDITOR float v = 0.2f; if (Input.GetKeyDown(KeyCode.A) && _sm.State == EInfiniteBuildingState.PlayerCtrl) { _crane.Rb.position += v * Vector2.left; } if (Input.GetKeyUp(KeyCode.D) && _sm.State == EInfiniteBuildingState.PlayerCtrl) { _crane.Rb.position += v * Vector2.right; } #endif else if (_sm.State == EInfiniteBuildingState.BlockFall) { _blockManager.BlockFallFixedUpdate(); } } private void LateUpdate() { _collisionSounds?.Clear(); } protected override void OnDestroy() { Debug.Log("Act Destroyed!"); } #if UNITY_EDITOR private void OnGUI() { if (_sm != null) GUI.Box(new Rect(20, 40, 200, 20), _sm.State.ToString()); //Debug.DrawLine(new Vector3(-10, _cam.LowerLimit.y, blockDepth), //new Vector3(10, _cam.LowerLimit.y, blockDepth), Color.red); if (_blockManager.CurrentBlock != null) { GUI.Box(new Rect(20, 60, 200, 20), $"Acc: {_blockManager.CurrentBlock.Acc.magnitude}"); GUI.Box(new Rect(20, 90, 200, 20), $"Angular Acc: {_blockManager.CurrentBlock.AngularAcc}"); } else { GUI.Box(new Rect(20, 60, 200, 20), $"Empty"); GUI.Box(new Rect(20, 90, 200, 20), $"Empty"); } } private void OnDrawGizmos() { if (_focus != null && _cfg != null) Debug.DrawLine(new Vector3(-10, BlockRestingHeight, blockDepth), new Vector3(10, BlockRestingHeight, blockDepth), Color.red); //Debug.DrawLine(new Vector3(-20, _cam.LowerLimit.y, blockDepth), // new Vector3(-20, _cam.LowerLimit.y, blockDepth), Color.red); } #endif private void UpdateLastHangingBlock() { _data.LastHangingBlock = _blockManager.CurrentBlock.Idx; _data.UploadData(); } private void UpdateData(int lvlDelta = 0) { _data.Lvl += lvlDelta; _data.Record = new List(); foreach (var block in _blockManager.Blocks) { //Debug.Log($"{block}"); //Debug.Log($"{block.Rb.position}"); //Debug.Log($"{block.transform.position}"); _data.Record.Add(new InfiniteBuildingData.BlockRecord(block.Idx, block.Rb.position)); } _data.UploadData(); } protected override void OnStopped() { //_data.LastHangingBlock = _blockManager.CurrentBlock.Idx; //UpdateData(); foreach (var e in _audioClips) { Addressables.Release(e.Value.Op); } _disposables.Dispose(); base.OnStopped(); } public void HandleBlockDeath(EventInfiniteBuildingBlockDeath e) { PlayFX("collapse", _focus.position + _cfg.fxCollapse); if (_sm.State != EInfiniteBuildingState.BlockFall) { PlayFX("replay", Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 1f, _cfg.blockDepth))); _blockManager.RebuildBlocks(true); return; } if (e.stateBeforeDeath == BlockState.Free) _eventAggregator.Publish(new EventInfiniteBuildingFail()); if (e.stateBeforeDeath == BlockState.Resting) { _eventAggregator.Publish(new EventInfiniteBuildingFail()); PlayFX("replay", Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 1f, Camera.main.nearClipPlane))); _blockManager.RebuildAllBlocks(); } } private async System.Threading.Tasks.Task TryAlignBlocks() { int alignCost = _tables.TbConstructionInfiniteConfig.ResetCost; if (_playerData.diamond < alignCost) { //Debug.LogWarning("inssuficient diamond"); GameObject ui = await UIManager.Instance.ShowUI(UITypes.LackOfResourceConfirmPopupPanel); //ui.GetComponent().Init(_eventAggregator); return; } _EVRetryType = 2; PlayAudio("Reset"); PlayFX("reset", Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0f, _cfg.blockDepth))); _playerData.AddDiamond(-alignCost); _blockManager.AlignBlocks(_eventAggregator); UpdateData(); _sm.SetState(EInfiniteBuildingState.InitLoop); UIManager.Instance.DestroyUI(UITypes.CampSettlementPanel); } private void HandleFirstCollision(Block b) { //Debug.Log($"name: {b.name}"); PlayFX("fall", b.transform.position + _cfg.fxFall); _blockManager.StartDestroyCountDown(); //PlayAudio("BlockFall"); } private void HandleReceiveReward(EventInfiniteBuildingGetReward e) { var drop = _tables.TbDrop[e.DropID]; if (drop.DropList.DropIDList[0] == 1001) _EVHookRewardGot = drop.DropList.DropCountList[0]; else if (drop.DropList.DropIDList[0] == 1002) _EVCashRewardGot = _playerItemData.GetExtraCoinMag(drop.DropList.DropCountList[0]); } private int GetConstructionPrice() { int price = _data.ConstructPrice; var buffTimeData = GContext.container.Resolve().GetWeelyBuffTimeData(); bool isConstructionCost = buffTimeData != null; if (isConstructionCost) { price = (int)(price * (1 - buffTimeData.Param)); } return price; } private int GetRating() { int excellentScore = _blockManager.GetExcellentScore(); if (excellentScore >= _tables.TbConstructionInfiniteConfig.GoodRankClamp[0] && excellentScore <= _tables.TbConstructionInfiniteConfig.GoodRankClamp[1]) { Debug.Log($"Good."); return 1; } // else if (excellentScore >= _tables.TbConstructionInfiniteConfig.GreatRankClamp[0] && // excellentScore <= _tables.TbConstructionInfiniteConfig.GreatRankClamp[1]) // Debug.Log($"Great."); if (excellentScore >= _tables.TbConstructionInfiniteConfig.FantasticRankClamp[0] && excellentScore <= _tables.TbConstructionInfiniteConfig.FantasticRankClamp[1]) { Debug.Log($"Fantastic."); return 2; } Debug.Log($"Undefined."); return 0; } private int CountCombo(bool doesComboContinue = true) { if (!doesComboContinue) { PlayerPrefs.SetInt(ComboKey, 0); Debug.Log($"Combo reset."); return 0; } int comboCount = PlayerPrefs.GetInt(ComboKey) + 1; PlayerPrefs.SetInt(ComboKey, comboCount); // if (comboCount >= 2) // Debug.Log($"Combo: {comboCount}"); return comboCount; } #region Audio private readonly Dictionary _audioAliases = new Dictionary() { ["CraneRise"] = "audio_ui_skyscraper_1", ["CraneDescend"] = "audio_ui_skyscraper_2", ["BlockFall"] = "audio_ui_skyscraper_3", ["BlockHit"] = "audio_ui_skyscraper_4", ["BlockPerfect"] = "audio_ui_skyscraper_5", ["Collapse"] = "audio_ui_skyscraper_6", ["Reset"] = "audio_ui_skyscraper_7", ["BGM"] = "audio_ui_skyscraper_bgm" }; private readonly Dictionary _audioClips = new Dictionary(); private struct InfiniteBuildingAudioClip { public string Name; public string Address; public AudioClip Clip; public AsyncOperationHandle Op; public InfiniteBuildingAudioClip(string name, string addr, AudioClip clip, AsyncOperationHandle op) { Name = name; Clip = clip; Address = addr; Op = op; } } private async System.Threading.Tasks.Task LoadAudioClips() { foreach (var e in _audioAliases) { AsyncOperationHandle op = Addressables.LoadAssetAsync(e.Value); _audioClips.Add(e.Key, new InfiniteBuildingAudioClip(e.Key, e.Value, await op.Task, op)); } } private async System.Threading.Tasks.Task PlayAudio(string audioName) { if (!_audioAliases.ContainsKey(audioName)) { Debug.LogError($"Audio name {audioName} not found in dictionary."); return; } Sound s = GContext.container.Resolve() .GetNewFishingSound(clip: _audioClips[audioName].Clip); s.soundMainType = SoundMainType.Fishing; s.audioSource.Play(); await System.Threading.Tasks.Task.Delay((int)(_audioClips[audioName].Clip.length * 1000)); s.ReturnPool(); } private void PlayBGM() { GContext.Publish(new EventBGMSound(_audioAliases["BGM"])); } private void OnCollisionSound(string hash) { if (_sm.State != EInfiniteBuildingState.BlockFall || _collisionSounds.Contains(hash)) return; _collisionSounds.Add(hash); PlayAudio("BlockHit"); } #endregion #region FX private readonly List _fxNames = new List() { "fx_infinitebuilding_cloud", "fx_infinitebuilding_collapse", "fx_infinitebuilding_reset", "fx_infinitebuilding_replay", "fx_infinitebuilding_fall", "fx_infinitebuilding_fallperfact" }; [SerializeField] private Dictionary _fxDic = new Dictionary(); private void GetFX() { foreach (string name in _fxNames) { _fxDic.Add(name.Split('_')[^1], transform.Find("Building999Main/fx/" + name).gameObject); } } private void PlayFX(string name, Vector3 pos) { _fxDic[name].transform.position = pos; _fxDic[name].SetActive(false); _fxDic[name].SetActive(true); } private void MoveFx(string name, Vector3 pos) { _fxDic[name].transform.position = pos; } #endregion #region State Machine Driver private void Init_Enter() { //Debug.Log("Init_Enter!"); //Debug.Log($"Init_Enter thread id: {Thread.CurrentThread.ManagedThreadId}"); InitEnterAsync(); //GenerateBlock().ContinueWith(_ => _sm.SetState(EInfiniteBuildingState.PlayerCtrl)); } private async System.Threading.Tasks.Task InitEnterAsync() { //Debug.Log($"InitEnterAsync thread id: {Thread.CurrentThread.ManagedThreadId}"); await _blockManager.RebuildBlocks(); _cam.Init(_focus, _cfg, _skyLoop, _data); _deathWall.transform.position = _focus.position + Vector3.up * _cfg.deathWallOffset; _crane.ResetPosition(); Block b = await _blockManager.GenerateBlock(_crane.transform.position + _cfg.blockSpawnOffset, true); _blockManager.CurrentBlock = b; _data.LastHangingBlock = b.Idx; UpdateData(); _crane.AttachJoints(b); if (_focus.position.y > _cfg.fxCloudAppearHeight) PlayFX("cloud", _focus.position + _cfg.fxCloud); _sm.SetState(EInfiniteBuildingState.PlayerCtrl); } private void Init_Exit() { //Debug.Log("Init_Exit!"); } private void InitLoop_Enter() { //Debug.Log("InitLoop_Enter!"); //GenerateBlock().ContinueWith(_ => _sm.SetState(EInfiniteBuildingState.PlayerCtrl)); InitLoopEnterAsync(); } private async System.Threading.Tasks.Task InitLoopEnterAsync() { // Debug.Log($"construction_state: {_EVConstructionState}"); // Debug.Log($"construction_lvl: {_data.Lvl}"); // Debug.Log($"retry_type: {_EVRetryType}"); // Debug.Log($"hook_count: {_playerData.GetEnergyRM()}"); // Debug.Log($"cost_cash: {_EVCost}"); // Debug.Log($"reward_hook: {_EVHookRewardGot}"); // Debug.Log($"reward_cash: {_EVCashRewardGot}"); // Debug.Log($"rating: {_EVRating}"); // Debug.Log($"combo_count: {_EVCombo}"); // Debug.Log($"cash_back: {_EVCashBack}"); #if AGG using (var e = GEvent.GameEvent("infinite_construction")) { e.AddContent("construction_state", _EVConstructionState) .AddContent("construction_lv", _data.Lvl) .AddContent("retry_type", _EVRetryType) .AddContent("hook_count", _playerData.GetEnergyRM()) .AddContent("cost_cash", _EVCost) .AddContent("reward_hook", _EVHookRewardGot) .AddContent("reward_cash", _EVCashRewardGot) .AddContent("rating", _EVRating) .AddContent("combo_count", _EVCombo) .AddContent("cash_back", _EVCashBack); } #endif var b = await _blockManager.GenerateBlock(_crane.transform.position + _cfg.blockSpawnOffset); _blockManager.CurrentBlock = b; _data.LastHangingBlock = b.Idx; UpdateData(); _crane.AttachJoints(b); _sm.SetState(EInfiniteBuildingState.PlayerCtrl); } private void InitLoop_Exit() { //Debug.Log("InitLoop_Exit!"); } private void PlayerCtrl_Enter() { //Debug.Log("PlayerCtrl_Enter!"); //Debug.Log($"Player Ctrl thread id: {Thread.CurrentThread.ManagedThreadId}"); PlayerCtrlEnterAsync(); //Debug.Log(_crane.gameObject); } private async System.Threading.Tasks.Task PlayerCtrlEnterAsync() { await _crane.Descend(); _crane.Kick(); _mainPanel.ActivateBtns(); } private void PlayerCtrl_Exit() { //Debug.Log("PlayerCtrl_Exit!"); _mainPanel.DeactivateBtns(); } private void BlockFall_Enter() { //Debug.Log("BlockFall_Enter!"); //Debug.Log($"Block Fall thread id: {Thread.CurrentThread.ManagedThreadId}"); StartCoroutine(_crane.DropBlockAndRise()); _blockManager.CurrentBlock.Drop(); //_currentBlock.Drop(); //DestroyCountDown(); } private void BlockFall_Exit() { //Debug.Log("BlockFall_Exit!"); _blockManager.ResetDestroyCountDown(); } private void Success_Enter() { //Debug.Log("Success_Enter!"); SuccessEnterAsync(); } private async System.Threading.Tasks.Task SuccessEnterAsync() { int newHeight = (int)_blockManager.CurrentBlock.WorldHeight; int oldHeight = (int)_focus.position.y; int deltaLvl = newHeight > oldHeight ? (int)_blockManager.CurrentBlock.Height : 0; //_blockManager.CurrentBlock.Rb.position = // new Vector2(_blockManager.CurrentBlock.Rb.position.x, _focus.position.y); _blockManager.CurrentBlock.PlayFurnitureAnimation(); int combo = CountCombo(); int rating = GetRating(); bool isPerfect = _blockManager.TrySnap(); _EVCombo = combo; _EVRating = rating; if (isPerfect) { _ = PlayAudio("BlockPerfect"); PlayFX("fallperfact", _blockManager.CurrentBlock.transform.position + _cfg.fxPerfect); _EVConstructionState = 2; } else { _EVConstructionState = 1; } float refundRatio = 0.0f; if (combo >= 2) refundRatio += Mathf.Min(combo - 1, _tables.TbConstructionInfiniteConfig.ComboReturnMaxCount) * _tables.TbConstructionInfiniteConfig.ComboOnceReturnPoint; if (rating != 0) refundRatio += rating == 1 ? _tables.TbConstructionInfiniteConfig.GoodReturnPoint : _tables.TbConstructionInfiniteConfig.FantasticReturnPoint; if (isPerfect) refundRatio += _tables.TbConstructionInfiniteConfig.PerfectReturnPoint; // Debug.Log($"RefundRatio: {refundRatio}"); _mainPanel.PlayRatingFx(rating); _mainPanel.PlayComboFx(combo); _EVCashBack = 0; if (refundRatio != 0.0f) { int price = GetConstructionPrice(); int amount = (int)(refundRatio * price); _EVCashBack = (ulong)amount; _playerData.SetGold(_playerData.gold + (ulong)amount); await _mainPanel.PlayCashFlyAnimation(amount); } _EVRetryType = 0; _EVHookRewardGot = 0; _EVCashRewardGot = 0; _data.Step++; if (await _mainPanel.UpdateUIWhenSuccess()) { await _mainPanel.ReceiveReward(); } if (newHeight > oldHeight) _focus.position = new Vector3(_focus.position.x, _blockManager.CurrentBlock.WorldHeight, _focus.position.z); _cam.FollowFocus(); _blockManager.AddCurrentBlock(); _blockManager.LockOrClearBlocks(true); UpdateData(deltaLvl); //leyuanniubi!!! if (deltaLvl > 0) { GContext.Publish(new ConditionTypeEvent(ConditionType.ConstructionCount, deltaLvl)); GContext.container.Resolve().AwaitSetLvData(); } _deathWall.transform.position += deltaLvl * Vector3.up; MoveFx(name: "cloud", pos: _focus.position + _cfg.fxCloud); _sm.SetState(EInfiniteBuildingState.InitLoop); } private void Success_Exit() { //Debug.Log("Success_Exit!"); } private void Failure_Enter() { //Debug.Log("Failure_Enter!"); FailureEnterAsync(); } private async System.Threading.Tasks.Task FailureEnterAsync() { _EVCombo = CountCombo(false); _EVConstructionState = 0; _EVRating = 0; _EVCashBack = 0; PlayAudio("Collapse"); CampSettlementPanel ui = (await UIManager.Instance.ShowUI(UITypes.CampSettlementPanel)) .GetComponent(); ui.Init(_eventAggregator); await _blockManager.RebuildAllBlocks(); _blockManager.LockOrClearBlocks(); } private void Failure_Exit() { //Debug.Log("Failure_Exit!"); } //private void BlockDestroy_Enter() //{ // Debug.Log("BlockDestroy_Enter!"); // //UIManager.Instance.ShowUI(UITypes.CampSettlementPanel); // EnterBlockDestroyAsync(); // //_sm.SetState(EInfiniteBuildingState.InitLoop); //} //private void BlockDestroy_Exit() //{ // Debug.Log("BlockDestroy_Exit!"); //} #endregion } public enum EInfiniteBuildingState { Default, Init, InitLoop, PlayerCtrl, BlockFall, Success, Failure } public class EventInfiniteBuildingRetry { } public class EventInfiniteBuildingAlign { } public class EventInfiniteBuildingBlockDeath { public BlockState stateBeforeDeath { get; set; } public EventInfiniteBuildingBlockDeath(BlockState stateBeforeDeath) { this.stateBeforeDeath = stateBeforeDeath; } } public class EventInfiniteBuildingConstruct { public ulong ConstructionCost; public EventInfiniteBuildingConstruct(ulong cost) { ConstructionCost = cost; } } public class EventInfiniteBuildingFail { } public class EventInfiniteBuildingSucceed { } public class EventInfiniteBuildingPlayAudio { public string AudioName { get; set; } public EventInfiniteBuildingPlayAudio(string audioName) { AudioName = audioName; } } public class EventInfiniteBuildingCollisionSound { public string Hash { get; set; } public EventInfiniteBuildingCollisionSound(string hash) { Hash = hash; } } public class EventInfiniteBuildingFirstCollision { public readonly Block block; public EventInfiniteBuildingFirstCollision(Block b) { block = b; } } public class EventInfiniteBuildingGetReward { public readonly int DropID; public EventInfiniteBuildingGetReward(int dropID) { DropID = dropID; } }