diff --git a/Ambermoon.Core/Battle.cs b/Ambermoon.Core/Battle.cs index 614359d1..954dfd8e 100644 --- a/Ambermoon.Core/Battle.cs +++ b/Ambermoon.Core/Battle.cs @@ -1352,7 +1352,8 @@ void HurtAnimationFinished() ShowBattleFieldDamage((int)tile, damage); if (target.Ailments.HasFlag(Ailment.Sleep)) game.RemoveAilment(Ailment.Sleep, target); - target.Damage(damage); + if (!game.Godmode || target is Monster) + target.Damage(damage); return; } default: @@ -1852,7 +1853,8 @@ void EndHurt() } if (target.Ailments.HasFlag(Ailment.Sleep)) RemoveAilment(Ailment.Sleep, target); - target.Damage(damage); + if (!game.Godmode || target is Monster) + target.Damage(damage); EndHurt(); } ); @@ -2703,15 +2705,13 @@ AttackResult ProcessAttack(Character attacker, int attackedSlot, out int damage, int CalculatePhysicalDamage(Character attacker, Character target) { // TODO: how is damage calculated? - return attacker.BaseAttack + game.RandomInt(0, attacker.VariableAttack) - (target.BaseDefense + game.RandomInt(0, target.VariableDefense)); ; + return Math.Min((int)target.HitPoints.TotalCurrentValue, attacker.BaseAttack + game.RandomInt(0, attacker.VariableAttack) - (target.BaseDefense + game.RandomInt(0, target.VariableDefense))); } uint CalculateSpellDamage(Character caster, Character target, uint baseDamage, uint variableDamage) { - // TODO: how is magic damage calculated? - // TODO: does INT affect damage? // Note: In contrast to physical attacks this should always deal at least 1 damage - return baseDamage + (uint)game.RandomInt(0, (int)variableDamage); // TODO + return Math.Min(target.HitPoints.TotalCurrentValue, baseDamage + (uint)game.RandomInt(0, (int)variableDamage)); } } diff --git a/Ambermoon.Core/Game.cs b/Ambermoon.Core/Game.cs index 349f694e..4fe4e4e1 100644 --- a/Ambermoon.Core/Game.cs +++ b/Ambermoon.Core/Game.cs @@ -174,6 +174,11 @@ enum CharacterInfo public FloatPosition ViewportOffset { get; private set; } = null; readonly bool legacyMode = false; public event Action QuitRequested; + public bool Godmode + { + get; + set; + } = false; bool ingame = false; bool is3D = false; internal bool WindowActive => currentWindow.Window != Window.MapView; @@ -198,9 +203,9 @@ enum CharacterInfo readonly Layout layout; readonly Dictionary characterInfoTexts = new Dictionary(); readonly Dictionary characterInfoPanels = new Dictionary(); - internal IMapManager MapManager { get; } - internal IItemManager ItemManager { get; } - internal ICharacterManager CharacterManager { get; } + public IMapManager MapManager { get; } + public IItemManager ItemManager { get; } + public ICharacterManager CharacterManager { get; } readonly Places places; readonly IRenderView renderView; internal ISavegameManager SavegameManager { get; } @@ -1885,7 +1890,7 @@ void ScrollCursor(Position cursorPosition, bool down) } } - internal IEnumerable PartyMembers => Enumerable.Range(0, MaxPartyMembers) + public IEnumerable PartyMembers => Enumerable.Range(0, MaxPartyMembers) .Select(i => GetPartyMember(i)).Where(p => p != null); internal PartyMember GetPartyMember(int slot) => CurrentSavegame.GetPartyMember(slot); internal Chest GetChest(uint index) => CurrentSavegame.Chests[(int)index]; @@ -2622,35 +2627,48 @@ void Fade(Action midFadeAction) AddTimedEvent(TimeSpan.FromMilliseconds(FadeTime), () => allInputDisabled = false); } - internal void Teleport(MapChangeEvent mapChangeEvent) + /// + /// This is used by external triggers like a cheat engine. + /// + public bool Teleport(uint mapIndex, uint x, uint y, CharacterDirection direction) { - Fade(() => + if (WindowActive || BattleActive || layout.PopupActive || !ingame) + return false; + + var newMap = MapManager.GetMap(mapIndex); + bool mapChange = newMap.Index != Map.Index; + var player = is3D ? (IRenderPlayer)player3D : player2D; + bool mapTypeChanged = Map.Type != newMap.Type; + + // The position (x, y) is 1-based in the data so we subtract 1. + // Moreover the players position is 1 tile below its drawing position + // in non-world 2D so subtract another 1 from y. + player.MoveTo(newMap, x - 1, + y - (newMap.Type == MapType.Map2D && !newMap.IsWorldMap ? 2u : 1u), + CurrentTicks, true, direction); + this.player.Position.X = RenderPlayer.Position.X; + this.player.Position.Y = RenderPlayer.Position.Y; + + if (!mapTypeChanged) { - var newMap = MapManager.GetMap(mapChangeEvent.MapIndex); - bool mapChange = newMap.Index != Map.Index; - var player = is3D ? (IRenderPlayer)player3D : player2D; - bool mapTypeChanged = Map.Type != newMap.Type; + // Trigger events after map transition + TriggerMapEvents(EventTrigger.Move, (uint)player.Position.X, + (uint)player.Position.Y + (Map.IsWorldMap || is3D ? 0u : 1u)); - // The position (x, y) is 1-based in the data so we subtract 1. - // Moreover the players position is 1 tile below its drawing position - // in non-world 2D so subtract another 1 from y. - player.MoveTo(newMap, mapChangeEvent.X - 1, - mapChangeEvent.Y - (newMap.Type == MapType.Map2D && !newMap.IsWorldMap ? 2u : 1u), - CurrentTicks, true, mapChangeEvent.Direction); - this.player.Position.X = RenderPlayer.Position.X; - this.player.Position.Y = RenderPlayer.Position.Y; + PlayerMoved(mapChange); + } - if (!mapTypeChanged) - { - // Trigger events after map transition - TriggerMapEvents(EventTrigger.Move, (uint)player.Position.X, - (uint)player.Position.Y + (Map.IsWorldMap || is3D ? 0u : 1u)); + if (mapChange) + UpdateMapName(); - PlayerMoved(mapChange); - } + return true; + } - if (mapChange) - UpdateMapName(); + internal void Teleport(MapChangeEvent mapChangeEvent) + { + Fade(() => + { + Teleport(mapChangeEvent.MapIndex, mapChangeEvent.X, mapChangeEvent.Y, mapChangeEvent.Direction); }); } @@ -2793,10 +2811,13 @@ static uint CalculateDamage(PartyMember partyMember) if (damage != 0) { // TODO: show damage splash - partyMember.Damage(damage); + if (!Godmode) + { + partyMember.Damage(damage); - if (partyMember.Alive) // update HP etc if not died already - layout.SetCharacter(i, partyMember); + if (partyMember.Alive) // update HP etc if not died already + layout.SetCharacter(i, partyMember); + } } } } @@ -3189,6 +3210,22 @@ void HandleNextEvent() }); } + /// + /// This is used by external triggers like a cheat engine. + /// + /// Returns false if the current game state does not allow + /// to start a fight. + /// + public bool StartBattle(uint monsterGroupIndex) + { + if (WindowActive || BattleActive || layout.PopupActive || + allInputDisabled || !inputEnable || !ingame) + return false; + + StartBattle(monsterGroupIndex, false, null); + return true; + } + /// /// Starts a battle with the given monster group index. /// It is used for monsters that are present on the map. diff --git a/Ambermoon.Data.Common/ICharacterManager.cs b/Ambermoon.Data.Common/ICharacterManager.cs index 637b5276..03c24151 100644 --- a/Ambermoon.Data.Common/ICharacterManager.cs +++ b/Ambermoon.Data.Common/ICharacterManager.cs @@ -1,10 +1,13 @@ -namespace Ambermoon.Data +using System.Collections.Generic; + +namespace Ambermoon.Data { public interface ICharacterManager { NPC GetNPC(uint index); Monster GetMonster(uint index); MonsterGroup GetMonsterGroup(uint index); - Monster[] Monsters { get; } + IReadOnlyList Monsters { get; } + IReadOnlyDictionary MonsterGroups { get; } } } diff --git a/Ambermoon.Data.Common/IItemManager.cs b/Ambermoon.Data.Common/IItemManager.cs index 4b59d715..f87b8ab2 100644 --- a/Ambermoon.Data.Common/IItemManager.cs +++ b/Ambermoon.Data.Common/IItemManager.cs @@ -1,7 +1,10 @@ -namespace Ambermoon.Data +using System.Collections.Generic; + +namespace Ambermoon.Data { public interface IItemManager { + IReadOnlyList Items { get; } Item GetItem(uint index); } } diff --git a/Ambermoon.Data.Common/IMapManager.cs b/Ambermoon.Data.Common/IMapManager.cs index f119d3ea..214fcf43 100644 --- a/Ambermoon.Data.Common/IMapManager.cs +++ b/Ambermoon.Data.Common/IMapManager.cs @@ -1,7 +1,10 @@ -namespace Ambermoon.Data +using System.Collections.Generic; + +namespace Ambermoon.Data { public interface IMapManager { + IReadOnlyList Maps { get; } Map GetMap(uint index); Tileset GetTilesetForMap(Map map); Labdata GetLabdataForMap(Map map); diff --git a/Ambermoon.Data.Legacy/Characters/CharacterManager.cs b/Ambermoon.Data.Legacy/Characters/CharacterManager.cs index dbab60a2..888355fa 100644 --- a/Ambermoon.Data.Legacy/Characters/CharacterManager.cs +++ b/Ambermoon.Data.Legacy/Characters/CharacterManager.cs @@ -29,6 +29,7 @@ public CharacterManager(IGameData gameData, IGraphicProvider graphicProvider) public MonsterGroup GetMonsterGroup(uint index) => index == 0 || !monsterGroups.ContainsKey(index) ? null : monsterGroups[index]; - public Monster[] Monsters => monsters.Values.ToArray(); + public IReadOnlyList Monsters => monsters.Values.ToList(); + public IReadOnlyDictionary MonsterGroups => monsterGroups; } } diff --git a/Ambermoon.Data.Legacy/ItemManager.cs b/Ambermoon.Data.Legacy/ItemManager.cs index 1ecf5975..e8b61777 100644 --- a/Ambermoon.Data.Legacy/ItemManager.cs +++ b/Ambermoon.Data.Legacy/ItemManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace Ambermoon.Data.Legacy { @@ -12,5 +13,6 @@ internal ItemManager(Dictionary items) } public Item GetItem(uint index) => items[index]; + public IReadOnlyList Items => items.Values.ToList(); } } diff --git a/Ambermoon.Data.Legacy/MapManager.cs b/Ambermoon.Data.Legacy/MapManager.cs index adf6d1d7..a8e718bb 100644 --- a/Ambermoon.Data.Legacy/MapManager.cs +++ b/Ambermoon.Data.Legacy/MapManager.cs @@ -1,5 +1,6 @@ using Ambermoon.Data.Serialization; using System.Collections.Generic; +using System.Linq; namespace Ambermoon.Data.Legacy { @@ -9,6 +10,8 @@ public class MapManager : IMapManager readonly Dictionary tilesets = new Dictionary(8); readonly Dictionary labdatas = new Dictionary(29); + public IReadOnlyList Maps => maps.Values.ToList(); + public MapManager(IGameData gameData, IMapReader mapReader, ITilesetReader tilesetReader, ILabdataReader labdataReader) { foreach (var tilesetFile in gameData.Files["Icon_data.amb"].Files) @@ -44,7 +47,5 @@ public MapManager(IGameData gameData, IMapReader mapReader, ITilesetReader tiles public Map GetMap(uint index) => maps[index]; public Tileset GetTilesetForMap(Map map) => tilesets[map.TilesetOrLabdataIndex]; public Labdata GetLabdataForMap(Map map) => labdatas[map.TilesetOrLabdataIndex]; - - public IEnumerable Maps => maps.Values; } } diff --git a/Ambermoon.net/Ambermoon.net.csproj b/Ambermoon.net/Ambermoon.net.csproj index 99477f73..c2c9a3de 100644 --- a/Ambermoon.net/Ambermoon.net.csproj +++ b/Ambermoon.net/Ambermoon.net.csproj @@ -5,7 +5,7 @@ netcoreapp3.1 Ambermoon win-x86;win-x64;linux-x64 - 0.2.2 + 0.2.3 Copyright (C) 2020-2021 by Robert Schneckenhaus Robert Schneckenhaus Robert Schneckenhaus diff --git a/Ambermoon.net/Cheats.cs b/Ambermoon.net/Cheats.cs new file mode 100644 index 00000000..0b17d6cb --- /dev/null +++ b/Ambermoon.net/Cheats.cs @@ -0,0 +1,476 @@ +using Ambermoon.Data; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ambermoon +{ + class Cheats + { + static Dictionary>> cheats = + new Dictionary>> + { + { "help", + Create + ( + "Shows general help or cheat help." + Environment.NewLine + + "Usage: help [cheat]", + Help + ) + }, + { "godmode", + Create + ( + "Makes the party invulnerable." + Environment.NewLine + + "Usage: godmode [0/1]", + Godmode + ) + }, + { "netsrak", + Create + ( + "Maxes all stats and grants all spells and languages." + Environment.NewLine + + "Usage: netsrak", + Netsrak + ) + }, + { "maps", + Create + ( + "Shows a list of all maps." + Environment.NewLine + + "Usage: maps", + ShowMaps + ) + }, + { "teleport", + Create + ( + "Makes the party invulnerable." + Environment.NewLine + + "Usage: teleport [x] [y] [direction]", + Teleport + ) + }, + { "monsters", + Create + ( + "Shows a list of all available monster groups." + Environment.NewLine + + "Usage: monsters", + ShowMonsters + ) + }, + { "fight", + Create + ( + "Starts a fight against a given monster group." + Environment.NewLine + + "Usage: fight ", + StartBattle + ) + } + }; + + static KeyValuePair> Create(string help, Action action) + => KeyValuePair.Create(help, action); + + static string currentAutoFillInput = null; + static string currentInput = ""; + static int autoFillIndex = -1; + static int cursorPosition = 0; + static int historyIndex = -1; + static readonly List history = new List(); + + public static void ProcessInput(ConsoleKeyInfo keyInfo, Game game) + { + if (keyInfo.Key != ConsoleKey.Tab) + autoFillIndex = -1; + + if (keyInfo.Key != ConsoleKey.Tab && + keyInfo.Key != ConsoleKey.Enter) + { + if (currentAutoFillInput != null) + { + int lengthDiff = currentAutoFillInput.Length - currentInput.Length; + currentAutoFillInput = null; + Console.CursorLeft = 0; + Console.Write(currentInput); + if (lengthDiff > 0) + Console.Write(new string(' ', lengthDiff)); + Console.CursorLeft = cursorPosition; + } + } + + if (keyInfo.Key != ConsoleKey.UpArrow && + keyInfo.Key != ConsoleKey.DownArrow) + historyIndex = -1; + + switch (keyInfo.Key) + { + case ConsoleKey.Enter: + if (currentInput.Length != 0 || currentAutoFillInput != null) + ProcessCurrentInput(game); + return; + case ConsoleKey.Backspace: + if (Console.CursorLeft > 0) + RemoveLastInput(); + return; + case ConsoleKey.Escape: + while (currentInput.Length != 0) + RemoveLastInput(); + return; + case ConsoleKey.Tab: + if (currentInput.Length != 0) + AutoFill(); + return; + case ConsoleKey.LeftArrow: + if (Console.CursorLeft > 0) + --Console.CursorLeft; + return; + case ConsoleKey.RightArrow: + if (Console.CursorLeft < currentInput.Length) + ++Console.CursorLeft; + return; + case ConsoleKey.Home: + Console.CursorLeft = 0; + return; + case ConsoleKey.End: + Console.CursorLeft = currentInput.Length; + return; + // TODO: History is not working great. Maybe add it later. + /*case ConsoleKey.UpArrow: + if (history.Count != 0) + SetHistoryEntry(Math.Min(history.Count - 1, historyIndex + 1)); + break; + case ConsoleKey.DownArrow: + if (history.Count != 0 && historyIndex != -1) + SetHistoryEntry(Math.Max(0, historyIndex - 1)); + break;*/ + } + + if (keyInfo.KeyChar >= ' ' && keyInfo.KeyChar < 127) + AddInput(keyInfo.KeyChar); + } + + static void SetHistoryEntry(int index) + { + historyIndex = Math.Min(history.Count - 1, index + 1); + string entry = history[history.Count - historyIndex - 1]; + int lengthDiff = Math.Max(0, currentInput.Length - entry.Length); + currentInput = entry; + Console.CursorLeft = 0; + Console.Write(entry); + if (lengthDiff != 0) + Console.WriteLine(new string(' ', lengthDiff)); + } + + static void RemoveLastInput() + { + if (Console.CursorLeft == currentInput.Length) + { + currentInput = currentInput.Remove(currentInput.Length - 1); + Console.Write("\b \b"); + } + else + { + int newCursorPosition = Console.CursorLeft - 1; + currentInput = currentInput.Remove(newCursorPosition, 1); + Console.CursorLeft = 0; + Console.Write(currentInput + " "); + Console.CursorLeft = newCursorPosition; + } + } + + static void AddInput(char input) + { + int newCursorPosition = Console.CursorLeft + 1; + currentInput += input; + Console.CursorLeft = 0; + Console.Write(currentInput); + Console.CursorLeft = newCursorPosition; + } + + static void AutoFill() + { + var possibleCheats = currentInput.Contains(' ') ? null : + cheats.Where(c => c.Key.StartsWith(currentInput.ToLower())).ToArray(); + + if (possibleCheats == null || possibleCheats.Length == 0) + { + autoFillIndex = -1; + currentAutoFillInput = null; + return; + } + + int lengthDiff = 0; + string newCheat = possibleCheats[(++autoFillIndex) % possibleCheats.Length].Key; + + if (currentAutoFillInput == null) + cursorPosition = Console.CursorLeft; + else + lengthDiff = currentAutoFillInput.Length - newCheat.Length; + + currentAutoFillInput = newCheat; + Console.CursorLeft = 0; + Console.Write(currentAutoFillInput); + if (lengthDiff > 0) + Console.Write(new string(' ', lengthDiff)); + } + + static void ProcessCurrentInput(Game game) + { + if (currentAutoFillInput != null) + currentInput = currentAutoFillInput; + + currentAutoFillInput = null; + + if (!string.IsNullOrWhiteSpace(currentInput)) + { + var parts = currentInput.Split(' '); + + if (parts.Length != 0) + { + foreach (var cheat in cheats) + { + if (cheat.Key == parts[0].ToLower()) + { + historyIndex = -1; + history.Add(currentInput); + currentAutoFillInput = null; + currentInput = ""; + autoFillIndex = -1; + cursorPosition = 0; + Console.CursorLeft = currentInput.Length; + Console.WriteLine(); + Console.WriteLine(); + + cheat.Value.Value?.Invoke(game, parts.Skip(1).ToArray()); + return; + } + } + } + } + } + + static void Help(Game game, string[] args) + { + if (args.Length != 0) + { + var cheatName = args[0].ToLower(); + + if (cheats.TryGetValue(cheatName, out var cheat)) + { + Console.WriteLine(); + Console.WriteLine(cheat.Key); + Console.WriteLine(); + + return; + } + } + + Console.WriteLine(); + Console.WriteLine("The following cheat commands are available:"); + + foreach (var cheat in cheats) + Console.WriteLine(cheat.Key); + + Console.WriteLine("Type 'help ' for more details."); + Console.WriteLine("Example: help godmode"); + Console.WriteLine(); + } + + static void Godmode(Game game, string[] args) + { + bool activate = args.Length == 0 || !int.TryParse(args[0], out int active) || active != 0; + + Console.WriteLine(); + + if (activate) + { + Console.WriteLine("All party members are now immune to damage."); + + if (!game.Godmode) + { + Console.WriteLine(); + Console.WriteLine("Robert was here I guess. :)"); + } + } + else + { + Console.WriteLine("All party members are no longer immune to damage."); + + if (game.Godmode) + { + Console.WriteLine(); + Console.WriteLine("Robert has gone I guess. :)"); + } + } + + Console.WriteLine(); + + game.Godmode = activate; + } + + static void Netsrak(Game game, string[] args) + { + Console.WriteLine(); + + foreach (var partyMember in game.PartyMembers) + { + partyMember.HitPoints.CurrentValue = partyMember.HitPoints.MaxValue; + partyMember.SpellPoints.CurrentValue = partyMember.SpellPoints.MaxValue; + + foreach (var attribute in Enum.GetValues()) + { + partyMember.Attributes[attribute].CurrentValue = partyMember.Attributes[attribute].MaxValue; + } + + foreach (var ability in Enum.GetValues()) + { + partyMember.Abilities[ability].CurrentValue = partyMember.Abilities[ability].MaxValue; + } + + partyMember.SpokenLanguages = (Language)0xff; + + switch (partyMember.Class) + { + case Class.Adventurer: + case Class.Alchemist: + partyMember.LearnedAlchemisticSpells = 0xffff; + break; + case Class.Healer: + case Class.Paladin: + partyMember.LearnedHealingSpells = 0xffff; + break; + case Class.Ranger: + case Class.Mystic: + partyMember.LearnedMysticSpells = 0xffff; + break; + case Class.Mage: + partyMember.LearnedDestructionSpells = 0xffff; + break; + } + } + + Console.WriteLine("All party members' LP and SP were filled."); + Console.WriteLine("All their attributes and abilites are maxed."); + Console.WriteLine("They all speak all languages now."); + Console.WriteLine("And they learned all spells of their school."); + Console.WriteLine(); + Console.WriteLine("Karsten was here I guess. :)"); + Console.WriteLine(); + } + + static void ShowMaps(Game game, string[] args) + { + Console.WriteLine(); + + var maps = new List(game.MapManager.Maps); + maps.Sort((a, b) => a.Index.CompareTo(b.Index)); + int halfCount = maps.Count / 2; + int secondRowOffset = halfCount; + + if (maps.Count % 2 == 1) + ++secondRowOffset; + + for (int i = 0; i < halfCount; ++i) + { + Console.Write($"{maps[i].Index:000}: {maps[i].Name}".PadRight(24)); + Console.WriteLine($"{maps[secondRowOffset + i].Index:000}: {maps[secondRowOffset + i].Name}"); + } + + if (secondRowOffset > halfCount) + Console.WriteLine($"{maps[secondRowOffset - 1].Index:000}: {maps[secondRowOffset - 1].Name}"); + } + + static void Teleport(Game game, string[] args) + { + Console.WriteLine(); + + if (args.Length == 0 || !uint.TryParse(args[0], out uint mapIndex) || + !game.MapManager.Maps.Any(m => m.Index == mapIndex)) + { + Console.WriteLine("Invalid map index."); + Console.WriteLine("Type 'maps' to see a list of maps."); + Console.WriteLine(); + return; + } + + static CharacterDirection? ParseDirection(string input) + { + input = input.ToLower(); + if (input == "0" || input == "up") + return CharacterDirection.Up; + else if (input == "1" || input == "right") + return CharacterDirection.Right; + else if (input == "2" || input == "down") + return CharacterDirection.Down; + else if (input == "3" || input == "left") + return CharacterDirection.Left; + else + return null; + } + + var map = game.MapManager.Maps.Single(m => m.Index == mapIndex); + var random = new Random(); + uint x = args.Length > 1 && uint.TryParse(args[1], out uint ax) ? ax : 1u + random.Next() % (uint)map.Width; + uint y = args.Length > 2 && uint.TryParse(args[2], out uint ay) ? ay : 1u + random.Next() % (uint)map.Height; + var direction = (args.Length > 3 ? ParseDirection(args[3]) : null) ?? (CharacterDirection)(random.Next() % 4); + + if (!game.Teleport(mapIndex, x, y, direction)) + { + Console.WriteLine("Unable to teleport in current game state."); + Console.WriteLine("Try to use the command when no ingame window is open."); + Console.WriteLine(); + return; + } + } + + static void ShowMonsters(Game game, string[] args) + { + Console.WriteLine(); + + static string GetMonsterNames(MonsterGroup monsterGroup) + { + var monsterNames = new Dictionary(); + + foreach (var monster in monsterGroup.Monsters) + { + if (monster != null) + { + if (!monsterNames.ContainsKey(monster.Name)) + monsterNames[monster.Name] = 1; + else + ++monsterNames[monster.Name]; + } + } + + return string.Join(", ", monsterNames.Select(m => $"{m.Value}x{m.Key}")); + } + + foreach (var monsterGroup in game.CharacterManager.MonsterGroups) + { + Console.WriteLine($"{monsterGroup.Key:000}: {GetMonsterNames(monsterGroup.Value)}"); + } + } + + static void StartBattle(Game game, string[] args) + { + Console.WriteLine(); + + if (args.Length == 0 || !uint.TryParse(args[0], out uint monsterGroupIndex) || + !game.CharacterManager.MonsterGroups.ContainsKey(monsterGroupIndex)) + { + Console.WriteLine("Invalid monster group index."); + Console.WriteLine("Type 'monsters' to see a list of monster groups."); + Console.WriteLine(); + return; + } + + if (!game.StartBattle(monsterGroupIndex)) + { + Console.WriteLine("Unable to start a fight in current game state."); + Console.WriteLine("Try to use the command when no ingame window is open."); + Console.WriteLine(); + return; + } + } + } +} diff --git a/Ambermoon.net/GameWindow.cs b/Ambermoon.net/GameWindow.cs index 5d28fd4b..c78c5fa5 100644 --- a/Ambermoon.net/GameWindow.cs +++ b/Ambermoon.net/GameWindow.cs @@ -521,10 +521,19 @@ void Window_Update(double delta) mainMenu?.Destroy(); mainMenu = null; gameCreator = null; + + // Show cheat info + Console.WriteLine("***** Ambermoon Cheat Console *****"); + Console.WriteLine("Type 'help' for more information."); } } else if (Game != null) + { Game.Update(delta); + + if (Console.KeyAvailable) + Cheats.ProcessInput(Console.ReadKey(true), Game); + } } void Window_Resize(System.Drawing.Size size)