using PuzzleDefine; using System.Collections; using System.Collections.Generic; using UnityEngine; using Sirenix.OdinInspector; using SonicBloom.Koreo; using SonicBloom.Koreo.Players; using UnityEngine.Video; public class PuzzleGameMode : MonoBehaviour { public static PuzzleGameMode main; #region Define [System.Serializable] public struct HeroConfig { public HeroID ID; public GameObject Prefab; } [System.Serializable] public struct GemDamageSheet { public PuzzleGemMatch GemMatch; public int ComboNum; } [System.Serializable] public struct RandomGemShape { public List Positions; } public delegate void VoidDelegate(); [System.Serializable] public struct MatchTiming { public float Timing; public float FirstRadius; public int MatchType; public PuzzleDefine.AttributeFix FirstBonusFix; public float SecondRadius; public PuzzleDefine.AttributeFix SecondBonusFix; public TimingNames TimingName; public bool InFirstRange(float timing) { return timing >= Timing - FirstRadius && timing <= Timing + FirstRadius; } public bool InSecondRange(float timing) { return timing >= Timing - SecondRadius && timing <= Timing + SecondRadius; } } [System.Serializable] public struct DamageComposition { public PuzzleDefine.Attribute Damage; public PuzzleDefine.AttributeFix GemBonus; } public enum TimingNames { Perfect, Good, Miss, } public enum FillStrategies { Fall, Generate, } #endregion private void Awake() { main = this; } #region Events public VoidDelegate OnAfterGameStarted; public VoidDelegate OnAfterRoundFinished; #endregion #region Game public bool GameStarted = false; public bool GameFinished = false; public List Boards; // public Board Board; public int Round; public void SetRound(int round) { Round = round; } public PuzzleGamePlayer GamePlayer; public PuzzleMonster Monster; public BattleConfig BattleConfig; public List HeroConfigs; public int PredictBlocks; public int MaxReplaceBlocks; public List RandomShapes; public PuzzleUXConfig PuzzleUXConfig; public PuzzleGameConfig PuzzleGameConfig; private List SkillConfigs; public ReplaceBlock GenRandomReplaceBlock() { ReplaceBlock block = new ReplaceBlock(); int rand = Random.Range(0, RandomShapes.Count); RandomGemShape shape = RandomShapes[rand]; block.GemsToReplace = new List(shape.Positions.Count); for (int j = 0; j < shape.Positions.Count; j++) { PositionedGem positionedGem = new PositionedGem() { Position = shape.Positions[j], Gem = GetRandomGem(), }; block.GemsToReplace.Add(positionedGem); } return block; } public void FillReplaceBlocks() { var blocks = GamePlayer.ReplaceBlocks; blocks.Clear(); for (int i = 0; i < MaxReplaceBlocks; i++) { blocks[i] = GenRandomReplaceBlock(); } for (int i = 0; i < PredictBlocks; i++) { blocks[-1 - i] = GenRandomReplaceBlock(); } GamePlayer.OnAfterReplaceBlockChagned?.Invoke(); } public void SlideReplaceBlocks() { var blocks = GamePlayer.ReplaceBlocks; int lastEmptySlot = MaxReplaceBlocks - 1; for (int i = MaxReplaceBlocks - 1; i >= -PredictBlocks; i--) { if (blocks.ContainsKey(i)) { if (lastEmptySlot == i) { lastEmptySlot--; continue; } else { blocks[lastEmptySlot] = blocks[i]; blocks.Remove(i); lastEmptySlot--; } } else { //lastEmptySlot = i; } } for (int i = -PredictBlocks; i < MaxReplaceBlocks; i++) { if (blocks.ContainsKey(i)) { break; } else { blocks.Add(i, GenRandomReplaceBlock()); } } GamePlayer.OnAfterReplaceBlockChagned?.Invoke(); } private void Start_Game() { PuzzlePlayer player = PuzzlePlayer.main; GamePlayer.Heroes = new List(); PuzzleLeveData currLevelData = LevelLoader.instance.currLevelData; SkillConfigs = PuzzleGameConfig.SkillConfigs; if (currLevelData != null) { m_currKoreography = LevelLoader.instance.currLevelData.Koreography; m_beatKoreEventID = m_currKoreography.Tracks[0].EventID; m_koreAudioClip = m_currKoreography.SourceClip; GameObject.Find("KoreMusicPlayer").GetComponent().LoadSong(m_currKoreography); } foreach (var item in player.HeroIDs) { GamePlayer.Heroes.Add(CreateHero(item)); } Monster.SetHealth(Monster.MaxHealth); Round = 0; StartRound(); FillReplaceBlocks(); GameStarted = true; OnAfterGameStarted?.Invoke(); for (int i = 0; i < Boards.Count; i++) { Boards[i].Init(); } } #endregion #region Combo public int ComboNum = 0; #endregion #region Animation private bool m_duringFallilngAnimation; #endregion #region Connection private static readonly Position[] s_iterateDirections = new Position[] { new Position() { X = 0, Y = 1, }, new Position() { X = 0, Y = -1, }, new Position() { X = 1, Y = 0, }, new Position() { X = -1, Y = 0, }, }; public Vector2 GetGemSize() { return Vector2.one * PuzzleUXConfig.GemSize; } public void CalcConnectionMatch(Position at, HashSet connected, out Gem outGem) { PositionedGem? _positionedGem = GetPositionedGem(at); if (_positionedGem == null) { outGem = default; return; } outGem = _positionedGem.Value.Gem; CalcConnectionMatchRec(at, connected, outGem); } private void CalcConnectionMatchRec(Position at, HashSet connected, Gem filterGem) { PositionedGem? _positionedGem = GetPositionedGem(at); if (_positionedGem == null || _positionedGem.Value.Gem != filterGem) { return; } connected.Add(_positionedGem.Value.Position); foreach (var direction in s_iterateDirections) { Position nextPosition = _positionedGem.Value.Position + direction; if (connected.Contains(nextPosition)) { continue; } CalcConnectionMatchRec(nextPosition, connected, filterGem); } } #endregion #region Music [SerializeField] private AudioClip m_koreAudioClip; [EventID] [SerializeField] private string m_beatKoreEventID; [SerializeField] private Koreography m_currKoreography; private static float PeekKoreEvents(string clipName, string eventID, List outEvents, float seconds, float? startFrom = null) { int currentTimeSample = Koreographer.Instance.GetMusicSampleTime(clipName); int samplingRate = Koreographer.Instance.GetMusicSampleRate(clipName); int peekLength = (int)(samplingRate * seconds); int peekOffset = currentTimeSample; if (startFrom != null) { peekOffset = (int)(samplingRate * startFrom); } List events = new List(); Koreographer.Instance.GetAllEventsInRange(clipName, eventID, peekOffset, peekOffset + peekLength, events); foreach (var item in events) { BeatEvent beatEvent = new BeatEvent() { StartTime = (float)item.StartSample / (float)samplingRate, EndTime = (float)item.EndSample / (float)samplingRate, EventID = eventID, EventType = item.GetIntValue(), }; outEvents.Add(beatEvent); } return (float)peekOffset / (float)samplingRate; } private void UpdateBeatTiming() { List koreographyEvents = new List(); float currentTime = PeekKoreEvents(m_koreAudioClip.name, m_beatKoreEventID, koreographyEvents, m_koreAudioClip.length); MatchTimings.Clear(); MonsterSkillTimings.Clear(); foreach (var item in koreographyEvents) { MatchTiming newTiming = TemplateTimng; newTiming.Timing = (item.StartTime - currentTime) / SecondsPerBeat; newTiming.MatchType = item.EventType; newTiming.TimingName = TimingNames.Miss; MatchTimings.Add(newTiming); if (newTiming.MatchType == 2) { MonsterSkillTimings.Add(newTiming); } } OnAfterMatchTimingsChanged?.Invoke(); } #endregion #region Time Beat public float SecondsPerBeat; private float m_lastBeatTime; public VoidDelegate OnAfterLastBeatTimeChanged; public GameObject PuzzleGemMatchPrefab; public List GemMatches = new List(); public MatchTiming TemplateTimng; public List MatchTimings; public List MonsterSkillTimings; public VoidDelegate OnAfterMatchTimingsChanged; public HashSet BeatBlockedPositions = new HashSet(); private int m_placedTimingsInBeat; public struct OnAfterPlayerClickParam { public float Timing; public int PerfectCount; public TimingNames TimingName; } public delegate void OnAfterPlayerClickHandler(OnAfterPlayerClickParam param); public OnAfterPlayerClickHandler OnAfterPlayerClick; public float GetCurrentTiming() { return (Time.time - m_lastBeatTime) / SecondsPerBeat; } public float GetLastBeatTime() { return m_lastBeatTime; } public void SetLastBeatTime(float value) { m_lastBeatTime = value; OnAfterLastBeatTimeChanged?.Invoke(); } //private IEnumerable GetCurrentBeatEventsRatio() //{ // foreach (var koreEvent in m_koreEvents) // { // koreEvent.StartSample // } //} private void Start_Beat() { SetLastBeatTime(Time.time); UpdateBeatTiming(); } private void Update_Beat() { if (GameFinished) { return; } if (!GameStarted) { return; } if (SecondsPerBeat <= 0) { return; } // float timeSpan = Time.time - m_lastBeatTime; // if (timeSpan > SecondsPerBeat) // { // DoBeat(); // } } int currMonsterSkillAnimIndex; private void Update_MonsterSkillAnim() { if (MonsterSkillTimings.Count < 0) return; if (MonsterSkillTimings.Count <= currMonsterSkillAnimIndex) return; if (GetCurrentTiming() > MonsterSkillTimings[currMonsterSkillAnimIndex].Timing - SkillConfigs[0].SkillPreAnim) { MonsterPreReleaseSkill(new PreReleaseSkillParam() { Monster = Monster, }); currMonsterSkillAnimIndex++; } } int currMonsterSkillIndex; private void Update_MonsterSkillDamage() { if (MonsterSkillTimings.Count < 0) return; if (MonsterSkillTimings.Count <= currMonsterSkillIndex) return; if (GetCurrentTiming() > MonsterSkillTimings[currMonsterSkillIndex].Timing + MonsterSkillTimings[currMonsterSkillIndex].SecondRadius) { if (MonsterSkillTimings[currMonsterSkillIndex].TimingName != TimingNames.Perfect) { Monster.TookAction(); // Debug.Log("~~~~~~MonsterSkillDamage"); } currMonsterSkillIndex++; } } private void AddUnique(List result, PuzzleGemMatch match) { if (result.Contains(match)) { return; } result.Add(match); } private List FindGemMatches(List items) { List result = new List(); foreach (var gemMatch in GemMatches) { foreach (var item in items) { PositionedGem? gem = GetItemPositionedGem(item); if (gem == null) { continue; } foreach (var gemInGemMatch in gemMatch.Gems) { if (gem.Value.Position == gemInGemMatch.Position) { AddUnique(result, gemMatch); break; } } } } if (result.Count > 0) { return result; } return null; } private PuzzleGemMatch FindGemMatch(MatchEvent items) { foreach (var gemMatch in GemMatches) { foreach (var gem in items.MatchedGems) { foreach (var gemInGemMatch in gemMatch.Gems) { if (gem.Position == gemInGemMatch.Position) { return gemMatch; } } } } return null; } private PuzzleGemMatch FindGemMatch(Position position) { foreach (var gemMatch in GemMatches) { foreach (var gem in gemMatch.Gems) { if (gem.Position == position) { return gemMatch; } } } return null; } private PuzzleGemMatch CreatePuzzleMatch(List itemMatch) { PuzzleGemMatch newGemMatch = PuzzleUtils.CreateObject(PuzzleGemMatchPrefab); foreach (var item in itemMatch) { newGemMatch.AddGem(GetItemPositionedGem(item).Value); } GemMatches.Add(newGemMatch); return newGemMatch; } private PuzzleGemMatch CreatePuzzleMatch(IEnumerable gems) { PuzzleGemMatch newGemMatch = PuzzleUtils.CreateObject(PuzzleGemMatchPrefab); foreach (var item in gems) { newGemMatch.AddGem(item); } GemMatches.Add(newGemMatch); return newGemMatch; } private void RefillPuzzleMatch(PuzzleGemMatch gemMatch, List itemMatch) { gemMatch.Clear(); foreach (var item in itemMatch) { gemMatch.AddGem(GetItemPositionedGem(item).Value); } } private void MergePuzzleMatch(PuzzleGemMatch dest, PuzzleGemMatch source) { dest.AddBonus(source.Bonus); GemMatches.Remove(source); Destroy(source); } int PerfectCount; private AttributeFix? GetTimingBonus(float timing, out TimingNames outTimingName) { foreach (MatchTiming matchTiming in MatchTimings) { if (matchTiming.InFirstRange(timing)) { if (matchTiming.MatchType == 1) { outTimingName = TimingNames.Miss; PerfectCount = 0; return null; } SetTimingName(matchTiming, TimingNames.Perfect); outTimingName = TimingNames.Perfect; PerfectCount++; return matchTiming.FirstBonusFix; } else if (matchTiming.InSecondRange(timing)) { if (matchTiming.MatchType == 1) { outTimingName = TimingNames.Miss; PerfectCount = 0; return null; } PerfectCount = 0; SetTimingName(matchTiming, TimingNames.Good); outTimingName = TimingNames.Good; return matchTiming.SecondBonusFix; } } PerfectCount = 0; outTimingName = TimingNames.Miss; return null; } void SetTimingName(MatchTiming currTiming, TimingNames currTimingName) { for (int j = 0; j < MonsterSkillTimings.Count; j++) { if (MonsterSkillTimings[j].Timing == currTiming.Timing) { MatchTiming newMatchTiming = new MatchTiming(); newMatchTiming.Timing = currTiming.Timing; newMatchTiming.TimingName = currTimingName; newMatchTiming.MatchType = MonsterSkillTimings[j].MatchType; Debug.Log("currIndex" + j + "SetTimingName:" + currTimingName); MonsterSkillTimings[j] = newMatchTiming; } } } public float GetTimingDis() { float dis = 0; int currAddIndex = 0; for (int j = 0; j < MatchTimings.Count; j++) { float currDis = Mathf.Abs(GetCurrentTiming() - MatchTimings[j].Timing); if (MatchTimings[j].MatchType == 1) { continue; } else if (currAddIndex == 0) { dis = currDis; currAddIndex++; } else if (currDis < dis) { dis = currDis; currAddIndex++; } } dis = dis > 0.1f ? 0.1f : dis; return dis; } private TimingNames GetSkillTimingBonus(float timing) { foreach (MatchTiming matchTiming in MatchTimings) { if (matchTiming.InFirstRange(timing)) { if (matchTiming.MatchType == 1) { return TimingNames.Perfect; } else if (matchTiming.MatchType == 2) { SetTimingName(matchTiming, TimingNames.Perfect); } } else if (matchTiming.InSecondRange(timing)) { if (matchTiming.MatchType == 1) { SetTimingName(matchTiming, TimingNames.Good); return TimingNames.Good; } else if (matchTiming.MatchType == 2) { SetTimingName(matchTiming, TimingNames.Good); } } } return TimingNames.Miss; } private bool ItemMatchSame(List a, List b) { if (a.Count != b.Count) { return false; } HashSet set = new HashSet(a); foreach (var item in b) { if (!set.Contains(item)) { return false; } } return true; } private List> GetItemMatches(int boardIndex) { List> result = Boards[boardIndex].GetMatches(); int count = result.Count; for (int i = count - 1; i >= 0; i--) { for (int j = i - 1; j >= 0; j--) { if (ItemMatchSame(result[i], result[j])) { result.RemoveAt(i); break; } } } return result; } private bool GemMatchContainsPosition(PuzzleGemMatch gemMatch, Position position) { foreach (var gem in gemMatch.Gems) { if (gem.Position == position) { return true; } } return false; } private bool GemMatchContainsBlock(PuzzleGemMatch gemMatch, ReplaceBlock placedBlock, Position placedPosition) { foreach (var gem in placedBlock.GemsToReplace) { Position finalPosition = gem.Position + placedPosition; if (!GemMatchContainsPosition(gemMatch, finalPosition)) { return false; } } return true; } private bool IsRelated(List itemMatch, ReplaceBlock placedBlock, Position placedPosition) { foreach (var item in itemMatch) { PositionedGem? gem = GetItemPositionedGem(item); if (gem == null) { continue; } foreach (var gemItem in placedBlock.GemsToReplace) { Position finalPosition = placedPosition + gemItem.Position; if (finalPosition == gem.Value.Position) { return true; } } } return false; } private IEnumerable CombineGem(IEnumerable positions, Gem gem) { foreach (var item in positions) { yield return new PositionedGem() { Position = item, Gem = gem, }; } } private void UpdateGemMatch(Position clickedPoisition, float timing) { if (m_placedTimingsInBeat >= MatchTimings.Count) { Debug.LogWarning($"Trying to choose position but reached timings limite per beat {m_placedTimingsInBeat}/{MatchTimings.Count}"); return; } PuzzleGemMatch findedGemMatch = FindGemMatch(clickedPoisition); if (findedGemMatch != null) { Debug.LogWarning($"Trying to choose position but this place has a gemMatch at {clickedPoisition}"); return; } Gem gem; HashSet connectedGems = new HashSet(); CalcConnectionMatch(clickedPoisition, connectedGems, out gem); if (connectedGems.Count <= 1) { Debug.LogWarning($"Trying to choose position but not enough connected gems at {clickedPoisition} type: {gem} connected: {connectedGems.Count}"); return; } OnAfterPlayerClickParam afterClickParam; afterClickParam.Timing = timing; PuzzleDefine.AttributeFix? bonus = GetTimingBonus(timing, out afterClickParam.TimingName); afterClickParam.PerfectCount = PerfectCount; PuzzleGemMatch newGemMatch = CreatePuzzleMatch(CombineGem(connectedGems, gem)); newGemMatch.AddBonus(bonus); newGemMatch.LastTimingName = afterClickParam.TimingName; OnAfterPlayerClick?.Invoke(afterClickParam); m_placedTimingsInBeat++; ProcessAllGemMatches(); if (Monster.Health.Value <= 0) { Win(clickedPoisition.BoardIndex); return; } UpdateBoard(clickedPoisition.BoardIndex); } private void UpdateGemMatch(ReplaceBlock placedBlock, Position placedPosition, float timing) { List> matches = GetItemMatches(placedPosition.BoardIndex); TimingNames outTimingName; PuzzleDefine.AttributeFix? bonus = GetTimingBonus(timing, out outTimingName); foreach (var itemMatch in matches) { if (!IsRelated(itemMatch, placedBlock, placedPosition)) { continue; } List findedGemMatches = FindGemMatches(itemMatch); if (findedGemMatches == null) { PuzzleGemMatch newGemMatch = CreatePuzzleMatch(itemMatch); newGemMatch.AddBonus(bonus); } else { if (findedGemMatches.Count > 1) { // Merge matches for (int i = 1; i < findedGemMatches.Count; i++) { MergePuzzleMatch(findedGemMatches[0], findedGemMatches[i]); } } else { // == 1 } RefillPuzzleMatch(findedGemMatches[0], itemMatch); if (GemMatchContainsBlock(findedGemMatches[0], placedBlock, placedPosition)) { findedGemMatches[0].AddBonus(bonus); } } } } #endregion #region Game Callbacks public void OnPlayerPlaced() { } public void OnMapMatchHappened(List matchEvents) { return; //bool processed = false; //foreach (var evt in matchEvents) //{ // if (evt.MatchedGems == null || evt.MatchedGems.Count == 0) // { // continue; // } // PuzzleGemMatch gemMatch = FindGemMatch(evt); // GemDamageSheet damageSheet = new GemDamageSheet() // { // GemMatch = gemMatch, // ComboNum = ComboNum, // }; // PuzzleDefine.Attribute finalDamage = CalcDamage(Monster, damageSheet); // DamageMonster(Monster, finalDamage); // PuzzleDefine.Attribute energyGot = PuzzleDefine.Attribute.Zero; // foreach (var item in evt.MatchedGems) // { // BoostEnergy(GamePlayer, item.Gem.Type, BattleConfig.BaseEneryPerGem); // } // processed = true; //} //if (processed) //{ // ComboNum++; //} } private void ProcessAllGemMatches() { foreach (var gemMatch in GemMatches) { GemDamageSheet damageSheet = new GemDamageSheet() { GemMatch = gemMatch, ComboNum = ComboNum, }; if (gemMatch.LastTimingName != TimingNames.Perfect && gemMatch.LastTimingName != TimingNames.Good) { continue; } DamageComposition finalDamage = CalcDamage(Monster, damageSheet); DamageMonster(new DamageMonsterParam() { Monster = Monster, Composition = finalDamage, TimingUsed = gemMatch.LastTimingName, }); PuzzleDefine.Attribute energyGot = PuzzleDefine.Attribute.Zero; foreach (var item in gemMatch.Gems) { Item theItem = GetItem(item.Position); if (theItem.puzzleHero == null || theItem.puzzleHero.IsDead) { continue; } BoostEnergy(GamePlayer, item.Gem.Type, BattleConfig.BaseEneryPerGem); } } foreach (var gemMatch in GemMatches) { foreach (var gem in gemMatch.Gems) { Item theItem = GetItem(gem.Position); theItem.Destroy(); } Destroy(gemMatch); } GemMatches.Clear(); } private MatchEvent m_currentMatchEvent; public void OnAfterGemDestroyed(Item item) { PositionedGem? gem = GetItemPositionedGem(item); if (gem == null) { return; } if (m_currentMatchEvent.MatchedGems == null) { m_currentMatchEvent.MatchedGems = new List(); } m_currentMatchEvent.MatchedGems.Add(gem.Value); } public void OnAftermapFilled() { if (m_currentMatchEvent.MatchedGems == null || m_currentMatchEvent.MatchedGems.Count == 0) { return; } OnMapMatchHappened(new List() { m_currentMatchEvent }); m_currentMatchEvent.MatchedGems.Clear(); } public void OnGemFallingFinished() { ComboNum = 0; m_duringFallilngAnimation = false; } public Gem GetRandomGem() { Gem result; result.Type = (ElementTypes)Random.Range(0, (int)ElementTypes.Max); return result; } public Item GetItem(Position position) { return Boards[position.BoardIndex].GetNode(position.Y, position.X)?.item; } public Gem? GetItemGem(Item item) { Gem result; var elementType = GetElementType(item.type); if (elementType == null) { return null; } result.Type = elementType.Value; return result; } public Position GetItemPosition(Item item) { return new Position() { X = item.node.j, Y = item.node.i, BoardIndex = item.node.board.BoardIndex, }; } public PositionedGem? GetItemPositionedGem(Item item) { if (item == null) { return null; } PositionedGem result; var elementType = GetElementType(item.type); if (elementType == null) { return null; } Gem gem; gem.Type = elementType.Value; result.Gem = gem; result.Position = GetItemPosition(item); return result; } public PositionedGem? GetPositionedGem(Position position) { return GetItemPositionedGem(GetItem(position)); } public void SetItemGem(Item item, Gem gem) { ITEM_TYPE newType = GetItemType(gem.Type); int color; GameObject prefab; Item.GetColorAndPrefab(newType, out color, out prefab); item.color = color; item.type = newType; item.GetComponent().sprite = prefab.GetComponent().sprite; } public void UpdateBoard(int boardIndex) { m_duringFallilngAnimation = true; Boards[boardIndex].FindMatches(); } public static ElementTypes? GetElementType(ITEM_TYPE itemType) { ITEM_TYPE cookieType = Item.GetCookie(itemType); switch (cookieType) { case ITEM_TYPE.COOKIE_1: return ElementTypes.Water; case ITEM_TYPE.COOKIE_2: return ElementTypes.Wood; case ITEM_TYPE.COOKIE_3: return ElementTypes.Thunder; case ITEM_TYPE.COOKIE_4: break; case ITEM_TYPE.COOKIE_5: return ElementTypes.Fire; case ITEM_TYPE.COOKIE_6: return ElementTypes.Light; case ITEM_TYPE.NONE: break; } return null; } public static ITEM_TYPE GetItemType(ElementTypes? elementType) { switch (elementType) { case ElementTypes.Fire: return ITEM_TYPE.COOKIE_5; case ElementTypes.Water: return ITEM_TYPE.COOKIE_1; case ElementTypes.Light: return ITEM_TYPE.COOKIE_6; case ElementTypes.Wood: return ITEM_TYPE.COOKIE_2; case ElementTypes.Thunder: return ITEM_TYPE.COOKIE_3; case ElementTypes.Max: break; } return ITEM_TYPE.NONE; } public SkillConfig? GetSkillConfig(int skillID) { foreach (var item in SkillConfigs) { if (item.SkillID == skillID) { return item; } } return null; } public HeroConfig? GetHeroConfig(HeroID heroID) { foreach (var item in HeroConfigs) { if (item.ID.Value == heroID.Value) { return item; } } return null; } public bool CheckLoseCondition() { if (GamePlayer.Heroes.Count == 0) { return false; } foreach (var item in GamePlayer.Heroes) { if (!item.IsDead) { return false; } } return true; } #endregion #region Round //public int PlayerPlacedTimes = 0; #endregion #region Debug private float m_startSeconds; private bool m_debuging = false; private void Start_Debug() { m_startSeconds = SecondsPerBeat; } private void Update_Debug() { if (Input.GetKeyDown(KeyCode.Space)) { m_debuging = !m_debuging; if (m_debuging) { SecondsPerBeat = 10000; } else { SecondsPerBeat = m_startSeconds; } } } #endregion #region Skill Effect public Transform EffectCenter; public GameObject VideoEffectPlayer; public GameObject VideoEffectPlane; public Camera CanvasCamera; private Canvas GetParentCanvas(Transform tr) { Canvas result = tr.GetComponent(); if (result != null) { return result; } if (tr.transform.parent != null) { return GetParentCanvas(tr.transform.parent); } return null; } private void OnHeroCastSkill_PlayEffect(PuzzleHero hero, int skillID) { SkillConfig? skillConfig = GetSkillConfig(skillID); if (skillConfig == null || skillConfig.Value.HeroSkillEffect.IsEmpty) { return; } Vector3 playPos; if (skillConfig.Value.HeroSkillEffect.PlayCentered) { playPos = EffectCenter.position; } else { // var rectTransform = hero.Displayer.GetComponent(); Vector3 worldPosition = hero.Displayer.transform.position; //worldPosition = CanvasCamera.ScreenToWorldPoint(new Vector3(worldPosition.x, worldPosition.y, EffectCenter.transform.position.z)); worldPosition.z = EffectCenter.transform.position.z; playPos = worldPosition; //Debug.Log(worldPosition); } GameObject newEffect = Instantiate(skillConfig.Value.HeroSkillEffect.Prefab, playPos, Quaternion.identity, null); if (skillConfig.Value.VideoClip != null) { float length = (float)skillConfig.Value.VideoClip.length; GameObject vidEffect = Instantiate(VideoEffectPlayer); VideoPlayer vidPlayer = vidEffect.GetComponent(); vidPlayer.clip = skillConfig.Value.VideoClip; vidPlayer.targetCamera = Camera.main; Destroy(vidEffect, length); StartCoroutine(DelayInActive(VideoEffectPlane, length)); } if (skillConfig.Value.HeroSkillEffect.Length > 0) { Destroy(newEffect, skillConfig.Value.HeroSkillEffect.Length); } } private IEnumerator DelayInActive(GameObject go, float time) { go.SetActive(true); yield return new WaitForSeconds(time); go.SetActive(false); } #endregion #region Interface public bool IfPlaceBlockWillMatch(Gem gem, Position position) { Item item = GetItem(position); PositionedGem? oldPositionedGem = GetItemPositionedGem(item); if (oldPositionedGem == null) { return false; } SetItemGem(item, gem); List> matches = GetItemMatches(position.BoardIndex); foreach (List match in matches) { foreach (Item matchItem in match) { if (item == matchItem) { return true; } } } SetItemGem(item, oldPositionedGem.Value.Gem); return false; } public void DoBeat() { //ProcessAllGemMatches(); //if (Monster.Health.Value <= 0) //{ // Win(); // return; //} //UpdateBoard(); m_placedTimingsInBeat = 0; BeatBlockedPositions.Clear(); SetLastBeatTime(Time.time); UpdateBeatTiming(); EndRound(); Monster.TookAction(); } public DamageComposition CalcDamage(PuzzleMonster monster, GemDamageSheet sheet) { PuzzleDefine.Attribute result = PuzzleDefine.Attribute.Zero; foreach (var item in sheet.GemMatch.Gems) { Item theItem = GetItem(item.Position); if (theItem.puzzleHero == null || theItem.puzzleHero.IsDead) { continue; } PuzzleDefine.Attribute baseDamage = BattleConfig.BaseDamagePerGem; if (IsWeakness(monster, item.Gem)) { baseDamage = baseDamage * BattleConfig.WeaknessDamageRatio; } result += baseDamage; } PuzzleDefine.AttributeFix comboFix = BattleConfig.ComboDamageRatio * sheet.ComboNum; result = result * (sheet.GemMatch.Bonus + comboFix + 1); return new DamageComposition() { Damage = result, GemBonus = sheet.GemMatch.Bonus, }; } public bool IsWeakness(PuzzleMonster monter, Gem gem) { foreach (var item in Monster.WeaknessTypes) { if (item == gem.Type) { return true; } } return false; } public void EndRound() { SetRound(Round + 1); StartRound(); OnAfterRoundFinished?.Invoke(); } public void StartRound() { } public void PlayerChoosePosition(Position choice) { UpdateGemMatch(choice, GetCurrentTiming()); } [Button] public void PlayerPlaceBlock(int blockID, Position placedOn) { ReplaceBlock replaceBlock; if (!GamePlayer.ReplaceBlocks.TryGetValue(blockID, out replaceBlock)) { Debug.LogWarning("Trying to place block but ReplaceBlock doesn't exist"); return; } foreach (var item in replaceBlock.GemsToReplace) { Position finalPosition = placedOn + item.Position; // If has been placed if (BeatBlockedPositions.Contains(finalPosition)) { Debug.LogWarning("Trying to place block but this place has been placed before"); return; } if (FindGemMatch(finalPosition) != null) { Debug.LogWarning($"Trying to place block but this place has a gemMatch at {finalPosition}"); return; } // If same color Item positionItem = GetItem(finalPosition); PositionedGem? gem = GetItemPositionedGem(positionItem); if (gem == null) { Debug.LogWarning($"Trying to place block but gem is not recognized at {finalPosition}"); return; } if (gem.Value.Gem.Type == item.Gem.Type) { Debug.LogWarning($"Trying to place block but gem is identical at {finalPosition}"); return; } if (!IfPlaceBlockWillMatch(item.Gem, gem.Value.Position)) { Debug.LogWarning($"Trying to place block but placement cannot form new match at{finalPosition}"); return; } } GamePlayer.ReplaceBlocks.Remove(blockID); foreach (var item in replaceBlock.GemsToReplace) { Position finalPosition = placedOn + item.Position; BeatBlockedPositions.Add(finalPosition); Item gemItem = GetItem(finalPosition); SetItemGem(gemItem, item.Gem); } //UpdateBoard(); UpdateGemMatch(replaceBlock, placedOn, GetCurrentTiming()); SlideReplaceBlocks(); GamePlayer.OnAfterReplaceBlockChagned?.Invoke(); } public PuzzleHero CreateHero(HeroID id) { HeroConfig? config = GetHeroConfig(id); if (config == null) { return null; } GameObject newHero = Instantiate(config.Value.Prefab); PuzzleHero result = newHero.GetComponent(); result.SetHealth(result.MaxHealth); return result; } public void PlayerCastSkill(PuzzleHero hero, int skillID, Board board) { if (hero.SkillEnergy.Value < hero.MaxSkillEnergy.Value) { return; } SkillConfig? config = GetSkillConfig(skillID); if (config == null) { return; } TimingNames timingNames = GetSkillTimingBonus(GetCurrentTiming()); hero.SetSkillEnergy(PuzzleDefine.Attribute.Zero); hero.IsSkillDisplay = false; // DamageMonster(hero,skillID,config,1); if (timingNames == TimingNames.Good) { DamageMonster(hero, skillID, config, 1); } else if (timingNames == TimingNames.Perfect) { DamageMonster(hero, skillID, config, 3); } DealSkillReset(hero, board); } void DealSkillReset(PuzzleHero hero, Board board) { board.ResetSkill(hero); } void DamageMonster(PuzzleHero hero, int skillID, SkillConfig? config, int count) { for (int j = 0; j < count; j++) { OnHeroCastSkill_PlayEffect(hero, skillID); DamageMonster(new DamageMonsterParam() { Monster = Monster, Composition = new DamageComposition() { Damage = config.Value.SkillDamage, }, }); } } public void BoostEnergy(PuzzleGamePlayer player, ElementTypes elementType, PuzzleDefine.Attribute amount) { foreach (var hero in player.Heroes) { if (hero.ElementType == elementType) { PuzzleDefine.Attribute afterEnergy = hero.SkillEnergy + amount; if (afterEnergy.Value > hero.MaxSkillEnergy.Value) { afterEnergy = hero.MaxSkillEnergy; } bool isShowCard = hero.SetSkillEnergy(afterEnergy); if (isShowCard) { ShowHeroSkillCard(hero); } break; } } } public void ShowHeroSkillCard(PuzzleHero hero) { // List boardIndexList = new List(); // for(int j=0;j0){ // int currBoardIndex = Random.Range(0,boardIndexList.Count); // Boards[boardIndexList[currBoardIndex]].CreateHeroSkillCard(hero); // } bool setfirst = false; List finalPosAndCount = new List(); for (int j = 0; j < Boards.Count; j++) { List currPosAndCount = Boards[j].GetMinEffectPosList(hero); if (currPosAndCount.Count > 0 && !setfirst) { setfirst = true; finalPosAndCount = currPosAndCount; } else if (currPosAndCount.Count > 0) { if (currPosAndCount[0].Count > finalPosAndCount[0].Count) { finalPosAndCount = currPosAndCount; } else if (currPosAndCount[0].Count == finalPosAndCount[0].Count) { finalPosAndCount.AddRange(currPosAndCount); } } } if (finalPosAndCount.Count < 0) { return; } int finalIndex = Random.Range(0, finalPosAndCount.Count); Boards[finalPosAndCount[finalIndex].BoardIndex].CreateHeroSkillCard(hero, finalPosAndCount[finalIndex].PosData); } public void MonsterCastSkill(PuzzleMonster monster, PuzzleHero target, int skillID) { SkillConfig? config = GetSkillConfig(skillID); if (config == null) { return; } DamageHero(target, config.Value.SkillDamage); } public void Win(int boardIndex) { GameFinished = true; Boards[boardIndex].winPopup.OpenPopup(); AudioManager.instance.PopupWinAudio(); Boards[boardIndex].SaveLevelInfo(); } public void Lose(int boardIndex) { GameFinished = true; Boards[boardIndex].losePopup.OpenPopup(); AudioManager.instance.PopupLoseAudio(); } public void DamageHero(PuzzleHero hero, PuzzleDefine.Attribute damage) { PuzzleDefine.Attribute afterHealth = hero.Health - damage; if (afterHealth.Value < 0) { afterHealth.Value = 0; } hero.SetHealth(afterHealth); hero.OnAfterDamaged?.Invoke(); Debug.Log($"Hero {hero.name} took {damage.Value} damage."); if (afterHealth.Value == 0) { hero.SetIsDead(true); if (CheckLoseCondition()) { Lose(0); } } } public delegate void OnDamageEvent(DamageMonsterParam param); [System.Serializable] public struct DamageMonsterParam { public PuzzleMonster Monster; public DamageComposition Composition; public TimingNames TimingUsed; } public void DamageMonster(DamageMonsterParam param) { PuzzleDefine.Attribute afterHealth = param.Monster.Health - param.Composition.Damage; if (afterHealth.Value < 0) { afterHealth.Value = 0; } param.Monster.SetHealth(afterHealth); param.Monster.OnAfterDamagedEvent?.Invoke(param); Debug.Log($"Monster took {param.Composition.Damage.Value} damage."); } public void MonsterPreReleaseSkill(PreReleaseSkillParam param) { param.Monster.OnAfterPreReleaseSkillEvent?.Invoke(param); } public PuzzleHero GetHeroWithItemType(ITEM_TYPE itemType) { foreach (var item in PuzzleGameMode.main.GamePlayer.Heroes) { if (item.ElementType == PuzzleGameMode.GetElementType(itemType)) { return item; } } return null; } public delegate void OnMonsterPreReleaseSkillEvent(PreReleaseSkillParam param); [System.Serializable] public struct PreReleaseSkillParam { public PuzzleMonster Monster; } #endregion private void Start() { Start_Game(); Start_Beat(); Start_Debug(); } private void Update() { Update_Beat(); Update_MonsterSkillAnim(); Update_MonsterSkillDamage(); Update_Debug(); if (Input.GetKeyDown(KeyCode.Q)) { ShowHeroSkillCard(GamePlayer.Heroes[3]); } } } [System.Serializable] public struct SkillConfig { public int SkillID; public float SkillPreAnim; public Vector2Int SkillSize; public PuzzleDefine.Attribute SkillDamage; public HeroSkillEffect HeroSkillEffect; public UnityEngine.Sprite SkillCardImage; public UnityEngine.Video.VideoClip VideoClip; }