public override void HandleGameKey(IStateOwner pOwner, GameKeys g) { if (HandleBlockGroupKey(pOwner, g)) { return; } if (g == GameKeys.GameKey_RotateCW || g == GameKeys.GameKey_RotateCCW) { bool ccw = g == GameKeys.GameKey_RotateCCW; foreach (var activeitem in PlayField.BlockGroups) { if (!activeitem.Controllable) { continue; } if (PlayField.CanRotate(activeitem, ccw)) { PerformRotation(pOwner, activeitem, ccw); } else if (this.GameOptions.AllowWallKicks) { //we will add up to 3 and subtract up to 3 to the X coordinate. if any say we can rotate then we proceed with allowing the rotation. int[] checkoffsets = new int[] { 1, -1, 2, -2, 3, -3 }; int OriginalPos = activeitem.X; Boolean revertpos = true; foreach (int currentoffset in checkoffsets) { if (currentoffset == 0) { continue; } activeitem.X = OriginalPos + currentoffset; if (PlayField.CanRotate(activeitem, ccw)) { PerformRotation(pOwner, activeitem, ccw); revertpos = false; break; } } if (revertpos) { activeitem.X = OriginalPos; } } } } else if (g == GameKeys.GameKey_Down) { HandleActiveGroups(pOwner, true); } else if (g == GameKeys.GameKey_Drop) { //drop all active groups. Nomino FirstGroup = PlayField.BlockGroups.FirstOrDefault(); if (FirstGroup != null) { //store the block positions for each block in the nomino. foreach (var activeitem in PlayField.BlockGroups) { if (!activeitem.Controllable) { continue; } List <Tuple <BCPoint, NominoElement> > StartBlockPositions = new List <Tuple <BCPoint, NominoElement> >(); List <Tuple <BCPoint, NominoElement> > EndBlockPositions = new List <Tuple <BCPoint, NominoElement> >(); foreach (var element in activeitem) { StartBlockPositions.Add(new Tuple <BCPoint, NominoElement>(new BCPoint(activeitem.X + element.X, activeitem.Y + element.Y), element)); } int dropqty = 0; var ghosted = GetGhostDrop(pOwner, activeitem, out dropqty, 0); foreach (var element in ghosted) { EndBlockPositions.Add(new Tuple <BCPoint, NominoElement>(new BCPoint(ghosted.X + element.X, ghosted.Y + element.Y), element)); } GenerateDropParticles(StartBlockPositions, EndBlockPositions); activeitem.X = ghosted.X; activeitem.SetY(pOwner, ghosted.Y); PlayField.SetGroupToField(activeitem); PlayField.RemoveBlockGroup(activeitem); if (GameStats is TetrisStatistics ts) { GameStats.AddScore((dropqty * (5 + (ts.LineCount / 10)))); } } pOwner.Feedback(0.6f, 200); Sounds.PlaySound(pOwner.AudioThemeMan.BlockGroupPlace.Key, pOwner.Settings.std.EffectVolume); GameHandler.ProcessFieldChange(this, pOwner, FirstGroup); //ProcessFieldChangeWithScore(pOwner, FirstGroup); } } else if (g == GameKeys.GameKey_Right || g == GameKeys.GameKey_Left) { int XMove = g == GameKeys.GameKey_Right ? 1 : -1; foreach (var ActiveItem in PlayField.BlockGroups) { if (!ActiveItem.Controllable) { continue; } if (PlayField.CanFit(ActiveItem, ActiveItem.X + XMove, ActiveItem.Y, false) == TetrisField.CanFitResultConstants.CanFit) { lastHorizontalMove = DateTime.Now; ActiveItem.X += XMove; Sounds.PlaySound(pOwner.AudioThemeMan.BlockGroupMove.Key, pOwner.Settings.std.EffectVolume); pOwner.Feedback(0.1f, 50); } else { Sounds.PlaySound(pOwner.AudioThemeMan.BlockStopped.Key, pOwner.Settings.std.EffectVolume); pOwner.Feedback(0.4f, 75); } } } else if (g == GameKeys.GameKey_Pause) { if (g == GameKeys.GameKey_Pause) { pOwner.LastPausedTime = DateTime.Now; pOwner.CurrentState = new PauseGameState(pOwner, this); var playing = Sounds.GetPlayingMusic_Active(); playing?.Pause(); Sounds.PlaySound(pOwner.AudioThemeMan.Pause.Key, pOwner.Settings.std.EffectVolume); } //pOwner.CurrentState = new PauseGameState(this); } else if (g == GameKeys.GameKey_Hold) { if (HoldBlock != null && !BlockHold) { //if there is a holdblock, take it and put it into the gamefield and make the first active blockgroup the new holdblock, //then set BlockHold to block it from being used until the next Tetromino is spawned. Nomino FirstGroup = PlayField.BlockGroups.FirstOrDefault(); if (FirstGroup != null) { if (FirstGroup.Controllable) { PlayField.RemoveBlockGroup(FirstGroup); PlayField.AddBlockGroup(HoldBlock); //We probably should set the speed appropriately here for the level. As is it will retain the speed from whe nthe hold block was //held. PlayField.Theme.ApplyTheme(HoldBlock, GameHandler, PlayField, NominoTheme.ThemeApplicationReason.Normal); HoldBlock.X = (int)(((float)PlayField.ColCount / 2) - ((float)HoldBlock.GroupExtents.Width / 2)); HoldBlock.SetY(pOwner, 0); HoldBlock.HighestHeightValue = 0; //reset the highest height as well, so the falling animation doesn't goof HoldBlock = FirstGroup; Sounds.PlaySound(pOwner.AudioThemeMan.Hold.Key, pOwner.Settings.std.EffectVolume); pOwner.Feedback(0.9f, 40); BlockHold = true; } } } else if (!BlockHold) { Nomino FirstGroup = PlayField.BlockGroups.FirstOrDefault(); if (FirstGroup != null) { if (FirstGroup.Controllable) { PlayField.RemoveBlockGroup(FirstGroup); HoldBlock = FirstGroup; BlockHold = true; Sounds.PlaySound(pOwner.AudioThemeMan.Hold.Key, pOwner.Settings.std.EffectVolume); } } } } else if (g == GameKeys.GameKey_Debug1) { pOwner.CurrentState = new ShowHighScoresState(TetrisGame.ScoreMan["Standard"], pOwner.CurrentState); } else if (g == GameKeys.GameKey_Debug2) { OptionsMenuSettingsSelectorState OptionState = new OptionsMenuSettingsSelectorState(BackgroundDrawers.StandardImageBackgroundGDI.GetStandardBackgroundDrawer(), pOwner, pOwner.CurrentState); pOwner.CurrentState = OptionState; /*if (pOwner.CurrentState is StandardTetrisGameState) * { * ((StandardTetrisGameState) pOwner.CurrentState).GameStats.Score += 1000; * }*/ } else if (g == GameKeys.GameKey_Debug3) { int DesiredFontPixelHeight = (int)(pOwner.GameArea.Height * (23d / 644d)); Font standardFont = new Font(TetrisGame.RetroFont, DesiredFontPixelHeight, FontStyle.Bold, GraphicsUnit.Pixel); Font ItemFont = new Font(TetrisGame.RetroFont, (int)((float)DesiredFontPixelHeight * (3f / 4f)), FontStyle.Bold, GraphicsUnit.Pixel); //set state to a testing menu state. MenuState ms = new MenuState(BackgroundDrawers.StandardImageBackgroundGDI.GetStandardBackgroundDrawer()); ms.StateHeader = "This is a Menu"; ms.HeaderTypeface = standardFont.Name; ms.HeaderTypeSize = standardFont.Size; MenuStateTextMenuItem returnitem = new MenuStateTextMenuItem(); returnitem.FontFace = ItemFont.Name; returnitem.FontSize = ItemFont.Size; returnitem.Text = "Return"; returnitem.BackColor = Color.Transparent; returnitem.ForeColor = Color.DarkBlue; var OriginalState = pOwner.CurrentState; ms.MenuElements.Add(returnitem); var scaleitem = new MenuStateScaleMenuItem(pOwner); scaleitem.FontFace = ItemFont.Name; scaleitem.FontSize = ItemFont.Size; ms.MenuElements.Add(scaleitem); ms.MenuItemActivated += (obj, e) => { if (e.MenuElement == returnitem) { pOwner.CurrentState = OriginalState; } }; for (int i = 0; i < 8; i++) { MenuStateTextMenuItem mts = new MenuStateTextMenuItem(); mts.FontFace = ItemFont.Name; mts.FontSize = ItemFont.Size; mts.BackColor = Color.Transparent; mts.ForeColor = Color.Black; mts.Text = "Item " + i.ToString(); ms.MenuElements.Add(mts); } pOwner.CurrentState = ms; } else if (g == GameKeys.GameKey_Debug4) { if (pOwner is IGamePresenter gp) { var _Present = gp.GetPresenter(); if (_Present.ai == null) { _Present.ai = new StandardNominoAI(pOwner); } else { _Present.ai.AbortAI(); _Present.ai = null; } } } }