2025-05-01 01:48:08 -07:00

1990 lines
76 KiB
C#

using System.Dynamic;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Audio;
using UnityEngine.UI;
using UnityEngine.U2D;
using DG.Tweening;
using UnityEngine.Rendering.Universal;
using TMPro;
using UnityEngine.Localization.Settings;
using static GameManager;
using UnityEngine.Localization.Tables;
using GUPS.AntiCheat.Protected;
public class GameManager : MonoBehaviour
{
private Camera mainCamera;
[HideInInspector]
public GameModifiers gameMods;
[HideInInspector]
public SoundManager soundManager;
[HideInInspector]
public TetrominoSpawner tetrominoSpawner;
private DemoTitleScreen demoTitleScreen;
ProtectedFloat startTime;
ProtectedFloat endtime;
[HideInInspector]
public ProtectedFloat timeLimit = Mathf.Infinity;
private ProtectedFloat score = 0;
private float[] scoreByPointSourceType = new float[5];
[SerializeField]
public ProtectedInt32 scoreMultiplier = 0;
public ProtectedInt32 scoreMultiplierLimit = 5000;
public ProtectedFloat scoreMultiplierDecayPerTick = 0.1f;
public ProtectedFloat scoreMultiplierTimer = 0f;
private ProtectedInt32 scoreMultiplierTimerCountdown = -1;
private ProtectedFloat scoreMultiplierAtDrain = 0;
private ProtectedInt32 startupTimerCountdown = 1;
private ProtectedFloat lastMultiplierTick = 0;
public ProtectedInt32 comboLinesFilled = -1; // C=-1; +1 when mino locks & line filled; C= when mino locks & line not filled
public bool lastFillWasDifficult = false; // Difficult fills are Tetrises or T-Spins
public bool previousClearWasDifficultSweep = false;
public bool perfectClearThisRound = true;
public bool previousPCWasTetris = false;
public ProtectedInt32 linesClearedTarget = 150;
public ProtectedInt32 linesCleared = 0;
public ProtectedInt32 tetrisweepsCleared = 0;
public ProtectedInt32 tSpinsweepsCleared = 0;
public ProtectedInt32 revealCombo = 0;
public ProtectedInt32 revealStreakManual = 0;
//[HideInInspector]
//public Tween revealComboDrainTween;
public ProtectedInt32 level = 1;
public ProtectedInt32 currentMines = 0;
public ProtectedInt32 currentFlags = 0;
public ProtectedInt32 safeEdgeTilesGained = 0;
public ProtectedInt32 sizeX = 10;
public ProtectedInt32 sizeY = 24;
public static ProtectedInt32 numMines = 5;
//private bool minesPlaced = false;
public static GameObject[][] gameBoard;
List<GameObject> floorTiles;
List<GameObject> leftBorderTiles;
List<GameObject> rightBorderTiles;
//public GameObject currentTetromino = null;
public GameObject previousTetromino = null;
public ProtectedBool isGameOver = false;
public ProtectedBool isEndless = false;
public ProtectedBool isPaused = false;
public ProtectedBool isStarted = false;
public ProtectedBool isTitleMenu = false;
ProtectedBool canPause = true;
public ProtectedBool hasQuit = false;
public ProtectedBool lineClearInstantly = false;
public ProtectedBool cheatGodMode = false;
public ProtectedBool cheatAutoFlagMode = false;
// Statistics
public ProtectedInt32 piecesPlaced;
public ProtectedInt32 holds;
public ProtectedInt32 linesweepsCleared;
public ProtectedFloat highestScoreMultiplier;
public ProtectedInt32 minesSweeped;
public ProtectedInt32 perfectClears;
public ProtectedInt32 singlesFilled;
public ProtectedInt32 doublesFilled;
public ProtectedInt32 triplesFilled;
public ProtectedInt32 tetrisesFilled;
public ProtectedInt32 tSpinMiniNoLines;
public ProtectedInt32 tSpinMiniSingle;
public ProtectedInt32 tSpinMiniDouble;
public ProtectedInt32 tSpinNoLines;
public ProtectedInt32 tSpinSingle;
public ProtectedInt32 tSpinDouble;
public ProtectedInt32 tSpinTriple;
public ProtectedFloat lastLineClearTime = 0;
private bool isBestToday = false;
private bool isHighScore = false;
private bool isHighScoreEndless = false;
private ProtectedFloat highScoreStarting;
private ProtectedFloat bestTodayStarting;
private ProtectedFloat highScoreEndlessStarting;
// Game Mods
[HideInInspector]
public GameModifiers.MinesweeperTextType textType = GameModifiers.MinesweeperTextType.numbers;
public ProtectedInt32 numBurningTiles = 0;
public ProtectedInt32 numFrozenTiles = 0;
public ProtectedInt32 numWetTiles = 0;
[HideInInspector]
public bool isHitstopPaused = false;
// Options
/*public enum WallType // your custom enumeration
{
disabledUntilAdded,
playableWalls,
zeroTileWalls,
noWalls
};
public WallType wallType = WallType.disabledUntilAdded;*/
private InputManager inputManager;
public GameObject tile;
public GameObject tileGroup;
public GameObject backgroundAnimated;
public PauseMenuMove pauseMenu;
public PauseMenuMove settingsMenu;
public PauseMenuMove marathonOverMenu;
public PauseMenuMove gameOverMenu;
public PieChart pieChart;
public ProgressBar revealStreakManualProgressBar;
public GameObject backgroundWallLeft;
public GameObject backgroundWallRight;
public GameObject backgroundFloor;
public GameObject backgroundGrid;
public GameObject guiCanvas;
public GameObject framePreview;
public GameObject frameHold;
public GameObject floatingText;
public FloatingTextQueue floatingTextQueue;
public GameObject confetti;
public AudioClip lineClearSound;
public AudioClip lineFullSound1;
public AudioClip lineFullSound2;
public AudioClip lineFullSound3;
public AudioClip lineFullSound4;
public AudioClip perfectClearSound;
public AudioClip gameOverSound;
public AudioClip multiplierCountdownSound;
public AudioClip multiplierCountdownSoundFinal;
public AudioClip startupCountdownSound;
public AudioClip startupCountdownSoundFinal;
public AudioClip startupCountdownSound2;
public AudioClip startupCountdownSoundFinal2;
public AudioClip revealStreakManual100Sound;
public AudioClip[] fanfareSounds;
public delegate void LineClearEvent(int lines);
public static event LineClearEvent OnLineClearEvent;
public delegate void HardDropEvent();
public static event HardDropEvent OnHardDropEvent;
public delegate void GameOverEvent();
public static event GameOverEvent OnGameOverEvent;
public delegate void LeftStuckEvent();
public static event LeftStuckEvent OnLeftStuckEvent;
public delegate void RightStuckEvent();
public static event RightStuckEvent OnRightStuckEvent;
public delegate void MinoLockEvent();
public static event MinoLockEvent OnMinoLockEvent;
public delegate void TileSolveEvent();
public static event TileSolveEvent OnTileSolveOrLandEvent;
public delegate void TSpinEvent(int dir);
public static event TSpinEvent OnTSpinEvent;
public delegate void ResetStartingPositionsEvent();
public static event ResetStartingPositionsEvent OnResetStartingPositionsEvent;
public delegate void KillTweenEvent();
public static event KillTweenEvent OnKillTweenEvent;
public delegate void NewPieceEvent();
public static event NewPieceEvent OnNewPieceEvent;
public delegate void MultiplierEvent();
public static event MultiplierEvent OnMultiplierEvent;
private void OnApplicationQuit()
{
DOTween.KillAll();
}
#region Game Setup
// Start is called before the first frame update
void Awake()
{
DOTween.KillAll();
mainCamera = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Camera>();
inputManager = InputManager.Instance;
soundManager = GetComponent<SoundManager>();
gameMods = ScoreKeeper.Instance.GetComponent<GameModifiers>();
tetrominoSpawner = GameObject.FindObjectOfType<TetrominoSpawner>();
demoTitleScreen = GameObject.FindObjectOfType<DemoTitleScreen>();
highScoreStarting = ScoreKeeper.Instance.bestScore;
bestTodayStarting = ScoreKeeper.Instance.bestScoreToday;
highScoreEndlessStarting = ScoreKeeper.Instance.bestScoreEndless;
ScoreKeeper.Instance.runs += 1;
textType = gameMods.minesweeperTextType;
linesClearedTarget = gameMods.targetLines;
timeLimit = gameMods.timeLimit;
if (gameMods.lineClearTrigger == GameModifiers.LineClearTriggerType.clearInstantly)
lineClearInstantly = true;
else
lineClearInstantly = false;
sizeX = Mathf.Max((int)gameMods.boardSize.x, 4);
sizeY = Mathf.Max((int)gameMods.boardSize.y + 4, 8);
BuildGameBoard();
startTime = Time.time;
if (gameMods.timeLimit == Mathf.Infinity && !gameMods.detailedTimer)
isStarted = true;
else
{
// This means we'll have a countdown to start. Let's start the page black!
mainCamera.GetComponent<LoadNewScene>().fadeIn = false;
mainCamera.GetComponent<LoadNewScene>().blackScreen.gameObject.SetActive(true);
}
// Fixed Marathon: 10 per level
while (linesCleared >= level * 10)
level += 1;
//PopulateMines();
}
public IEnumerator Start()
{
yield return LocalizationSettings.InitializationOperation;
//Debug.Log(LocalizationSettings.SelectedLocale);
}
#region Input
void OnEnable()
{
inputManager.escapePress.started += _ => PressEscape();
inputManager.restartPress.started += _ => PressRestart();
inputManager.hardClearPress.started += _ => PressHardClear();
}
void OnDisable()
{
inputManager.escapePress.started -= _ => PressEscape();
inputManager.restartPress.started -= _ => PressRestart();
inputManager.hardClearPress.started -= _ => PressHardClear();
}
void OnDestroy()
{
OnLineClearEvent = null;
OnHardDropEvent = null;
OnGameOverEvent = null;
OnLeftStuckEvent = null;
OnRightStuckEvent = null;
OnMinoLockEvent = null;
OnTileSolveOrLandEvent = null;
OnTSpinEvent = null;
OnResetStartingPositionsEvent = null;
OnKillTweenEvent = null;
OnNewPieceEvent = null;
OnMultiplierEvent = null;
transform.DOKill();
}
void PressEscape()
{
Pause(!isPaused);
}
void PressRestart()
{
ReloadScene();
}
void PressHardClear()
{
deleteFullRows(false);
previousTetromino = null;
//currentTetromino = null;
}
#endregion
// Update is called once per frame
void Update()
{
if (!isStarted)
{
mainCamera.GetComponent<LoadNewScene>().blackScreen.color = new Color(0, 0, 0, 1 - (float)(startupTimerCountdown + 1) / 6);
//GetComponent<Light2D>().intensity = ((float)startupTimerCountdown / 4) * 0.6f;
if (Time.time - startTime >= (float)startupTimerCountdown - 0.5f)
{
GameObject floater = Instantiate(floatingText, new Vector3((sizeX / 2f) - 0.5f, (sizeY - 4) / 2, 0), Quaternion.identity, guiCanvas.transform);
//Debug.Log(startupTimerCountdown);
switch (startupTimerCountdown)
{
case 4:
floater.GetComponent<TextMeshProUGUI>().text = GetTranslation("UIText", "GUI Go");
soundManager.PlayClip(startupCountdownSoundFinal, 1, false);
soundManager.PlayClip(startupCountdownSoundFinal2, 1, false);
GetComponent<Light2D>().intensity = 1;
mainCamera.GetComponent<LoadNewScene>().blackScreen.color = Color.clear;
isStarted = true;
startTime = Time.time;
break;
default:
floater.GetComponent<TextMeshProUGUI>().text = (4 - startupTimerCountdown).ToString();
soundManager.PlayClip(startupCountdownSound, 1, false);
soundManager.PlayClip(startupCountdownSound2, 1, false);
break;
}
startupTimerCountdown++;
}
}
//Debug.Log((Mathf.Floor(0.04f * 100) / 100) + 0.01f);
if (scoreMultiplierTimer > 0 && !isGameOver)
scoreMultiplierTimer -= Time.deltaTime;
if (scoreMultiplierTimer > -1 && GetScoreMultiplier() > 0)
{
if (scoreMultiplierTimer <= (float)scoreMultiplierTimerCountdown)
{
//Debug.Log(scoreMultiplierTimerCountdown);
switch (scoreMultiplierTimerCountdown)
{
case 0:
soundManager.PlayClip(multiplierCountdownSoundFinal, 0.5f, true);
break;
default:
soundManager.PlayClip(multiplierCountdownSound, 1, false);
break;
}
scoreMultiplierTimerCountdown--;
}
}
if (scoreMultiplierTimer <= 0 && GetScoreMultiplier() > 0)
{
soundManager.PlayMultiplierDrain();
float tickTime = 0.04f; // x0.25 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.1f)
tickTime = 0.03f; // x0.33 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.2f)
tickTime = 0.02f; // x0.50 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.3f)
tickTime = 0.01f; // x1 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.4f)
tickTime = 0.005f; // x1.5 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.5f)
tickTime = 0.0025f; // x2 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.6f)
tickTime = 0.00125f; // x2.5 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.7f)
tickTime = 0.000625f; // x3 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.8f)
tickTime = 0.0003125f; // x3.5 / sec
if (scoreMultiplier > scoreMultiplierLimit * 0.9f)
tickTime = 0.00015625f; // x4 / sec
SetMultiplierBackground();
if (Time.time - lastMultiplierTick >= tickTime && !isGameOver)
{
if (GetScoreMultiplier() > 0)
{
// Get the current level, but cap it at 30: endless mode should not be affected.
float tempLevel = level;
if (tempLevel > 30)
tempLevel = 30;
// Multiplier drain per tick will be half of the level
int multiplierDrain = Mathf.CeilToInt(tempLevel / 4);
// If the level is a multiple of 5, increase it by 1 so that the display doesn't flash between .5 and no decimal; the true cap on marathon games is 16 at level 29.
if (multiplierDrain % 5 == 0)
multiplierDrain += 1;
// Drain the multiplier...
SetScoreMultiplier(-1 * multiplierDrain, 0);
// If the multiplier is gone, turn off the background animation.
if (GetScoreMultiplier() <= 0)
{
backgroundAnimated.SetActive(false);
soundManager.StopMultiplierDrain();
}
}
}
scoreMultiplierTimer = 0;
}
if (timeLimit < Mathf.Infinity)
if (GetTime() >= timeLimit)
Pause(true, true);
/*if (cheatGodMode)
{
if (Input.GetKeyDown(KeyCode.M))
SetScoreMultiplier(4f, 30);
if (Input.GetKeyDown(KeyCode.K))
AddSafeTileToEdges();
if (Input.GetKeyDown(KeyCode.L))
linesCleared++;
}*/
}
void BuildGameBoard()
{
// Playable Game Board
gameBoard = new GameObject[sizeX][];
for (int i = 0; i < sizeX; i++)
{
GameObject[] tileColumn = new GameObject[sizeY];
for (int j = 0; j < sizeY; j++)
{
tileColumn[j] = null; // blankTile;
}
gameBoard[i] = tileColumn;
}
// Bottom Display Tiles
floorTiles = new List<GameObject>();
for (int i = 0; i < sizeX; i++)
{
//place display tiles at bottom
GameObject newTile = Instantiate(tile, new Vector3(i, -1, 0), new Quaternion(0, 0, 0, 0), this.gameObject.transform) as GameObject;
newTile.name = "Tile (" + i + ", " + -1 + ")";
newTile.GetComponent<Tile>().coordX = i;
newTile.GetComponent<Tile>().coordY = -1;
newTile.GetComponent<Tile>().isRevealed = true;
newTile.GetComponent<Tile>().isDisplay = true;
floorTiles.Add(newTile);
}
// Left and Right Tiles
leftBorderTiles = new List<GameObject>();
rightBorderTiles = new List<GameObject>();
if (gameMods.wallType == GameModifiers.WallType.playable)
{
for (int i = -1; i < sizeY; i++)
{
//place display tile on left side
GameObject newTile = Instantiate(tileGroup, new Vector3(-1, i, 0), new Quaternion(0, 0, 0, 0), this.gameObject.transform) as GameObject;
newTile.name = "Tile Group (" + -1 + ", " + i + ")";
newTile.GetComponentInChildren<Tile>().coordX = -1;
newTile.GetComponentInChildren<Tile>().coordY = i;
newTile.GetComponent<Group>().isDisplay = true;
newTile.GetComponent<Group>().minePercent = 10;
leftBorderTiles.Add(newTile);
//place display tile on right side
newTile = Instantiate(tileGroup, new Vector3(sizeX, i, 0), new Quaternion(0, 0, 0, 0), this.gameObject.transform) as GameObject;
newTile.name = "Tile Group (" + sizeX + ", " + i + ")";
newTile.GetComponentInChildren<Tile>().coordX = sizeX;
newTile.GetComponentInChildren<Tile>().coordY = i;
newTile.GetComponent<Group>().isDisplay = true;
newTile.GetComponent<Group>().minePercent = 10;
rightBorderTiles.Add(newTile);
}
// Place bombs Left and Right Tiles
// Place mine at bottom most space
leftBorderTiles[0].GetComponentInChildren<Tile>().isMine = true;
rightBorderTiles[0].GetComponentInChildren<Tile>().isMine = true;
// Prevent most 0-Cascade tiles from spawning at index 10-18
for (int i = 2+Random.Range(0, 3); i < sizeY - 5; i+=Random.Range(1, 4))
{
if (i < sizeY - 4)
if (!leftBorderTiles[i-1].GetComponentInChildren<Tile>().isMine || !leftBorderTiles[i-2].GetComponentInChildren<Tile>().isMine)
leftBorderTiles[i].GetComponentInChildren<Tile>().isMine = true;
}
for (int i = 2+Random.Range(0, 3); i < sizeY - 5; i+=Random.Range(1, 4))
{
if (i < sizeY - 4)
if (!rightBorderTiles[i-1].GetComponentInChildren<Tile>().isMine || !rightBorderTiles[i-2].GetComponentInChildren<Tile>().isMine)
rightBorderTiles[i].GetComponentInChildren<Tile>().isMine = true;
}
}
else if (gameMods.wallType == GameModifiers.WallType.unlock)
{
safeEdgeTilesGained = -1;
AddSafeTileToEdges();
}
SetCameraScale();
/*float backgroundHeight = sizeY + 1;
if (cameraSize > cameraSizeYprefer)
backgroundHeight = sizeY - 3;*/
if (!isTitleMenu)
{
framePreview.transform.position = new Vector3(sizeX + 4, sizeY - 8);
frameHold.transform.position = new Vector3(-10, sizeY - 8);
}
float backgroundHeight = sizeY - 3;
backgroundWallRight.transform.position = new Vector3(sizeX - 0.5f, -1.5f, 1);
backgroundWallLeft.GetComponent<SpriteRenderer>().size = new Vector2(1, backgroundHeight);
backgroundWallRight.GetComponent<SpriteRenderer>().size = new Vector2(1, backgroundHeight);
backgroundFloor.GetComponent<SpriteRenderer>().size = new Vector2(sizeX, 1);
backgroundGrid.GetComponent<SpriteRenderer>().size = new Vector2(sizeX, sizeY);
SetGridOpacity();
backgroundAnimated.GetComponent<SpriteRenderer>().size = new Vector2(sizeX, backgroundHeight - 1);
}
public void SetCameraScale(bool isStartup = true)
{
if (!isStartup && isTitleMenu)
return;
// Get the two points that need to be visible
bool previewBuffer = (PlayerPrefs.GetInt("PreviewSpaceAboveBoardEnabled", 0) != 0);
float guiBuffer = 12f;
float borderBufferX = 0f;
float borderBufferY = 0f;
Vector3 bottomLeft = new Vector3(-1.5f - guiBuffer - borderBufferX, -1.5f - borderBufferY);
Vector3 topRight = new Vector3(sizeX + 0.5f + guiBuffer + borderBufferX, sizeY - 4.5f + borderBufferY);
if (previewBuffer)
topRight.y += 4;
Transform buttonsBoundTransform = null;
if (isTitleMenu)
{
/*cameraSizeXprefer = (sizeX * 2 + 18) * 0.5f * ((float)mainCamera.pixelHeight / mainCamera.pixelWidth);
cameraSizeXprefer = Mathf.Max(cameraSizeXprefer, 10.5f * ((float)mainCamera.pixelHeight / mainCamera.pixelWidth));
cameraSizeYprefer = Mathf.Max(cameraSizeYprefer, 10.5f);*/
buttonsBoundTransform = demoTitleScreen.topLeftBounds;
bottomLeft = new Vector3(Mathf.Min((sizeX + 1.5f) * -1, buttonsBoundTransform.position.x), -1.5f);
topRight = new Vector3(sizeX + 1.5f, Mathf.Max(sizeY - 4.5f, buttonsBoundTransform.position.y));
}
Vector3 centerPoint = (bottomLeft + topRight) / 2f;
float distance = Vector3.Distance(bottomLeft, topRight);
float horizontalDistance = Mathf.Abs(bottomLeft.x - topRight.x);
float verticalDistance = Mathf.Abs(bottomLeft.y - topRight.y);
//float greaterDistance = Mathf.Max(horizontalDistance, verticalDistance);
float aspectRatio = mainCamera.aspect;
float orthographicSize = verticalDistance / 2f;
// Adjust orthographic size based on the aspect ratio
if (horizontalDistance / aspectRatio > verticalDistance) // Landscape
{
orthographicSize = horizontalDistance / (2f * aspectRatio);
}
else // Portrait
{
orthographicSize = verticalDistance / 2f;
}
float sizeModifier = orthographicSize / mainCamera.orthographicSize;
mainCamera.transform.position = new Vector3(centerPoint.x, centerPoint.y, mainCamera.transform.position.z);
mainCamera.orthographicSize = orthographicSize;
if (isTitleMenu)
{
Transform buttonsTransform = demoTitleScreen.gameObject.transform;
buttonsTransform.localScale = new Vector3(sizeModifier, sizeModifier, sizeModifier);
buttonsTransform.localPosition = new Vector3(bottomLeft.x + horizontalDistance / 4, mainCamera.ScreenToWorldPoint(new Vector3(0, (float)Screen.height / 2, 0)).y);
demoTitleScreen.bouncyLogo.transform.position = new Vector3((sizeX / 2f) - 0.5f, topRight.y);
demoTitleScreen.ResetStartingPositionsInChildren(sizeModifier);
}
/*float cameraSizeYprefer = ((sizeY - 4) / 2f) + 0.5f; //10.5f; Y Bounds
float cameraSizeXprefer = (sizeX + 28) * 0.5f * ((float)mainCamera.pixelHeight / mainCamera.pixelWidth); //10f; X Bounds
if (isTitleMenu)
{
cameraSizeXprefer = (sizeX * 2 + 18) * 0.5f * ((float)mainCamera.pixelHeight / mainCamera.pixelWidth);
cameraSizeXprefer = Mathf.Max(cameraSizeXprefer, 10.5f * ((float)mainCamera.pixelHeight / mainCamera.pixelWidth));
cameraSizeYprefer = Mathf.Max(cameraSizeYprefer, 10.5f);
}
float cameraSize = Mathf.Max(cameraSizeXprefer, cameraSizeYprefer);
if (sizeX == 10 && sizeY == 24 && (Mathf.Floor(((float)mainCamera.pixelWidth / mainCamera.pixelHeight) * 100) / 100) == Mathf.Floor((16f / 9f) * 100) / 100)
cameraSize = cameraSizeYprefer;
//Debug.Log(((float)mainCamera.pixelWidth / mainCamera.pixelHeight) + ", " + (16f/9f));
float cameraX = (sizeX / 2f) - 0.5f; //4.5f;
float cameraY = ((sizeY - 4) / 2f) - 1f;//cameraSize - 1.5f; //9
if (isTitleMenu)
{
// cameraSize = ((sizeY - 4) / 2f) + 0.5f;
// cameraSize - .5f = (sizeY - 4) / 2f
// (cameraSize - .5f) * 2 = sizeY - 4
// ((cameraSize - .5f) * 2) + 4 = sizeY
float yscaleModifier = (cameraSize - 0.5f);
float sizeModifier = Mathf.Max((cameraSize / 10.5f), 1);
//cameraX = (cameraSize / 4) - (sizeX * sizeModifier) / 4 - 1; // Mathf.Max(cameraX - sizeX - 1, -4.5f);
//cameraX = (cameraSize / ((float)mainCamera.pixelHeight / mainCamera.pixelWidth)) / -4;
float camWithCoord00AtBottomLeft = ((float)mainCamera.pixelWidth / mainCamera.pixelHeight) * cameraSize;
cameraX = camWithCoord00AtBottomLeft - (sizeX + 1.5f) * 1.5f;// (camWithCoord00AtBottomLeft * -1) + sizeX * 1.5f;// - 1.5f;
cameraY = yscaleModifier - sizeModifier;
cameraY = Mathf.Max(cameraY, 9);
Transform buttonsTransform = GameObject.FindObjectOfType<DemoTitleScreen>().gameObject.transform;
//float sizeModifier = Mathf.Max(cameraX / (10.5f * ((float)mainCamera.pixelHeight / mainCamera.pixelWidth)),
cameraY / (10.5f * ((float)mainCamera.pixelHeight / mainCamera.pixelWidth)),
1);
sizeModifier = Mathf.Max(yscaleModifier / 10.5f, 1);
buttonsTransform.localScale = new Vector3(sizeModifier, sizeModifier, sizeModifier);
//buttonsTransform.localPosition = new Vector3(Mathf.Min(-7.5f, sizeX / -4), buttonsTransform.localPosition.y);
buttonsTransform.localPosition = new Vector3(mainCamera.ScreenToWorldPoint(new Vector3((float)Screen.width / 4, 0, 0)).x * sizeModifier, buttonsTransform.localPosition.y);
// buttonsTransform.localPosition = new Vector3(mainCamera.ScreenToWorldPoint(new Vector3((float)mainCamera.pixelWidth / 4,0,0)).x, buttonsTransform.localPosition.y);//Mathf.Min(-7.5f, sizeX / -4)
}
mainCamera.transform.position = new Vector3(cameraX, cameraY, -10);
mainCamera.orthographicSize = cameraSize;
*/
float canvasHeight = (450 / 10.5f) * mainCamera.orthographicSize;
if (guiCanvas != null)
{
guiCanvas.transform.position = new Vector3(mainCamera.transform.position.x, mainCamera.transform.position.y, 0);
guiCanvas.transform.GetComponent<RectTransform>().sizeDelta = new Vector2(canvasHeight * aspectRatio, canvasHeight);
}
TriggerOnResetStartingPositionsEvent();
}
/*void BuildMinesweeperBoard()
{
gameBoard = new GameObject[sizeX][];
for (int i = 0; i < sizeX; i++)
{
GameObject[] tileColumn = new GameObject[sizeY];
for (int j = 0; j < sizeY; j++)
{
GameObject newTile = Instantiate(tile, new Vector3(i, j, 0), new Quaternion(0, 0, 0, 0), this.gameObject.transform) as GameObject;
newTile.name = "Tile (" + i + ", " + j + ")";
newTile.GetComponent<Tile>().coordX = i;
newTile.GetComponent<Tile>().coordY = j;
tileColumn[j] = newTile;
}
gameBoard[i] = tileColumn;
}
}*/
/*void PopulateMines(int startX = -10, int startY = -10)
{
int currentMines = 0;
while (currentMines < numMines)
{
int randX = Random.Range(0, sizeX - 1);
int randY = Random.Range(0, sizeY - 1);
if (!(randX == startX && randY == startY)
&& !(randX == startX - 1 && randY + 1 == startY)
&& !(randX == startX - 1 && randY == startY)
&& !(randX == startX - 1 && randY - 1 == startY)
&& !(randX == startX && randY + 1 == startY)
&& !(randX == startX && randY - 1 == startY)
&& !(randX == startX + 1 && randY + 1 == startY)
&& !(randX == startX + 1 && randY == startY)
&& !(randX == startX + 1 && randY - 1 == startY))
{
if (GetGameTile(randX, randY).isMine == false)
{
GetGameTile(randX, randY).isMine = true;
DetectProximity(randX, randY);
currentMines += 1;
}
}
}
minesPlaced = true;
}*/
/*void DetectProximity(int x, int y)
{
foreach (Tile t in GetNeighborTiles(x, y))
{
t.nearbyMines += 1;
}
}*/
//public void RevealTile(int x, int y, int nearbyMines, bool isMine)
//{
//if (!minesPlaced)
//PopulateMines(x, y);
//}
#endregion
#region Minesweeper Logic
public ArrayList GetNeighborTiles(int x, int y, bool getDiagonals = true)
{
ArrayList neighbors = new ArrayList();
if (GetGameTile(x - 1, y) != null)
neighbors.Add(GetGameTile(x - 1, y));
if (GetGameTile(x + 1, y) != null)
neighbors.Add(GetGameTile(x + 1, y));
if (GetGameTile(x, y - 1) != null)
neighbors.Add(GetGameTile(x, y - 1));
if (GetGameTile(x, y + 1) != null)
neighbors.Add(GetGameTile(x, y + 1));
if (getDiagonals)
{
if (GetGameTile(x - 1, y - 1) != null)
neighbors.Add(GetGameTile(x - 1, y - 1));
if (GetGameTile(x - 1, y + 1) != null)
neighbors.Add(GetGameTile(x - 1, y + 1));
if (GetGameTile(x + 1, y - 1) != null)
neighbors.Add(GetGameTile(x + 1, y - 1));
if (GetGameTile(x + 1, y + 1) != null)
neighbors.Add(GetGameTile(x + 1, y + 1));
}
return neighbors;
}
public Tile GetGameTile(int x, int y)
{
/*if (x < 0 || y < 0 || x >= gameBoard.GetLength(0) || y >= gameBoard.GetLength(1))
{
Debug.Log("GetGameTile: Out of Bounds; " + x + ", " + y);
return null;
}*/
if (x >= 0 && x < sizeX && y >= 0 && y < sizeY)
{
if (gameBoard[x][y] != null)
return gameBoard[x][y].GetComponent<Tile>();
else
return null;
}
else if (x == -1 && y >= -1 && y < leftBorderTiles.Count-1)
{
return leftBorderTiles[y+1].GetComponentInChildren<Tile>();
}
else if (x == sizeX && y >= -1 && y < rightBorderTiles.Count-1)
{
return rightBorderTiles[y+1].GetComponentInChildren<Tile>();
}
else if (x >= 0 && x < sizeX && y == -1)
{
return floorTiles[x].GetComponent<Tile>();
}
//Debug.Log("Failed to find tile " + x + ", " + y);
return null;
}
public static bool insideBorder(Vector2 pos)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
return ((int)pos.x >= 0 &&
(int)pos.x < gm.sizeX &&
(int)pos.y >= 0 &&
(int)pos.y < gm.sizeY);
}
#endregion
#region Tetris Logic
public static void deleteRow(int y)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
for (int x = 0; x < gm.sizeX; ++x)
{
Tile tile = gameBoard[x][y].gameObject.GetComponent<Tile>();
if (tile.aura == Tile.AuraType.frozen)
{
tile.PlaySoundIceBreak();
tile.SetAura(Tile.AuraType.wet);
}
else
{
tile.isDestroyed = true;
Destroy(gameBoard[x][y].gameObject);
gameBoard[x][y] = null;
}
}
}
public static bool decreaseRow(int y) // returns true if the falling piece clips into the falling row
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
Group activeTetrominoInRow = null;
bool currentTetrominoClipped = false;
for (int x = 0; x < gm.sizeX; ++x)
{
if (gameBoard[x][y] != null)
{
if (gameBoard[x][y].GetComponentInParent<Group>().isFalling) // Active tetromino should not be moved directly, or it will cause a rotation error
{
activeTetrominoInRow = gameBoard[x][y].GetComponentInParent<Group>();
}
else // All static tiles should be moved down
{
bool dropDown = false;
// ...unless it has a tile there already (caused by ice or falling piece that didn't kick correctly)
if (gameBoard[x][y - 1] == null)
{
dropDown = true;
}
else if (gameBoard[x][y - 1].GetComponentInParent<Group>().isFalling) // Handle falling minos in the wrong space
{
Group activeMino = gameBoard[x][y - 1].GetComponentInParent<Group>();
int i = 1;
while (!activeMino.WallKickMove(0, i, true, false))
{
i++;
}
currentTetrominoClipped = true;
dropDown = true;
}
if (dropDown)
{
// Move one towards bottom
gameBoard[x][y - 1] = gameBoard[x][y];
gameBoard[x][y] = null;
// Update Block position
gameBoard[x][y - 1].GetComponent<Tile>().coordY -= 1;
}
}
}
}
if (activeTetrominoInRow != null) // Move the active tetromino after the rest of the row has finished
{
// The active tetromino should not change position, unless it needs to fall in order for blocks above it to have a place to land
activeTetrominoInRow.WallKickMove(0, 1); // Attempt to move the tetromino up. This will do nothing if the space is blocked
activeTetrominoInRow.Fall(1, false, false); // Move the tetromino down. This will put it back in the same place if it was moved up, otherwise it will get the tetromino out of the way
}
return currentTetrominoClipped;
}
public Group GetActiveTetromino()
{
for (int x = 0; x < sizeX; ++x)
{
for (int y = 0; y < sizeY; ++y)
{
if (gameBoard[x][y] != null)
{
if (gameBoard[x][y].GetComponentInParent<Group>().isFalling) // Active tetromino should not be moved directly, or it will cause a rotation error
{
return gameBoard[x][y].GetComponentInParent<Group>();
}
}
}
}
return null;
}
public static void decreaseRowsAbove(int y)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
bool currentTetrominoClipped = false;
for (int i = y; i < gm.sizeY; ++i)
currentTetrominoClipped = currentTetrominoClipped || decreaseRow(i);
if (currentTetrominoClipped)
gm.tetrominoSpawner.currentTetromino.GetComponent<Group>().Fall(1, false, false);
}
public static bool isRowFull(int y)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
for (int x = 0; x < gm.sizeX; ++x)
if (gameBoard[x][y] == null)
return false;
else if (gameBoard[x][y].GetComponentInParent<Group>().isFalling)
return false;
return true;
}
public static bool isRowEmpty(int y)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
for (int x = 0; x < gm.sizeX; ++x)
if (gameBoard[x][y] != null)
if (!gameBoard[x][y].GetComponentInParent<Group>().isFalling)
return false;
return true;
}
// Checks whether a perfect clear is possible if all currently solved lines were cleared, then clears those lines if it would create a PC.
public static void CheckForPossiblePerfectClear()
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
for (int y = 0; y < gm.sizeY; ++y)
{
if (!isRowEmpty(y))
if (!isRowSolved(y))
return;
}
int rowsFilled = 0;
if (gm.previousTetromino != null)
rowsFilled = gm.previousTetromino.GetComponent<Group>().rowsFilled;
deleteFullRows(true, rowsFilled);
}
public void CheckForPerfectClear(int rowsCleared, int rowsFilled, bool isInstantPC)
{
//Debug.Log(rowsCleared + ", " + rowsFilled + ", " + isInstantPC);
if (perfectClearThisRound)
return;
if (linesCleared == 0)
return;
//if (linesCleared == 0)
//return;
// Make sure nothing is on the board
for (int y = 0; y < sizeY; ++y)
{
if (!isRowEmpty(y))
return;
}
perfectClearThisRound = true;
int rowsActuallyFilled = rowsFilled;
if (rowsCleared < rowsActuallyFilled)
rowsActuallyFilled = rowsCleared;
float pcScore = 800;
string scoreTranslationKey = "";
string scoreTranslationKeyPrefix1 = "";
string scoreTranslationKeyPrefix2 = "";
// Give rewards for a perfect clear!
if (rowsActuallyFilled == 0 || rowsActuallyFilled == 1)
{
pcScore = 800 + (100 * (rowsCleared - 1));
SetScoreMultiplier(10, 30);
previousPCWasTetris = false;
scoreTranslationKey = "Scoring Perfect clear 1";
}
else if (rowsActuallyFilled == 2)
{
pcScore = 1200 + (150 * (rowsCleared - 2));
SetScoreMultiplier(25, 30);
previousPCWasTetris = false;
scoreTranslationKey = "Scoring Perfect clear 2";
}
else if (rowsActuallyFilled == 3)
{
pcScore = 1800 + (200 * (rowsCleared - 3));
SetScoreMultiplier(50, 30);
previousPCWasTetris = false;
scoreTranslationKey = "Scoring Perfect clear 3";
}
else if (rowsActuallyFilled >= 4)
{
pcScore = 500 * rowsCleared;
scoreTranslationKey = "Scoring Perfect clear 4";
if (previousPCWasTetris)
{
pcScore = pcScore * 1.6f;
scoreTranslationKeyPrefix1 = "Scoring Back-to-back";
}
SetScoreMultiplier(100, 30);
previousPCWasTetris = true;
}
if (isInstantPC)
{
AddSafeTileToEdges();
pcScore = pcScore * 1.5f;
scoreTranslationKeyPrefix2 = "Scoring Instant";
}
AddScore(((int)pcScore), scoreTranslationKey, 0, scoreTranslationKeyPrefix1, scoreTranslationKeyPrefix2);
/*if (previousTetromino.GetComponent<Group>().rowsFilled == 4) // Tetrisweep Perfect Clear!
{
Debug.Log("Tetrisweep Perfect Clear!");
AddScore(3200);
SetScoreMultiplier(50, 50);
}
else // Normal Perfect Clear!
{
Debug.Log("Perfect Clear!");
AddScore(2000);
SetScoreMultiplier(15, 30);
}*/
perfectClears += 1;
soundManager.PlayClip(perfectClearSound, 1, true);
}
#endregion
#region Tetrisweeper Solved Logic
public static int deleteFullRows(bool getMultiplier = true, int rowsFilled = 0, GameObject tetrominoThatJustLocked = null) //, bool isTriggeredByLock = false)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
if (gm.isGameOver || gm.isHitstopPaused)
return 0;
int rowsCleared = 0;
int delayedLinesweepsCleared = 0;
int instantLinesweepsCleared = 0;
int highestRowSolved = -1;
float burningMultiplier = 1;
bool containsFrozenTile = false;
// Score all of the solved rows
for (int y = 0; y < gm.sizeY; ++y)
{
if (isRowSolved(y))
{
highestRowSolved = y;
if (!getMultiplier)
{
gm.ResetScoreMultiplier();
}
(bool isDelayedLinesweep, bool isInstantLinesweep, float lineBurningMultiplier, bool lineContainsFrozenTile) = scoreSolvedRow(y, getMultiplier, tetrominoThatJustLocked);
gm.linesCleared++;
gm.lastLineClearTime = Time.time;
rowsCleared++;
if (isInstantLinesweep)
{
instantLinesweepsCleared++;
gm.linesweepsCleared += 1;
}
else if (isDelayedLinesweep)
{
delayedLinesweepsCleared++;
gm.linesweepsCleared += 1;
}
burningMultiplier += lineBurningMultiplier;
containsFrozenTile = containsFrozenTile || lineContainsFrozenTile;
}
}
// Hitstop pause
if (rowsCleared > 0)
{
if (!gm.isHitstopPaused)
{
Time.timeScale = 0f;
gm.isHitstopPaused = true;
}
//FADEOUT TODO
float delayTime = 0.15f;
if (PlayerPrefs.GetInt("LineClearDelayEnabled", 1) == 0) // If Line Clear Delay is disabled, set delay time to 0.
delayTime = 0;
gm.StartCoroutine(gm.DeleteFullRowsDelayedHelper(delayTime, rowsCleared, rowsFilled, instantLinesweepsCleared, tetrominoThatJustLocked));
}
/*// Delete the finished Rows
for (int y = 0; y < gm.sizeY; ++y)
{
if (isRowSolved(y))
{
deleteRow(y);
decreaseRowsAbove(y + 1);
--y;
}
} */
if (rowsCleared > 0)
{
gm.soundManager.PlayClip(gm.lineClearSound, 0.5f, true);
// Fixed Marathon: 10 per level
while (gm.linesCleared >= gm.level * 10)
gm.level += 1;
if (OnLineClearEvent != null)
OnLineClearEvent(rowsCleared);
string scoreTranslationKeyLineClear = "Scoring Lines Cleared";
if (rowsCleared == 1)
scoreTranslationKeyLineClear = "Scoring Line Cleared";
// Calculate Multipliers
float lineClearMultiplier = 1;
lineClearMultiplier *= gm.GetRowHeightPointModifier(highestRowSolved);
lineClearMultiplier *= burningMultiplier;
if (containsFrozenTile)
lineClearMultiplier *= 0.5f;
if (getMultiplier)
gm.SetScoreMultiplier(rowsCleared * rowsCleared, rowsCleared * 2);
if (rowsCleared > gm.safeEdgeTilesGained)
gm.AddSafeTileToEdges();
int normalRowsCleared = (rowsCleared - delayedLinesweepsCleared) - instantLinesweepsCleared;
bool isDifficultSweep = false;
//Debug.Log("Instant: " + instantLinesweepsCleared + ", Delayed: " + delayedLinesweepsCleared + ", Normal: " + normalRowsCleared + "... " + tetrominoThatJustLocked);
// Linesweep: Row was solved before the next tetromino was placed
if (instantLinesweepsCleared > 0)
{
// tetrominoThatJustLocked should be gm.tetrominoSpawner.currentTetromino
isDifficultSweep = gm.ScoreLinesweepHelper(instantLinesweepsCleared, tetrominoThatJustLocked, null, true, lineClearMultiplier, getMultiplier) || isDifficultSweep;
}
if (delayedLinesweepsCleared > 0)
{
isDifficultSweep = gm.ScoreLinesweepHelper(delayedLinesweepsCleared, gm.previousTetromino, gm.tetrominoSpawner.currentTetromino, false, lineClearMultiplier, getMultiplier) || isDifficultSweep;
}
if (normalRowsCleared > 0)
{
// Lines Cleared Points
float lineClearScore = 100 * normalRowsCleared;
lineClearScore *= lineClearMultiplier;
gm.AddScore((int)lineClearScore, scoreTranslationKeyLineClear, 1, "", "", "", true, true, false, rowsCleared); // TODO high clear
}
//Debug.Log("Is Difficult sweep? " + isDifficultSweep);
gm.previousClearWasDifficultSweep = isDifficultSweep;
}
return rowsCleared;
}
IEnumerator DeleteFullRowsDelayedHelper(float duration, int rowsCleared, int rowsFilled, int instantLinesweepsCleared, GameObject tetrominoThatJustLocked)
{
//Debug.Log("DeleteFullRowsDelayedHelper");
yield return new WaitForSecondsRealtime(duration);
Time.timeScale = 1f;
isHitstopPaused = false;
// Delete the finished Rows
for (int y = 0; y < sizeY; ++y)
{
if (isRowSolved(y))
{
deleteRow(y);
decreaseRowsAbove(y + 1);
--y;
}
}
if (tetrominoThatJustLocked != null)
tetrominoThatJustLocked.GetComponent<Group>().CascadeTetromino();
CheckForPerfectClear(rowsCleared, rowsFilled, instantLinesweepsCleared > 0);
GameManager.markSolvedRows();
// Did you win?
if (linesCleared >= linesClearedTarget && isEndless == false)
Pause(true, true);
}
private bool ScoreLinesweepHelper(int linesweepsCleared, GameObject tetrominoToCheckForSweeps1, GameObject tetrominoToCheckForSweeps2, bool isInstant, float lineClearMultiplier, bool getMultiplier)
{
string scoreTranslationKeyLinesweep = "";
string scoreTranslationKeyPrefix1 = "";
string scoreTranslationKeyPrefix2 = "";
float sweepMultiplier = lineClearMultiplier;
if (isInstant) // Instant Sweep multiplier
{
sweepMultiplier *= 1.5f;
scoreTranslationKeyPrefix2 = "Scoring Instant";
}
// Check for difficult sweeps, to find back-to-back. The below back-to-back bonus will also need to be applied inside the Tspinsweep separately.
bool isTetrisweep = false;
bool isTspinsweep = false;
bool isTetrisweep2 = false;
bool isTspinsweep2 = false;
if (tetrominoToCheckForSweeps1 != null)
(isTetrisweep, isTspinsweep) = tetrominoToCheckForSweeps1.GetComponent<Group>().CheckForTetrisweeps(getMultiplier, sweepMultiplier, isInstant, true);
if (tetrominoToCheckForSweeps2 != null)
(isTetrisweep2, isTspinsweep2) = tetrominoToCheckForSweeps2.GetComponent<Group>().CheckForTetrisweeps(getMultiplier, sweepMultiplier, isInstant, true);
//Debug.Log("Lines: " + linesweepsCleared + " isInstant: " + isInstant + "! ..... Previous (or Just Locked) Tetrisweep: " + isTetrisweep + ", Previous (or Just Locked) Tspinsweep: " + isTspinsweep + ", Current Tetrisweep: " + isTetrisweep2 + ", Current Tspinsweep: " + isTspinsweep2);
// Get Translation key...
switch (linesweepsCleared)
{
case 1:
scoreTranslationKeyLinesweep = "Scoring Linesweep";
break;
case 2:
scoreTranslationKeyLinesweep = "Scoring Duosweep";
break;
case 3:
scoreTranslationKeyLinesweep = "Scoring Triosweep";
break;
case 4:
//isTetrisweep = true;
scoreTranslationKeyLinesweep = "Scoring Tetrisweep";
break;
default: // Pentominos go here...
scoreTranslationKeyLinesweep = "Scoring Tetrisweep";
break;
}
bool isDifficultSweep = isTetrisweep || isTspinsweep || isTetrisweep2 || isTspinsweep2;
if (isDifficultSweep && previousClearWasDifficultSweep)
{
sweepMultiplier *= 1.5f;
scoreTranslationKeyPrefix1 = "Scoring Back-to-back";
}
// Linesweep Score...
float linesweepScore = 100 * (linesweepsCleared * linesweepsCleared);
linesweepScore *= sweepMultiplier;
// Add normal line clear score...
float lineClearScore = 100 * linesweepsCleared;
lineClearScore *= lineClearMultiplier;
AddScore(Mathf.FloorToInt(linesweepScore) + (int)lineClearScore, scoreTranslationKeyLinesweep, 1, scoreTranslationKeyPrefix1, scoreTranslationKeyPrefix2); // TODO high clear
if (getMultiplier)
SetScoreMultiplier(10 * linesweepsCleared, 10f);
// Tetrisweep Staggerred... Triggered when part of a Tetrisweep was scored as an instant sweep
if (isTetrisweep && linesweepsCleared < 4)
{
float tetrisweepStaggeredScore = 595 * sweepMultiplier;
AddScore((int)tetrisweepStaggeredScore, "Scoring Tetrisweep Staggered", 1, scoreTranslationKeyPrefix1, scoreTranslationKeyPrefix2); // TODO high clear
}
return isDifficultSweep;
}
public static void markSolvedRows()
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
if (gm.isGameOver)
return;
for (int y = 0; y < gm.sizeY; ++y)
{
if (isRowSolved(y))
{
for (int x = 0; x < gm.sizeX; ++x)
{
gm.GetGameTile(x, y).isRowSolved = true;
}
if (gm.GetGameTile(-1, y) != null)
gm.GetGameTile(-1, y).isRowSolved = true;
if (gm.GetGameTile(gm.sizeX, y) != null)
gm.GetGameTile(gm.sizeX, y).isRowSolved = true;
}
else
{
for (int x = 0; x < gm.sizeX; ++x)
{
if (gm.GetGameTile(x, y) != null)
{
gm.GetGameTile(x, y).isRowSolved = false;
}
}
if (gm.GetGameTile(-1, y) != null)
gm.GetGameTile(-1, y).isRowSolved = false;
if (gm.GetGameTile(gm.sizeX, y) != null)
gm.GetGameTile(gm.sizeX, y).isRowSolved = false;
}
}
}
public static bool isRowSolved(int y)
{
if (!isRowFull(y))
return false;
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
bool isSolved = true;
for (int x = 0; x < gm.sizeX; ++x)
{
if (gameBoard[x][y] != null)
{
Tile tile = gameBoard[x][y].GetComponent<Tile>();
if (!(tile.isRevealed && !tile.isMine)
&& !(!tile.isRevealed && tile.isMine && tile.isFlagged))
{
if (tile.aura != Tile.AuraType.frozen
&& !tile.burnoutInvisible)
isSolved = false;
}
}
}
return isSolved;
}
public void AddSafeTileToEdges()
{
if (gameMods.wallType == GameModifiers.WallType.unlock)
{
// Place display tiles at bottom
GameObject newTile = Instantiate(tile, new Vector3(-1, leftBorderTiles.Count-1, 0), new Quaternion(0, 0, 0, 0), this.gameObject.transform) as GameObject;
newTile.name = "Tile (" + -1 + ", " + (leftBorderTiles.Count-1) + ")";
newTile.GetComponent<Tile>().coordX = -1;
newTile.GetComponent<Tile>().coordY = leftBorderTiles.Count - 1;
newTile.GetComponent<Tile>().isRevealed = true;
newTile.GetComponent<Tile>().isDisplay = true;
if (safeEdgeTilesGained != -1)
newTile.GetComponentInChildren<Tile>().shimmerOverlay.gameObject.SetActive(true);
leftBorderTiles.Add(newTile);
newTile = Instantiate(tile, new Vector3(sizeX, rightBorderTiles.Count-1, 0), new Quaternion(0, 0, 0, 0), this.gameObject.transform) as GameObject;
newTile.name = "Tile (" + sizeX + ", " + (rightBorderTiles.Count-1) + ")";
newTile.GetComponent<Tile>().coordX = sizeX;
newTile.GetComponent<Tile>().coordY = rightBorderTiles.Count - 1;
newTile.GetComponent<Tile>().isRevealed = true;
newTile.GetComponent<Tile>().isDisplay = true;
if (safeEdgeTilesGained != -1)
newTile.GetComponentInChildren<Tile>().shimmerOverlay.gameObject.SetActive(true);
rightBorderTiles.Add(newTile);
if (safeEdgeTilesGained == 0)
{
GameObject.Find("Tile (-1, -1)").GetComponent<Tile>().shimmerOverlay.gameObject.SetActive(true);
GameObject.Find("Tile (" + sizeX + ", -1)").GetComponent<Tile>().shimmerOverlay.gameObject.SetActive(true);
}
safeEdgeTilesGained++;
}
}
/*public void RemoveSafeTileFromEdges()
{
if (safeEdgeTilesGained == 0)
return;
// Remove display tiles from the top
GameObject leftTile = leftBorderTiles[leftBorderTiles.Count-1];
leftBorderTiles.RemoveAt(leftBorderTiles.Count-1);
Destroy(leftTile);
GameObject rightTile = rightBorderTiles[rightBorderTiles.Count-1];
rightBorderTiles.RemoveAt(rightBorderTiles.Count-1);
Destroy(rightTile);
safeEdgeTilesGained--;
}*/
#endregion
#region Gamestate Logic
public void EndGame(string deathType = "")
{
if (cheatGodMode)
return;
if (isGameOver)
return;
endtime = GetTime();
isGameOver = true;
/*if (!canPause)
TriggerOnResetStartingPositionsEvent();*/
if (!isTitleMenu)
ScoreKeeper.Instance.SaveCurrentGame();
soundManager.DisablePauseFilter();
soundManager.StopMultiplierDrain();
if (OnGameOverEvent != null)
OnGameOverEvent();
// Reveal all tiles!
/*for (int i = -1; i <= sizeX; i++)
{
for (int j = -1; j < sizeY + 4; j++)
{
if (GetGameTile(i, j) != null)
{
//GetGameTile(i, j).isFlagged = false;
GetGameTile(i, j).Reveal(true);
}
}
}*/
if (!isTitleMenu)
GameObject.FindGameObjectWithTag("Audio").GetComponent<AudioSource>().Stop();
soundManager.PlayClip(gameOverSound, 0.35f, true);
StartCoroutine(GameOver(deathType));
}
IEnumerator GameOver(string deathType = "") // "" = normal, "Burn"
{
yield return new WaitForSeconds(1f);
if (!isTitleMenu)
{
if (deathType == "")
gameOverMenu.GetComponent<GameOverMenu>().SetDeathNormal();
else if (deathType.ToLower() == "burn")
gameOverMenu.GetComponent<GameOverMenu>().SetDeathBurn();
gameOverMenu.SetActive(true);
}
}
#endregion
#region Scoring
public void AddScore(int newScore, string scoreSourceName, int scoreSourceType, string translationKeyPrefix1 = "", string translationKeyPrefix2 = "", string translationKeySuffix = "", bool levelMultiplierActive = true, bool scoreMultiplierActive = true, bool combineExistingScoreFloatingText = false, int comboCountOverride = 1)
{
float tempScore = newScore;
if (scoreMultiplierActive)
if (GetScoreMultiplier() > 0)
tempScore = tempScore * (1 + GetScoreMultiplier());
if (levelMultiplierActive)
tempScore = tempScore * level;
tempScore = Mathf.Floor(tempScore);
scoreByPointSourceType[scoreSourceType] += tempScore; // 0=Block Placing, 1=Line Clearing, 2=Minesweeping, 3=Misc.
if (pieChart != null)
pieChart.SetValues(scoreByPointSourceType);
score += tempScore;
//floater.GetComponent<TextMeshProUGUI>().text = "+" + tempScore.ToString("#,#")
Transform scoreFloaterSpawnPoint = transform;
//GameObject floater = Instantiate(floatingText, new Vector3((sizeX / 2f) - 0.5f, (sizeY - 4) / 1.5f, 0), Quaternion.identity, guiCanvas.transform);
//floater.GetComponent<TextMeshProUGUI>().text = "+" + tempScore.ToString("#,#") + " " + "Placeholder Text";//GetTranslation("UIText", scoreSourceName);
if (floatingTextQueue != null && scoreSourceName != "")
{
if (scoreSourceName == "Scoring Tile Revealed")
floatingTextQueue.SpawnText(tempScore, scoreSourceName, combineExistingScoreFloatingText, translationKeyPrefix1, translationKeyPrefix2, translationKeySuffix, comboCountOverride, 10);
//floatingTextQueue.UpdateSingularFloater(newScore, scoreSourceName, translationKeyPrefix1, translationKeyPrefix2, translationKeySuffix, comboCountOverride);
else
floatingTextQueue.SpawnText(tempScore, scoreSourceName, combineExistingScoreFloatingText, translationKeyPrefix1, translationKeyPrefix2, translationKeySuffix, comboCountOverride);
}
if (score >= bestTodayStarting && !isTitleMenu && !gameMods.detailedTimer) // Normal score mode
{
if (!isHighScore && score >= highScoreStarting && highScoreStarting > 0 && !isEndless)
{
PlayFanfare("UIText", "GUI HighScore");
isHighScore = true;
isBestToday = true;
}
if (!isBestToday && bestTodayStarting > 0 && !isEndless)
{
PlayFanfare("UIText", "GUI HighScoreBestToday");
isBestToday = true;
}
if (!isHighScoreEndless && score >= highScoreEndlessStarting && highScoreEndlessStarting > highScoreStarting && isEndless)
{
PlayFanfare("UIText", "GUI HighScore");
isHighScoreEndless = true;
}
}
}
public void PlayFanfare(string translationTableName, string translationEntryName)
{
Vector3 spawnPoint = new Vector3((sizeX / 2f) - 0.5f, (sizeY - 4) / 1.5f, 0);
GameObject fanfareFloater = Instantiate(floatingText, spawnPoint, Quaternion.identity, guiCanvas.transform);
fanfareFloater.GetComponent<TextMeshProUGUI>().text = GetTranslation(translationTableName, translationEntryName) + "!";
fanfareFloater.GetComponent<FloatingText>().duration = 3;
Instantiate(confetti, spawnPoint, Quaternion.identity, guiCanvas.transform);
soundManager.PlayClip(fanfareSounds[Random.Range(0, fanfareSounds.Length)], 1, true);
}
public void SetScoreMultiplier(int mult, float duration, bool isSweep = false)
{
float sm = GetScoreMultiplier();
scoreMultiplier += mult;
if (scoreMultiplier < 0)
{
scoreMultiplier = 0;
soundManager.StopMultiplierDrain();
}
if (scoreMultiplier > scoreMultiplierLimit)
scoreMultiplier = scoreMultiplierLimit;
//Debug.Log(gameObject.name + ": " + sm + " + " + mult + " = " + scoreMultiplier + " -> " + GetScoreMultiplier());
//if (duration > scoreMultiplierTimer)
//scoreMultiplierTimer = duration;
float addDuration = 0.25f;
if (isSweep)
addDuration = 1f;
if (mult > 0)
{
if (scoreMultiplierTimer >= 0)
scoreMultiplierTimer += addDuration;
else
scoreMultiplierTimer = addDuration;
if (scoreMultiplierTimer > 16)
scoreMultiplierTimer = 16;
if (Mathf.FloorToInt(scoreMultiplierTimer) > scoreMultiplierTimerCountdown && Mathf.FloorToInt(scoreMultiplierTimer) >= 1)
scoreMultiplierTimerCountdown = Mathf.Min(Mathf.FloorToInt(scoreMultiplierTimer), 3);
soundManager.StopMultiplierDrain();
scoreMultiplierAtDrain = GetScoreMultiplier();
SetMultiplierBackground();
}
if (GetScoreMultiplier() > 0)
backgroundAnimated.SetActive(true);
if (GetScoreMultiplier() > highestScoreMultiplier)
highestScoreMultiplier = GetScoreMultiplier();
lastMultiplierTick = Time.time;
TriggerOnMultiplierEvent();
}
public void ResetScoreMultiplier()
{
//scoreMultiplier = 0;
scoreMultiplierTimer = 0;
if (scoreMultiplierTimerCountdown > 0)
scoreMultiplierTimerCountdown = 0;
}
public float GetScoreMultiplier()
{
//scoreMultiplier = Mathf.Floor(scoreMultiplier * 100) / 100;
return scoreMultiplier / 100f;
}
private void SetMultiplierBackground()
{
//Debug.Log(GetScoreMultiplier() + " / " + scoreMultiplierAtDrain + " = " + Mathf.Clamp(GetScoreMultiplier() / scoreMultiplierAtDrain, 0, 1));
float tweenedAlpha = Mathf.SmoothStep(0.25f, 1, GetScoreMultiplier() / scoreMultiplierAtDrain);
backgroundAnimated.GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, tweenedAlpha);
foreach (SpriteRenderer bg in backgroundAnimated.GetComponentsInChildren<SpriteRenderer>())
{
bg.color = new Color(1, 1, 1, tweenedAlpha);
}
}
// Returns true if this row was a linesweep
public static (bool, bool, float, bool) scoreSolvedRow(int y, bool getMultiplier, GameObject tetrominoThatJustLocked)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
//int rowScore = 0;
int minesFlagged = 0;
bool isDelayedLinesweep = false;
bool isInstantLinesweep = false;
float burningMultiplier = 0f;
bool containsFrozenTile = false;
for (int x = 0; x < gm.sizeX; ++x)
{
if (gameBoard[x][y] != null)
{
gameBoard[x][y].GetComponent<ButtonJiggle>().ShrinkToZero(false);
gameBoard[x][y].GetComponent<Tile>().isMarkCleared = true;
if (gameBoard[x][y].GetComponent<Tile>().isMine)
{
//rowScore += 50;
minesFlagged += 1;
}
/*else
{
rowScore += gameBoard[x][y].GetComponent<Tile>().nearbyMines;
}*/
if (gameBoard[x][y].GetComponent<Tile>().aura == Tile.AuraType.burning)
burningMultiplier += 0.25f;
if (gameBoard[x][y].GetComponent<Tile>().aura == Tile.AuraType.frozen)
containsFrozenTile = true;
if (gameBoard[x][y].GetComponentInParent<Group>().gameObject == gm.previousTetromino)
isDelayedLinesweep = true;
if (gameBoard[x][y].GetComponentInParent<Group>().gameObject == tetrominoThatJustLocked)
{
isInstantLinesweep = true;
isDelayedLinesweep = false; // These shouldn't actually both happen at the same time anyways...
}
}
}
//gm.AddScore(50 * (y + 1));
//gm.SetScoreMultiplier(0.2f * (y + 1), 2f);
//if (getMultiplier)
//gm.SetScoreMultiplier(0.5f, 2f);
gm.currentMines -= minesFlagged;
gm.currentFlags -= minesFlagged;
gm.minesSweeped += minesFlagged;
return (isDelayedLinesweep, isInstantLinesweep, burningMultiplier, containsFrozenTile);
}
public static int scoreFullRows(Transform tetronimo)
{
GameManager gm = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
if (gm.isGameOver)
return 0;
HashSet<int> rowsToCheck = new HashSet<int>();
foreach (Transform child in tetronimo.transform)
{
if (child.gameObject.GetComponent<Tile>() != null)
{
rowsToCheck.Add(child.gameObject.GetComponent<Tile>().coordY);
}
}
int fullRows = 0;
foreach (int row in rowsToCheck)
{
if (isRowFull(row))
{
fullRows++;
}
}
if (fullRows > 0)
{
AudioClip clipToPlay = null;
switch (fullRows) {
case 1:
clipToPlay = gm.lineFullSound1;
gm.AddScore(100, "Scoring Line-Fill", 0);
gm.SetScoreMultiplier(3, 5f);
break;
case 2:
clipToPlay = gm.lineFullSound2;
gm.AddScore(300, "Scoring Double-Fill", 0);
gm.SetScoreMultiplier(5, 5f);
break;
case 3:
clipToPlay = gm.lineFullSound3;
gm.AddScore(500, "Scoring Triple-Fill", 0);
gm.SetScoreMultiplier(8, 5f);
break;
default:
clipToPlay = gm.lineFullSound4;
int actionScore = 800;
if (gm.lastFillWasDifficult)
{
gm.AddScore(Mathf.RoundToInt(actionScore * 1.5f), "Scoring Tetri-Fill", 0, "Scoring Back-to-back");
gm.SetScoreMultiplier(20, 5f);
}
else
{
gm.AddScore(actionScore, "Scoring Tetri-Fill", 0);
gm.SetScoreMultiplier(10, 5f);
}
break;
}
// C-c-c-Combo!
if (gm.comboLinesFilled > 0)
gm.AddScore(50 * gm.comboLinesFilled, "Scoring Combo: Line Filled", 0, "", "", "", true, true, true, gm.comboLinesFilled + 1);
gm.soundManager.PlayClip(clipToPlay, 1, true);
}
return fullRows;
}
public void AddRevealCombo(bool isAutomatic, bool isFailedChord, int rowHeight)
{
//ResetRevealComboDrain();
revealCombo++;
float revealScore = revealCombo;
if (isAutomatic)
revealScore *= 0.1f;
if (isFailedChord)
revealScore *= 0.1f;
revealScore *= GetRowHeightPointModifier(rowHeight);
AddScore(Mathf.FloorToInt(10 * revealScore), "Scoring Tile Revealed", 2, "", "", "", true, true, true, revealCombo); // TODO automatic, failed, high reveal...
}
public void ResetRevealCombo()
{
/*if (revealComboDrainTween != null)
if (revealComboDrainTween.IsActive())
if (revealComboDrainTween.IsPlaying())
return;
revealComboDrainTween = DOTween.To(()=>revealCombo, x=> revealCombo = x, 0, 31f);*/
revealCombo = 0;
if (floatingTextQueue != null)
floatingTextQueue.DisableCombineExistingScoresOnFloater("Scoring Tile Revealed");
}
public void AddRevealStreakManual()
{
revealStreakManual++;
if (revealStreakManual >= 50)
{
AddScore(2500, "Scoring Manual Reveal Streak (50 tiles)", 2);
SetScoreMultiplier(10, 10, true);
soundManager.PlayClip(revealStreakManual100Sound, 1, true);
ResetRevealStreakManual();
}
if (revealStreakManualProgressBar != null)
revealStreakManualProgressBar.current = revealStreakManual;
}
public void ResetRevealStreakManual()
{
revealStreakManual = 0;
if (revealStreakManualProgressBar != null)
revealStreakManualProgressBar.current = 0;
}
/*public void ResetRevealComboDrain()
{
if (revealComboDrainTween != null)
if (revealComboDrainTween.IsActive())
if (revealComboDrainTween.IsPlaying())
{
revealComboDrainTween.Kill();
revealComboDrainTween = null;
}
}*/
#endregion
#region Helper Functions
public void ReloadScene()
{
Time.timeScale = 1;
DOTween.Clear(true);
//DOTween.KillAll();
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
public void QuitToTitleMenu ()
{
Time.timeScale = 1;
SceneManager.LoadScene("Title");
}
public void Quit()
{
if (Application.platform != RuntimePlatform.WebGLPlayer)
Application.Quit();
hasQuit = true;
}
public void PauseFromButton(bool pause)
{
Pause(pause, false, true);
}
public void Pause(bool pause, bool isMarathonOverPause = false, bool bypassTitlePause = false)
{
if (isGameOver && !isTitleMenu)
return;
/*if (isTitleMenu && !bypassTitlePause)
return;*/
if (pause && !canPause)
return;
if (pause)
{
Time.timeScale = 0;
isPaused = true;
if (!isTitleMenu)
canPause = false;
TriggerOnKillTweenEvent();
if (isMarathonOverPause)
{
GameWin();
}
else
{
if (isTitleMenu)
{
settingsMenu.SetActive(true);
demoTitleScreen.gameObject.SetActive(false);
}
else
pauseMenu.SetActive(true);
soundManager.EnablePauseFilter();
}
}
else
{
settingsMenu.SetActive(false);
if (isTitleMenu)
demoTitleScreen.gameObject.SetActive(true);
if (!isGameOver)
{
Time.timeScale = 1;
isPaused = false;
pauseMenu.SetActive(false);
soundManager.DisablePauseFilter();
marathonOverMenu.SetActive(false);
/*if (!bypassTitlePause)
settingsMenu.SetActive(false);*/
StartCoroutine(ResetPause());
}
}
}
private void GameWin()
{
isEndless = true;
marathonOverMenu.SetActive(true);
if (!isTitleMenu)
{
ScoreKeeper.Instance.SaveCurrentGame();
if (gameMods.detailedTimer)
{
Debug.Log(GetTime() + ": ...best? " + ScoreKeeper.Instance.bestTimeToday + " isEndless=" + isEndless);
if (ScoreKeeper.Instance.bestTime == GetTime())
{
Debug.Log("Best Time! " + GetTime());
GameObject floater = Instantiate(floatingText, new Vector3((sizeX / 2f) - 0.5f, (sizeY - 4) / 1.4f, 0), Quaternion.identity, guiCanvas.transform);
floater.GetComponent<TextMeshProUGUI>().text = GetTranslation("UIText", "GUI HighScore") + "!";
floater.GetComponent<FloatingText>().duration = 3f;
soundManager.PlayClip(fanfareSounds[Random.Range(0, fanfareSounds.Length)], 1, true);
}
else if (ScoreKeeper.Instance.bestTimeToday == GetTime() && ScoreKeeper.Instance.runs > 1)
{
Debug.Log("Best Time Today! " + GetTime());
soundManager.PlayClip(fanfareSounds[Random.Range(0, fanfareSounds.Length)], 1, true);
GameObject floater = Instantiate(floatingText, new Vector3((sizeX / 2f) - 0.5f, (sizeY - 4) / 1.4f, 0), Quaternion.identity, guiCanvas.transform);
floater.GetComponent<TextMeshProUGUI>().text = GetTranslation("UIText", "GUI HighScoreBestToday") + "!";
floater.GetComponent<FloatingText>().duration = 3f;
}
}
}
}
IEnumerator ResetPause()
{
yield return new WaitForSeconds(0.4f);
//TriggerOnResetStartingPositionsEvent();
canPause = true;
}
public void Resume()
{
Pause(false);
}
public float GetTime()
{
if (isGameOver)
return endtime;
if (!isStarted)
return 0;
return Time.time - startTime;
}
public float GetScore()
{
return score;
}
public static Vector2 roundVec2(Vector2 v)
{
return new Vector2(Mathf.Round(v.x), Mathf.Round(v.y));
}
public static string GetTranslation(string tableName, string entryName, int depth = 0)
{
StringTable table = LocalizationSettings.StringDatabase.GetTableAsync(tableName).Result;
if (table == null)
{
//Debug.Log(entryName + ", " + depth);
if (depth > 10)
return entryName;
return GetTranslation(tableName, entryName, depth + 1);
}
else
if (table.GetEntry(entryName) != null)
return table.GetEntry(entryName).GetLocalizedString();
return "";
//[entryName].LocalizedValue;
/*#if !UNITY_EDITOR && UNITY_WEBGL
return LocalizationSettings.StringDatabase.GetTable(tableName)[entryName].LocalizedValue;
#endif
return LocalizationSettings.StringDatabase.GetLocalizedString(tableName, entryName);*/
}
#endregion
#region Extra Helpers
public void SetGridOpacity()
{
Color gridColor = backgroundGrid.GetComponent<SpriteRenderer>().color;
backgroundGrid.GetComponent<SpriteRenderer>().color = new Color(gridColor.r, gridColor.g, gridColor.b, PlayerPrefs.GetFloat("GridOpacity", 0));
}
#endregion
#region Event Triggers
public void TriggerOnHardDropEvent()
{
if (OnHardDropEvent != null)
OnHardDropEvent();
}
public void TriggerOnLeftStuckEvent()
{
if (OnLeftStuckEvent != null)
OnLeftStuckEvent();
}
public void TriggerOnRightStuckEvent()
{
if (OnRightStuckEvent != null)
OnRightStuckEvent();
}
public void TriggerOnMinoLockEvent()
{
if (OnMinoLockEvent != null)
OnMinoLockEvent();
}
public void TriggerOnTileSolveOrLandEvent()
{
if (OnTileSolveOrLandEvent != null)
OnTileSolveOrLandEvent();
}
public void TriggerOnTSpinEvent(int dir)
{
if (OnTSpinEvent != null)
OnTSpinEvent(dir);
}
public void TriggerOnResetStartingPositionsEvent()
{
if (OnResetStartingPositionsEvent != null)
OnResetStartingPositionsEvent();
}
public void TriggerOnKillTweenEvent()
{
if (OnKillTweenEvent != null)
OnKillTweenEvent();
}
public void TriggerOnNewPieceEvent()
{
if (OnNewPieceEvent != null)
OnNewPieceEvent();
}
public void TriggerOnMultiplierEvent()
{
if (OnMultiplierEvent != null)
OnMultiplierEvent();
}
public float GetRowHeightPointModifier(int rowHeight)
{
float adjustedHeight = (rowHeight / 20f) * (sizeY - 4);
if (adjustedHeight > 16)
return 1.75f;
else if (adjustedHeight > 11)
return 1.5f;
else if (adjustedHeight > 6)
return 1.25f;
return 1;
}
#endregion
}