From 0c0072025c8aa984609f2a80509c52855e4cc797 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Mon, 12 Mar 2018 17:43:54 +1100 Subject: [PATCH] Start porting CommandList to C --- ClassicalSharp/Commands/CommandList.cs | 2 +- ClassicalSharp/Commands/Commands.cs | 8 +- ClassicalSharp/Game/Game.Init.cs | 2 +- ClassicalSharp/TexturePack/Animations.cs | 4 +- src/Client/Chat.c | 329 ++++++++++++++++++++++- src/Client/String.c | 26 +- src/Client/String.h | 1 + src/Client/Typedefs.h | 13 +- 8 files changed, 365 insertions(+), 20 deletions(-) diff --git a/ClassicalSharp/Commands/CommandList.cs b/ClassicalSharp/Commands/CommandList.cs index ba3c1a119..94cc11d2a 100644 --- a/ClassicalSharp/Commands/CommandList.cs +++ b/ClassicalSharp/Commands/CommandList.cs @@ -84,7 +84,7 @@ namespace ClassicalSharp.Commands { if (text.Length == 0) { // only / or /client game.Chat.Add("&eList of client commands:"); PrintDefinedCommands(game); - game.Chat.Add("&eTo see a particular command's help, type &a/client help [cmd name]"); + game.Chat.Add("&eTo see help for a command, type &a/client help [cmd name]"); return; } diff --git a/ClassicalSharp/Commands/Commands.cs b/ClassicalSharp/Commands/Commands.cs index 948916813..adf1d48a1 100644 --- a/ClassicalSharp/Commands/Commands.cs +++ b/ClassicalSharp/Commands/Commands.cs @@ -30,13 +30,14 @@ namespace ClassicalSharp.Commands { if (args.Length == 1) { game.Chat.Add("&eList of client commands:"); game.CommandList.PrintDefinedCommands(game); - game.Chat.Add("&eTo see a particular command's help, type /client help [cmd name]"); + game.Chat.Add("&eTo see help for a command, type /client help [cmd name]"); } else { Command cmd = game.CommandList.GetMatch(args[1]); if (cmd == null) return; string[] help = cmd.Help; - for (int i = 0; i < help.Length; i++) + for (int i = 0; i < help.Length; i++) { game.Chat.Add(help[i]); + } } } } @@ -53,8 +54,9 @@ namespace ClassicalSharp.Commands { public override void Execute(string[] args) { string[] lines = game.Graphics.ApiInfo; - for (int i = 0; i < lines.Length; i++) + for (int i = 0; i < lines.Length; i++) { game.Chat.Add("&a" + lines[i]); + } } } diff --git a/ClassicalSharp/Game/Game.Init.cs b/ClassicalSharp/Game/Game.Init.cs index 3f1044c32..8c8e5a9c0 100644 --- a/ClassicalSharp/Game/Game.Init.cs +++ b/ClassicalSharp/Game/Game.Init.cs @@ -204,7 +204,7 @@ namespace ClassicalSharp { ChatScale = Options.GetFloat(OptionsKey.ChatScale, 0.35f, 5f, 1f); ShowFPS = Options.GetBool(OptionsKey.ShowFPS, true); - UseClassicGui = Options.GetBool(OptionsKey.UseClassicGui, true) || ClassicMode; + UseClassicGui = Options.GetBool(OptionsKey.UseClassicGui, true) || ClassicMode; UseClassicTabList = Options.GetBool(OptionsKey.UseClassicTabList, false) || ClassicMode; UseClassicOptions = Options.GetBool(OptionsKey.UseClassicOptions, false) || ClassicMode; diff --git a/ClassicalSharp/TexturePack/Animations.cs b/ClassicalSharp/TexturePack/Animations.cs index a732766ad..1241ed9d7 100644 --- a/ClassicalSharp/TexturePack/Animations.cs +++ b/ClassicalSharp/TexturePack/Animations.cs @@ -80,7 +80,7 @@ namespace ClassicalSharp.Textures { /// 1) the target tile in the terrain atlas 2) the start location of animation frames
/// 3) the size of each animation frame 4) the number of animation frames
/// 5) the delay between advancing animation frames. - public void ReadAnimationsDescription(StreamReader reader) { + void ReadAnimationsDescription(StreamReader reader) { string line; while ((line = reader.ReadLine()) != null) { if (line.Length == 0 || line[0] == '#') continue; @@ -182,7 +182,7 @@ namespace ClassicalSharp.Textures { /// Disposes the atlas bitmap that contains animation frames, and clears /// the list of defined animations. - public void Clear() { + void Clear() { animations.Clear(); if (animBmp == null) return; diff --git a/src/Client/Chat.c b/src/Client/Chat.c index 0b22ebf4e..24a68a6e1 100644 --- a/src/Client/Chat.c +++ b/src/Client/Chat.c @@ -21,8 +21,8 @@ void Chat_Send(STRING_PURE String* text) { if (text->length == 0) return; StringsBuffer_Add(&InputLog, text); - if (game.CommandList.IsCommandPrefix(text)) { - game.CommandList.Execute(text); + if (Commands_IsCommandPrefix(text)) { + Commands_Execute(text); } else { ServerConnection_SendChat(text); } @@ -148,4 +148,329 @@ IGameComponent Chat_MakeGameComponent(void) { IGameComponent comp = IGameComponent_MakeEmpty(); comp.Reset = Chat_Reset; return comp; +} + +typedef struct ChatCommand_ { + String Name; + String Help[6]; + void (*Execute)(STRING_PURE String* args, UInt32 argsCount); + bool SingleplayerOnly; +} ChatCommand; +typedef void (*ChatCommandConstructor)(ChatCommand* cmd); + +#define COMMANDS_PREFIX "/client" +#define COMMANDS_PREFIX_STARTS "/client " +bool Commands_IsCommandPrefix(STRING_PURE String* input) { + if (input->length == 0) return false; + if (ServerConnection_IsSinglePlayer && input->buffer[0] == '/') + return true; + + String starts = String_FromConst(COMMANDS_PREFIX_STARTS); + String prefix = String_FromConst(COMMANDS_PREFIX); + return String_CaselessStarts(input, &starts) + || String_CaselessEquals(input, &prefix); +} + + +void HelpCommand_Execute(STRING_PURE String* args, UInt32 argsCount) { + if (argsCount == 1) { + game.Chat.Add("&eList of client commands:"); + Commands_PrintDefined(); + game.Chat.Add("&eTo see help for a command, type /client help [cmd name]"); + } else { + ChatCommand* cmd = Commands_GetMatch(&args[1]); + if (cmd == NULL) return; + + UInt32 i; + for (i = 0; i < Array_NumElements(cmd->Help); i++) { + String* help = &cmd->Help[i]; + if (help->length == 0) continue; + Chat_Add(help); + } + } +} + +void HelpCommand_Make(ChatCommand* cmd) { + String name = String_FromConst("Help"); cmd->Name = name; + String help0 = String_FromConst("&a/client help [command name]"); cmd->Help[0] = help0; + String help1 = String_FromConst("&eDisplays the help for the given command."); cmd->Help[1] = help1; + cmd->Execute = HelpCommand_Execute; +} + +void GpuInfoCommand_Execute(STRING_PURE String* args, UInt32 argsCount) { + string[] lines = game.Graphics.ApiInfo; + for (int i = 0; i < lines.Length; i++) { + game.Chat.Add("&a" + lines[i]); + } +} + +void GpuInfoCommand_Make(ChatCommand* cmd) { + String name = String_FromConst("GpuInfo"); cmd->Name = name; + String help0 = String_FromConst("&a/client gpuinfo"); cmd->Help[0] = help0; + String help1 = String_FromConst("&eDisplays information about your GPU."); cmd->Help[1] = help1; +} + +void RenderTypeCommand_Execute(STRING_PURE String* args, UInt32 argsCount) { + if (argsCount == 1) { + game.Chat.Add("&e/client: &cYou didn't specify a new render type."); + } else if (game.SetRenderType(args[1])) { + game.Chat.Add("&e/client: &fRender type is now " + args[1] + "."); + } else { + game.Chat.Add("&e/client: &cUnrecognised render type &f\"" + args[1] + "\"&c."); + } +} + +void RenderTypeCommand_Make(ChatCommand* cmd) { + String name = String_FromConst("RenderType"); cmd->Name = name; + String help0 = String_FromConst("&a/client rendertype [normal/legacy/legacyfast]"); cmd->Help[0] = help0; + String help1 = String_FromConst("&bnormal: &eDefault renderer, with all environmental effects enabled."); cmd->Help[1] = help1; + String help2 = String_FromConst("&blegacy: &eMay be slightly slower than normal, but produces the same environmental effects."); cmd->Help[2] = help2; + String help3 = String_FromConst("&blegacyfast: &eSacrifices clouds, fog and overhead sky for faster performance."); cmd->Help[3] = help3; + String help4 = String_FromConst("&bnormalfast: &eSacrifices clouds, fog and overhead sky for faster performance."); cmd->Help[4] = help4; +} + +void ResolutionCommand_Execute(STRING_PURE String* args, UInt32 argsCount) { + Int32 width, height; + if (argsCount < 3) { + game.Chat.Add("&e/client: &cYou didn't specify width and height"); + } else if (!Convert_TryParseInt32(&args[1], &width) || !Convert_TryParseInt32(&args[2], &height)) { + game.Chat.Add("&e/client: &cWidth and height must be integers."); + } else if (width <= 0 || height <= 0) { + game.Chat.Add("&e/client: &cWidth and height must be above 0."); + } else { + game.window.ClientSize = new Size(width, height); + Options_SetInt32(OPTION_WINDOW_WIDTH, width); + Options_SetInt32(OPTION_WINDOW_HEIGHT, height); + } +} + +void ResolutionCommand_Make(ChatCommand* cmd) { + String name = String_FromConst("Resolution"); cmd->Name = name; + String help0 = String_FromConst("&a/client resolution [width] [height]"); cmd->Help[0] = help0; + String help1 = String_FromConst("&ePrecisely sets the size of the rendered window."); cmd->Help[1] = help1; +} + +void ModelCommand_Execute(STRING_PURE String* args, UInt32 argsCount) { + if (argsCount == 1) { + game.Chat.Add("&e/client model: &cYou didn't specify a model name."); + } else { + game.LocalPlayer.SetModel(Utils.ToLower(args[1])); + } +} + +void ModelCommand_Make(ChatCommand* cmd) { + String name = String_FromConst("Model"); cmd->Name = name; + String help0 = String_FromConst("&a/client model [name]"); cmd->Help[0] = help0; + String help1 = String_FromConst("&bnames: &echibi, chicken, creeper, human, pig, sheep"); cmd->Help[1] = help1; + String help2 = String_FromConst("&e skeleton, spider, zombie, sitting, "); cmd->Help[2] = help2; + cmd->SingleplayerOnly = true; +} + +void CuboidCommand_Make(ChatCommand* cmd) { + String name = String_FromConst("Cuboid"); + Help = new string[]{ + "&a/client cuboid [block] [persist]", + "&eFills the 3D rectangle between two points with [block].", + "&eIf no block is given, uses your currently held block.", + "&e If persist is given and is \"yes\", then the command", + "&e will repeatedly cuboid, without needing to be typed in again.", + }; + cmd->SingleplayerOnly = true; +} + int block = -1; + Vector3I mark1, mark2; + bool persist = false; + + void CuboidCommand_Execute(STRING_PURE String* args, UInt32 argsCount) { + game.UserEvents.BlockChanged -= BlockChanged; + block = -1; + mark1 = new Vector3I(int.MaxValue); + mark2 = new Vector3I(int.MaxValue); + persist = false; + + if (!ParseBlock(args)) return; + if (args.Length > 2 && Utils.CaselessEquals(args[2], "yes")) + persist = true; + + game.Chat.Add("&eCuboid: &fPlace or delete a block.", MessageType.ClientStatus3); + game.UserEvents.BlockChanged += BlockChanged; + } + + bool ParseBlock(string[] args) { + if (args.Length == 1) return true; + if (Utils.CaselessEquals(args[1], "yes")) { persist = true; return true; } + + int temp = -1; + BlockID block = 0; + if ((temp = BlockInfo.FindID(args[1])) != -1) { + block = (BlockID)temp; + } + else if (!BlockID.TryParse(args[1], out block)) { + game.Chat.Add("&eCuboid: &c\"" + args[1] + "\" is not a valid block name or id."); return false; + } + + if (block >= Block.CpeCount && BlockInfo.Name[block] == "Invalid") { + game.Chat.Add("&eCuboid: &cThere is no block with id \"" + args[1] + "\"."); return false; + } + this.block = block; + return true; + } + + void BlockChanged(object sender, BlockChangedEventArgs e) { + if (mark1.X == int.MaxValue) { + mark1 = e.Coords; + game.UpdateBlock(mark1.X, mark1.Y, mark1.Z, e.OldBlock); + game.Chat.Add("&eCuboid: &fMark 1 placed at (" + e.Coords + "), place mark 2.", + MessageType.ClientStatus3); + } + else { + mark2 = e.Coords; + DoCuboid(); + game.Chat.Add(null, MessageType.ClientStatus3); + + if (!persist) { + game.UserEvents.BlockChanged -= BlockChanged; + } + else { + mark1 = new Vector3I(int.MaxValue); + game.Chat.Add("&eCuboid: &fPlace or delete a block.", MessageType.ClientStatus3); + } + } + } + + void DoCuboid() { + Vector3I min = Vector3I.Min(mark1, mark2); + Vector3I max = Vector3I.Max(mark1, mark2); + if (!game.World.IsValidPos(min) || !game.World.IsValidPos(max)) return; + + BlockID toPlace = (BlockID)block; + if (block == -1) toPlace = game.Inventory.Selected; + + for (int y = min.Y; y <= max.Y; y++) + for (int z = min.Z; z <= max.Z; z++) + for (int x = min.X; x <= max.X; x++) + { + game.UpdateBlock(x, y, z, toPlace); + } + } +} + +void TeleportCommand_Make(ChatCommand* cmd) { + String name = String_FromConst("TP"); cmd->Name = name; + String help0 = String_FromConst("&a/client tp [x y z]"); cmd->Help[0] = help0; + String help1 = String_FromConst("&eMoves you to the given coordinates."); cmd->Help[1] = help1; + cmd->SingleplayerOnly = true; +} + + void TeleportCommand_Execute(STRING_PURE String* args, UInt32 argsCount) { + if (args.Length != 4) { + game.Chat.Add("&e/client teleport: &cYou didn't specify X, Y and Z coordinates."); + } else { + float x = 0, y = 0, z = 0; + if (!Utils.TryParseDecimal(args[1], out x) || + !Utils.TryParseDecimal(args[2], out y) || + !Utils.TryParseDecimal(args[3], out z)) { + game.Chat.Add("&e/client teleport: &cCoordinates must be decimals"); + return; + } + + Vector3 v = new Vector3(x, y, z); + LocationUpdate update = LocationUpdate.MakePos(v, false); + game.LocalPlayer.SetLocation(update, false); + } + } +} +} + +ChatCommand commands_List[10]; +UInt32 commands_Count; +void Init() { + Register(new GpuInfoCommand()); + Register(new HelpCommand()); + Register(new RenderTypeCommand()); + Register(new ResolutionCommand()); + Register(new ModelCommand()); + Register(new CuboidCommand()); + Register(new TeleportCommand()); +} + +void Register(Command command) { + RegisteredCommands.Add(command); +} + +ChatCommand* Commands_GetMatch(STRING_PURE String* cmdName) { + ChatCommand* match = NULL; + UInt32 i; + for (i = 0; i < commands_Count; i++) { + ChatCommand* cmd = &commands_List[i]; + if (!String_CaselessStarts(&cmd->Name, cmdName)) continue; + + if (match != NULL) { + game.Chat.Add("&e/client: Multiple commands found that start with: \"&f" + cmdName + "&e\"."); + return NULL; + } + match = cmd; + } + + if (match == NULL) { + game.Chat.Add("&e/client: Unrecognised command: \"&f" + cmdName + "&e\"."); + game.Chat.Add("&e/client: Type &a/client &efor a list of commands."); + return NULL; + } + if (match->SingleplayerOnly && !ServerConnection_IsSinglePlayer) { + game.Chat.Add("&e/client: \"&f" + cmdName + "&e\" can only be used in singleplayer."); + return NULL; + } + return match; +} + +void Commands_Execute(STRING_PURE String* input) { + String text = *input; + String prefix = String_FromConst(COMMANDS_PREFIX); + + if (String_CaselessStarts(&text, &prefix)) { /* /client command args */ + text = String_UNSAFE_SubstringAt(&text, prefix.length); + text = text.TrimStart(splitChar); + } else { /* /command args */ + text = String_UNSAFE_SubstringAt(&text, 1); + } + + if (text.length == 0) { /* only / or /client */ + String m1 = String_FromConst("&eList of client commands:"); Chat_Add(&m1); + Commands_PrintDefined(); + String m2 = String_FromConst("&eTo see help for a command, type &a/client help [cmd name]"); Chat_Add(&m2); + return; + } + + String args[10]; + UInt32 argsCount = Array_NumElements(args); + String_UNSAFE_Split(&text, ' ', args, &argsCount); + + ChatCommand* cmd = Commands_GetMatch(&args[0]); + if (cmd == NULL) return; + cmd->Execute(args, argsCount); +} + +void Commands_PrintDefined(void) { + UInt8 strBuffer[String_BufferSize(STRING_SIZE)]; + String str = String_InitAndClearArray(strBuffer); + UInt32 i; + + for (i = 0; i < commands_Count; i++) { + ChatCommand* cmd = &commands_List[i]; + String name = cmd->Name; + + if ((str.length + name.length + 2) > str.capacity) { + Chat_Add(&str); + String_Clear(&str); + } + String_Append(&str, &name); + String_AppendConst(&str, ", "); + } + + if (str.length > 0) { Chat_Add(&str); } +} + +void Dispose() { + RegisteredCommands.Clear(); } \ No newline at end of file diff --git a/src/Client/String.c b/src/Client/String.c index 56706a065..a9479afa9 100644 --- a/src/Client/String.c +++ b/src/Client/String.c @@ -3,10 +3,11 @@ #include "ErrorHandler.h" #include "Platform.h" +#define Char_MakeLower(ch) if ((ch) >= 'A' && (ch) <= 'Z') { (ch) += ' '; } bool Char_IsUpper(UInt8 c) { return c >= 'A' && c <= 'Z'; } UInt8 Char_ToLower(UInt8 c) { - if (!Char_IsUpper(c)) return c; - return (UInt8)(c + ' '); + Char_MakeLower(c); + return c; } String String_Init(STRING_REF UInt8* buffer, UInt16 length, UInt16 capacity) { @@ -103,8 +104,8 @@ bool String_CaselessEquals(STRING_PURE String* a, STRING_PURE String* b) { Int32 i; for (i = 0; i < a->length; i++) { - UInt8 aCur = a->buffer[i]; if (aCur >= 'A' && aCur <= 'Z') { aCur += ' '; } - UInt8 bCur = b->buffer[i]; if (bCur >= 'A' && bCur <= 'Z') { bCur += ' '; } + UInt8 aCur = a->buffer[i]; Char_MakeLower(aCur); + UInt8 bCur = b->buffer[i]; Char_MakeLower(bCur); if (aCur != bCur) return false; } return true; @@ -228,6 +229,7 @@ void String_InsertAt(STRING_TRANSIENT String* str, Int32 offset, UInt8 c) { for (i = str->length; i > offset; i--) { str->buffer[i] = str->buffer[i - 1]; } + str->buffer[offset] = c; str->length++; } @@ -241,6 +243,7 @@ void String_DeleteAt(STRING_TRANSIENT String* str, Int32 offset) { for (i = offset; i < str->length - 1; i++) { str->buffer[i] = str->buffer[i + 1]; } + str->buffer[str->length - 1] = NULL; str->length--; } @@ -264,17 +267,30 @@ Int32 String_IndexOfString(STRING_PURE String* str, STRING_PURE String* sub) { bool String_StartsWith(STRING_PURE String* str, STRING_PURE String* sub) { if (str->length < sub->length) return false; - Int32 i; + for (i = 0; i < sub->length; i++) { if (str->buffer[i] != sub->buffer[i]) return false; } return true; } +bool String_CaselessStarts(STRING_PURE String* str, STRING_PURE String* sub) { + if (str->length < sub->length) return false; + Int32 i; + + for (i = 0; i < sub->length; i++) { + UInt8 strCur = str->buffer[i]; Char_MakeLower(strCur); + UInt8 subCur = sub->buffer[i]; Char_MakeLower(subCur); + if (strCur != subCur) return false; + } + return true; +} + Int32 String_Compare(STRING_PURE String* a, STRING_PURE String* b) { Int32 minLen = min(a->length, b->length); Int32 i; + for (i = 0; i < minLen; i++) { if (a->buffer[i] == b->buffer[i]) continue; return a->buffer[i] < b->buffer[i] ? 1 : -1; diff --git a/src/Client/String.h b/src/Client/String.h index 49844fc49..d6bbc423a 100644 --- a/src/Client/String.h +++ b/src/Client/String.h @@ -73,6 +73,7 @@ void String_DeleteAt(STRING_TRANSIENT String* str, Int32 offset); Int32 String_IndexOfString(STRING_PURE String* str, STRING_PURE String* sub); #define String_ContainsString(str, sub) (String_IndexOfString(str, sub) >= 0) bool String_StartsWith(STRING_PURE String* str, STRING_PURE String* sub); +bool String_CaselessStarts(STRING_PURE String* str, STRING_PURE String* sub); Int32 String_Compare(STRING_PURE String* a, STRING_PURE String* b); UInt16 Convert_CP437ToUnicode(UInt8 c); diff --git a/src/Client/Typedefs.h b/src/Client/Typedefs.h index 48815e91c..f5f1b562f 100644 --- a/src/Client/Typedefs.h +++ b/src/Client/Typedefs.h @@ -46,12 +46,13 @@ typedef UInt32 ReturnCode; typedef struct FontDesc_ { void* Handle; UInt16 Size, Style; } FontDesc; -#define UInt8_MaxValue ((UInt8)0xFF) -#define Int16_MaxValue ((Int16)0x7FFF) -#define UInt16_MaxValue ((UInt16)0xFFFF) -#define Int32_MaxValue ((Int32)0x7FFFFFFFL) -#define UInt32_MaxValue ((UInt32)0xFFFFFFFFUL) -#define Int32_MinValue ((Int32)0xFFFFFFFFL) +#define UInt8_MaxValue ((UInt8)255) +#define Int16_MinValue ((Int16)-32768) +#define Int16_MaxValue ((Int16)32767) +#define UInt16_MaxValue ((UInt16)65535) +#define Int32_MinValue ((Int32)-2147483648L) +#define Int32_MaxValue ((Int32)2147483647L) +#define UInt32_MaxValue ((UInt32)4294967295UL) #define USE_DX true