diff options
Diffstat (limited to 'src/Commands.c')
-rw-r--r-- | src/Commands.c | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/src/Commands.c b/src/Commands.c new file mode 100644 index 0000000..3976d18 --- /dev/null +++ b/src/Commands.c @@ -0,0 +1,742 @@ +#include "Commands.h" +#include "Chat.h" +#include "String.h" +#include "Event.h" +#include "Game.h" +#include "Logger.h" +#include "Server.h" +#include "World.h" +#include "Inventory.h" +#include "Entity.h" +#include "Window.h" +#include "Graphics.h" +#include "Funcs.h" +#include "Block.h" +#include "EnvRenderer.h" +#include "Utils.h" +#include "TexturePack.h" +#include "Options.h" +#include "Drawer2D.h" + +#define COMMANDS_PREFIX "/client" +#define COMMANDS_PREFIX_SPACE "/client " +static struct ChatCommand* cmds_head; +static struct ChatCommand* cmds_tail; + +void Commands_Register(struct ChatCommand* cmd) { + LinkedList_Append(cmd, cmds_head, cmds_tail); +} + + +/*########################################################################################################################* +*------------------------------------------------------Command handling---------------------------------------------------* +*#########################################################################################################################*/ +static struct ChatCommand* Commands_FindMatch(const cc_string* cmdName) { + struct ChatCommand* match = NULL; + struct ChatCommand* cmd; + cc_string name; + + for (cmd = cmds_head; cmd; cmd = cmd->next) + { + name = String_FromReadonly(cmd->name); + if (String_CaselessEquals(&name, cmdName)) return cmd; + } + + for (cmd = cmds_head; cmd; cmd = cmd->next) + { + name = String_FromReadonly(cmd->name); + if (!String_CaselessStarts(&name, cmdName)) continue; + + if (match) { + Chat_Add1("&e/client: Multiple commands found that start with: \"&f%s&e\".", cmdName); + return NULL; + } + match = cmd; + } + + if (!match) { + Chat_Add1("&e/client: Unrecognised command: \"&f%s&e\".", cmdName); + Chat_AddRaw("&e/client: Type &a/client &efor a list of commands."); + } + return match; +} + +static void Commands_PrintDefault(void) { + cc_string str; char strBuffer[STRING_SIZE]; + struct ChatCommand* cmd; + cc_string name; + + Chat_AddRaw("&eList of client commands:"); + String_InitArray(str, strBuffer); + + for (cmd = cmds_head; cmd; cmd = cmd->next) { + name = String_FromReadonly(cmd->name); + + if ((str.length + name.length + 2) > str.capacity) { + Chat_Add(&str); + str.length = 0; + } + String_AppendString(&str, &name); + String_AppendConst(&str, ", "); + } + + if (str.length) { Chat_Add(&str); } + Chat_AddRaw("&eTo see help for a command, type /client help [cmd name]"); +} + +cc_bool Commands_Execute(const cc_string* input) { + static const cc_string prefixSpace = String_FromConst(COMMANDS_PREFIX_SPACE); + static const cc_string prefix = String_FromConst(COMMANDS_PREFIX); + cc_string text; + + struct ChatCommand* cmd; + int offset, count; + cc_string name, value; + cc_string args[50]; + + if (String_CaselessStarts(input, &prefixSpace)) { + /* /client [command] [args] */ + offset = prefixSpace.length; + } else if (String_CaselessEquals(input, &prefix)) { + /* /client */ + offset = prefix.length; + } else if (Server.IsSinglePlayer && String_CaselessStarts(input, &prefix)) { + /* /client[command] [args] */ + offset = prefix.length; + } else if (Server.IsSinglePlayer && input->length && input->buffer[0] == '/') { + /* /[command] [args] */ + offset = 1; + } else { + return false; + } + + text = String_UNSAFE_SubstringAt(input, offset); + /* Check for only / or /client */ + if (!text.length) { Commands_PrintDefault(); return true; } + + String_UNSAFE_Separate(&text, ' ', &name, &value); + cmd = Commands_FindMatch(&name); + if (!cmd) return true; + + if ((cmd->flags & COMMAND_FLAG_SINGLEPLAYER_ONLY) && !Server.IsSinglePlayer) { + Chat_Add1("&e/client: \"&f%s&e\" can only be used in singleplayer.", &name); + return true; + } + + if (cmd->flags & COMMAND_FLAG_UNSPLIT_ARGS) { + /* argsCount = 0 if value.length is 0, 1 otherwise */ + cmd->Execute(&value, value.length != 0); + } else { + count = String_UNSAFE_Split(&value, ' ', args, Array_Elems(args)); + cmd->Execute(args, value.length ? count : 0); + } + return true; +} + + +/*########################################################################################################################* +*------------------------------------------------------Simple commands----------------------------------------------------* +*#########################################################################################################################*/ +static void HelpCommand_Execute(const cc_string* args, int argsCount) { + struct ChatCommand* cmd; + int i; + + if (!argsCount) { Commands_PrintDefault(); return; } + cmd = Commands_FindMatch(args); + if (!cmd) return; + + for (i = 0; i < Array_Elems(cmd->help); i++) { + if (!cmd->help[i]) continue; + Chat_AddRaw(cmd->help[i]); + } +} + +static struct ChatCommand HelpCommand = { + "Help", HelpCommand_Execute, + COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client help [command name]", + "&eDisplays the help for the given command.", + } +}; + +static void GpuInfoCommand_Execute(const cc_string* args, int argsCount) { + char buffer[7 * STRING_SIZE]; + cc_string str, line; + String_InitArray(str, buffer); + Gfx_GetApiInfo(&str); + + while (str.length) { + String_UNSAFE_SplitBy(&str, '\n', &line); + if (line.length) Chat_Add1("&a%s", &line); + } +} + +static struct ChatCommand GpuInfoCommand = { + "GpuInfo", GpuInfoCommand_Execute, + COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client gpuinfo", + "&eDisplays information about your GPU.", + } +}; + +static void RenderTypeCommand_Execute(const cc_string* args, int argsCount) { + int flags; + if (!argsCount) { + Chat_AddRaw("&e/client: &cYou didn't specify a new render type."); return; + } + + flags = EnvRenderer_CalcFlags(args); + if (flags >= 0) { + EnvRenderer_SetMode(flags); + Options_Set(OPT_RENDER_TYPE, args); + Chat_Add1("&e/client: &fRender type is now %s.", args); + } else { + Chat_Add1("&e/client: &cUnrecognised render type &f\"%s\"&c.", args); + } +} + +static struct ChatCommand RenderTypeCommand = { + "RenderType", RenderTypeCommand_Execute, + COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client rendertype [normal/legacy/fast]", + "&bnormal: &eDefault render mode, with all environmental effects enabled", + "&blegacy: &eSame as normal mode, &cbut is usually slightly slower", + " &eIf you have issues with clouds and map edges disappearing randomly, use this mode", + "&bfast: &eSacrifices clouds, fog and overhead sky for faster performance", + } +}; + +static void ResolutionCommand_Execute(const cc_string* args, int argsCount) { + int width, height; + if (argsCount < 2) { + Chat_Add4("&e/client: &fCurrent resolution is %i@%f2 x %i@%f2", + &Window_Main.Width, &DisplayInfo.ScaleX, &Window_Main.Height, &DisplayInfo.ScaleY); + } else if (!Convert_ParseInt(&args[0], &width) || !Convert_ParseInt(&args[1], &height)) { + Chat_AddRaw("&e/client: &cWidth and height must be integers."); + } else if (width <= 0 || height <= 0) { + Chat_AddRaw("&e/client: &cWidth and height must be above 0."); + } else { + Window_SetSize(width, height); + /* Window_Create uses these, but scales by DPI. Hence DPI unscale them here. */ + Options_SetInt(OPT_WINDOW_WIDTH, (int)(width / DisplayInfo.ScaleX)); + Options_SetInt(OPT_WINDOW_HEIGHT, (int)(height / DisplayInfo.ScaleY)); + } +} + +static struct ChatCommand ResolutionCommand = { + "Resolution", ResolutionCommand_Execute, + 0, + { + "&a/client resolution [width] [height]", + "&ePrecisely sets the size of the rendered window.", + } +}; + +static void ModelCommand_Execute(const cc_string* args, int argsCount) { + if (argsCount) { + Entity_SetModel(&Entities.CurPlayer->Base, args); + } else { + Chat_AddRaw("&e/client model: &cYou didn't specify a model name."); + } +} + +static struct ChatCommand ModelCommand = { + "Model", ModelCommand_Execute, + COMMAND_FLAG_SINGLEPLAYER_ONLY | COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client model [name]", + "&bnames: &echibi, chicken, creeper, human, pig, sheep", + "&e skeleton, spider, zombie, sit, <numerical block id>", + } +}; + +static void ClearDeniedCommand_Execute(const cc_string* args, int argsCount) { + int count = TextureCache_ClearDenied(); + Chat_Add1("Removed &e%i &fdenied texture pack URLs.", &count); +} + +static struct ChatCommand ClearDeniedCommand = { + "ClearDenied", ClearDeniedCommand_Execute, + COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client cleardenied", + "&eClears the list of texture pack URLs you have denied", + } +}; + +static void MotdCommand_Execute(const cc_string* args, int argsCount) { + if (Server.IsSinglePlayer) { + Chat_AddRaw("&eThis command can only be used in multiplayer."); + return; + } + Chat_Add1("&eName: &f%s", &Server.Name); + Chat_Add1("&eMOTD: &f%s", &Server.MOTD); +} + +static struct ChatCommand MotdCommand = { + "Motd", MotdCommand_Execute, + COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client motd", + "&eDisplays the server's name and MOTD." + } +}; + +/*########################################################################################################################* +*-------------------------------------------------------DrawOpCommand-----------------------------------------------------* +*#########################################################################################################################*/ +static IVec3 drawOp_mark1, drawOp_mark2; +static cc_bool drawOp_persist, drawOp_hooked, drawOp_hasMark1; +static const char* drawOp_name; +static void (*drawOp_Func)(IVec3 min, IVec3 max); + +static void DrawOpCommand_BlockChanged(void* obj, IVec3 coords, BlockID old, BlockID now); +static void DrawOpCommand_ResetState(void) { + if (drawOp_hooked) { + Event_Unregister_(&UserEvents.BlockChanged, NULL, DrawOpCommand_BlockChanged); + drawOp_hooked = false; + } + + drawOp_hasMark1 = false; +} + +static void DrawOpCommand_Begin(void) { + cc_string msg; char msgBuffer[STRING_SIZE]; + String_InitArray(msg, msgBuffer); + + String_Format1(&msg, "&e%c: &fPlace or delete a block.", drawOp_name); + Chat_AddOf(&msg, MSG_TYPE_CLIENTSTATUS_1); + + Event_Register_(&UserEvents.BlockChanged, NULL, DrawOpCommand_BlockChanged); + drawOp_hooked = true; +} + + +static void DrawOpCommand_Execute(void) { + IVec3 min, max; + + IVec3_Min(&min, &drawOp_mark1, &drawOp_mark2); + IVec3_Max(&max, &drawOp_mark1, &drawOp_mark2); + if (!World_Contains(min.x, min.y, min.z)) return; + if (!World_Contains(max.x, max.y, max.z)) return; + + drawOp_Func(min, max); +} + +static void DrawOpCommand_BlockChanged(void* obj, IVec3 coords, BlockID old, BlockID now) { + cc_string msg; char msgBuffer[STRING_SIZE]; + String_InitArray(msg, msgBuffer); + Game_UpdateBlock(coords.x, coords.y, coords.z, old); + + if (!drawOp_hasMark1) { + drawOp_mark1 = coords; + drawOp_hasMark1 = true; + + String_Format4(&msg, "&e%c: &fMark 1 placed at (%i, %i, %i), place mark 2.", + drawOp_name, &coords.x, &coords.y, &coords.z); + Chat_AddOf(&msg, MSG_TYPE_CLIENTSTATUS_1); + } else { + drawOp_mark2 = coords; + + DrawOpCommand_Execute(); + DrawOpCommand_ResetState(); + + if (!drawOp_persist) { + Chat_AddOf(&String_Empty, MSG_TYPE_CLIENTSTATUS_1); + } else { + DrawOpCommand_Begin(); + } + } +} + +static const cc_string yes_string = String_FromConst("yes"); +static void DrawOpCommand_ExtractPersistArg(cc_string* value) { + drawOp_persist = false; + if (!String_CaselessEnds(value, &yes_string)) return; + + drawOp_persist = true; + value->length -= 3; + String_UNSAFE_TrimEnd(value); +} + +static int DrawOpCommand_ParseBlock(const cc_string* arg) { + int block = Block_Parse(arg); + + if (block == -1) { + Chat_Add2("&e%c: &c\"%s\" is not a valid block name or id.", drawOp_name, arg); + return -1; + } + + if (block > Game_Version.MaxCoreBlock && !Block_IsCustomDefined(block)) { + Chat_Add2("&e%c: &cThere is no block with id \"%s\".", drawOp_name, arg); + return -1; + } + return block; +} + + +/*########################################################################################################################* +*-------------------------------------------------------CuboidCommand-----------------------------------------------------* +*#########################################################################################################################*/ +static int cuboid_block; + +static void CuboidCommand_Draw(IVec3 min, IVec3 max) { + BlockID toPlace; + int x, y, z; + + toPlace = (BlockID)cuboid_block; + if (cuboid_block == -1) toPlace = Inventory_SelectedBlock; + + for (y = min.y; y <= max.y; y++) { + for (z = min.z; z <= max.z; z++) { + for (x = min.x; x <= max.x; x++) { + Game_ChangeBlock(x, y, z, toPlace); + } + } + } +} + +static void CuboidCommand_Execute(const cc_string* args, int argsCount) { + cc_string value = *args; + + DrawOpCommand_ResetState(); + drawOp_name = "Cuboid"; + drawOp_Func = CuboidCommand_Draw; + + DrawOpCommand_ExtractPersistArg(&value); + cuboid_block = -1; /* Default to cuboiding with currently held block */ + + if (value.length) { + cuboid_block = DrawOpCommand_ParseBlock(&value); + if (cuboid_block == -1) return; + } + + DrawOpCommand_Begin(); +} + +static struct ChatCommand CuboidCommand = { + "Cuboid", CuboidCommand_Execute, + COMMAND_FLAG_SINGLEPLAYER_ONLY | COMMAND_FLAG_UNSPLIT_ARGS, + { + "&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.", + } +}; + + +/*########################################################################################################################* +*-------------------------------------------------------ReplaceCommand-----------------------------------------------------* +*#########################################################################################################################*/ +static int replace_source, replace_target; + +static void ReplaceCommand_Draw(IVec3 min, IVec3 max) { + BlockID cur, source, toPlace; + int x, y, z; + + source = (BlockID)replace_source; + toPlace = (BlockID)replace_target; + if (replace_target == -1) toPlace = Inventory_SelectedBlock; + + for (y = min.y; y <= max.y; y++) { + for (z = min.z; z <= max.z; z++) { + for (x = min.x; x <= max.x; x++) { + cur = World_GetBlock(x, y, z); + if (cur != source) continue; + Game_ChangeBlock(x, y, z, toPlace); + } + } + } +} + +static void ReplaceCommand_Execute(const cc_string* args, int argsCount) { + cc_string value = *args; + cc_string parts[2]; + int count; + + DrawOpCommand_ResetState(); + drawOp_name = "Replace"; + drawOp_Func = ReplaceCommand_Draw; + + DrawOpCommand_ExtractPersistArg(&value); + replace_target = -1; /* Default to replacing with currently held block */ + + if (!value.length) { + Chat_AddRaw("&eReplace: &cAt least one argument is required"); return; + } + count = String_UNSAFE_Split(&value, ' ', parts, 2); + + replace_source = DrawOpCommand_ParseBlock(&parts[0]); + if (replace_source == -1) return; + + if (count > 1) { + replace_target = DrawOpCommand_ParseBlock(&parts[1]); + if (replace_target == -1) return; + } + + DrawOpCommand_Begin(); +} + +static struct ChatCommand ReplaceCommand = { + "Replace", ReplaceCommand_Execute, + COMMAND_FLAG_SINGLEPLAYER_ONLY | COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client replace [source] [replacement] [persist]", + "&eReplaces all [source] blocks between two points with [replacement].", + "&eIf no [replacement] is given, replaces with your currently held block.", + "&e If persist is given and is \"yes\", then the command", + "&e will repeatedly replace, without needing to be typed in again.", + } +}; + + +/*########################################################################################################################* +*------------------------------------------------------TeleportCommand----------------------------------------------------* +*#########################################################################################################################*/ +static void TeleportCommand_Execute(const cc_string* args, int argsCount) { + struct Entity* e = &Entities.CurPlayer->Base; + struct LocationUpdate update; + Vec3 v; + + if (argsCount != 3) { + Chat_AddRaw("&e/client teleport: &cYou didn't specify X, Y and Z coordinates."); + return; + } + if (!Convert_ParseFloat(&args[0], &v.x) || !Convert_ParseFloat(&args[1], &v.y) || !Convert_ParseFloat(&args[2], &v.z)) { + Chat_AddRaw("&e/client teleport: &cCoordinates must be decimals"); + return; + } + + update.flags = LU_HAS_POS; + update.pos = v; + e->VTABLE->SetLocation(e, &update); +} + +static struct ChatCommand TeleportCommand = { + "TP", TeleportCommand_Execute, + COMMAND_FLAG_SINGLEPLAYER_ONLY, + { + "&a/client tp [x y z]", + "&eMoves you to the given coordinates.", + } +}; + + +/*########################################################################################################################* +*------------------------------------------------------BlockEditCommand----------------------------------------------------* +*#########################################################################################################################*/ +static cc_bool BlockEditCommand_GetInt(const cc_string* str, const char* name, int* value, int min, int max) { + if (!Convert_ParseInt(str, value)) { + Chat_Add1("&eBlockEdit: &e%c must be an integer", name); + return false; + } + + if (*value < min || *value > max) { + Chat_Add3("&eBlockEdit: &e%c must be between %i and %i", name, &min, &max); + return false; + } + return true; +} + +static cc_bool BlockEditCommand_GetTexture(const cc_string* str, int* tex) { + return BlockEditCommand_GetInt(str, "Texture", tex, 0, ATLAS1D_MAX_ATLASES - 1); +} + +static cc_bool BlockEditCommand_GetCoords(const cc_string* str, Vec3* coords) { + cc_string parts[3]; + IVec3 xyz; + + int numParts = String_UNSAFE_Split(str, ' ', parts, 3); + if (numParts != 3) { + Chat_AddRaw("&eBlockEdit: &c3 values are required for a coordinate (X Y Z)"); + return false; + } + + if (!BlockEditCommand_GetInt(&parts[0], "X coord", &xyz.x, -127, 127)) return false; + if (!BlockEditCommand_GetInt(&parts[1], "Y coord", &xyz.y, -127, 127)) return false; + if (!BlockEditCommand_GetInt(&parts[2], "Z coord", &xyz.z, -127, 127)) return false; + + coords->x = xyz.x / 16.0f; + coords->y = xyz.y / 16.0f; + coords->z = xyz.z / 16.0f; + return true; +} + +static cc_bool BlockEditCommand_GetBool(const cc_string* str, const char* name, cc_bool* value) { + if (String_CaselessEqualsConst(str, "true") || String_CaselessEqualsConst(str, "yes")) { + *value = true; + return true; + } + + if (String_CaselessEqualsConst(str, "false") || String_CaselessEqualsConst(str, "no")) { + *value = false; + return true; + } + + Chat_Add1("&eBlockEdit: &e%c must be either &ayes &eor &ano", name); + return false; +} + + +static void BlockEditCommand_Execute(const cc_string* args, int argsCount__) { + cc_string parts[3]; + cc_string* prop; + cc_string* value; + int argsCount, block, v; + cc_bool b; + Vec3 coords; + + if (String_CaselessEqualsConst(args, "properties")) { + Chat_AddRaw("&eEditable block properties:"); + Chat_AddRaw("&a name &e- Sets the name of the block"); + Chat_AddRaw("&a all &e- Sets textures on all six sides of the block"); + Chat_AddRaw("&a sides &e- Sets textures on four sides of the block"); + Chat_AddRaw("&a left/right/front/back/top/bottom &e- Sets one texture"); + Chat_AddRaw("&a collide &e- Sets collision mode of the block"); + Chat_AddRaw("&a drawmode &e- Sets draw mode of the block"); + Chat_AddRaw("&a min/max &e- Sets min/max corner coordinates of the block"); + Chat_AddRaw("&eSee &a/client blockedit properties 2 &efor more properties"); + return; + } + if (String_CaselessEqualsConst(args, "properties 2")) { + Chat_AddRaw("&eEditable block properties (page 2):"); + Chat_AddRaw("&a walksound &e- Sets walk/step sound of the block"); + Chat_AddRaw("&a breaksound &e- Sets break sound of the block"); + Chat_AddRaw("&a fullbright &e- Sets whether the block is fully lit"); + Chat_AddRaw("&a blockslight &e- Sets whether the block stops light"); + return; + } + + argsCount = String_UNSAFE_Split(args, ' ', parts, 3); + if (argsCount < 3) { + Chat_AddRaw("&eBlockEdit: &eThree arguments required &e(See &a/client help blockedit&e)"); + return; + } + + block = Block_Parse(&parts[0]); + if (block == -1) { + Chat_Add1("&eBlockEdit: &c\"%s\" is not a valid block name or ID", &parts[0]); + return; + } + + /* TODO: Redo as an array */ + prop = &parts[1]; + value = &parts[2]; + if (String_CaselessEqualsConst(prop, "name")) { + Block_SetName(block, value); + } else if (String_CaselessEqualsConst(prop, "all")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_SetSide(v, block); + Block_Tex(block, FACE_YMAX) = v; + Block_Tex(block, FACE_YMIN) = v; + } else if (String_CaselessEqualsConst(prop, "sides")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_SetSide(v, block); + } else if (String_CaselessEqualsConst(prop, "left")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_Tex(block, FACE_XMIN) = v; + } else if (String_CaselessEqualsConst(prop, "right")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_Tex(block, FACE_XMAX) = v; + } else if (String_CaselessEqualsConst(prop, "bottom")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_Tex(block, FACE_YMIN) = v; + } else if (String_CaselessEqualsConst(prop, "top")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_Tex(block, FACE_YMAX) = v; + } else if (String_CaselessEqualsConst(prop, "front")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_Tex(block, FACE_ZMIN) = v; + } else if (String_CaselessEqualsConst(prop, "back")) { + if (!BlockEditCommand_GetTexture(value, &v)) return; + + Block_Tex(block, FACE_ZMAX) = v; + } else if (String_CaselessEqualsConst(prop, "collide")) { + if (!BlockEditCommand_GetInt(value, "Collide mode", &v, 0, COLLIDE_CLIMB)) return; + + Blocks.Collide[block] = v; + } else if (String_CaselessEqualsConst(prop, "drawmode")) { + if (!BlockEditCommand_GetInt(value, "Draw mode", &v, 0, DRAW_SPRITE)) return; + + Blocks.Draw[block] = v; + } else if (String_CaselessEqualsConst(prop, "min")) { + if (!BlockEditCommand_GetCoords(value, &coords)) return; + + Blocks.MinBB[block] = coords; + } else if (String_CaselessEqualsConst(prop, "max")) { + if (!BlockEditCommand_GetCoords(value, &coords)) return; + + Blocks.MaxBB[block] = coords; + } else if (String_CaselessEqualsConst(prop, "walksound")) { + if (!BlockEditCommand_GetInt(value, "Sound", &v, 0, SOUND_COUNT - 1)) return; + + Blocks.StepSounds[block] = v; + } else if (String_CaselessEqualsConst(prop, "breaksound")) { + if (!BlockEditCommand_GetInt(value, "Sound", &v, 0, SOUND_COUNT - 1)) return; + + Blocks.DigSounds[block] = v; + } else if (String_CaselessEqualsConst(prop, "fullbright")) { + if (!BlockEditCommand_GetBool(value, "Full brightness", &b)) return; + //TODO: Fix this, brightness isn't just a bool anymore + Blocks.Brightness[block] = b; + } else if (String_CaselessEqualsConst(prop, "blockslight")) { + if (!BlockEditCommand_GetBool(value, "Blocks light", &b)) return; + + Blocks.BlocksLight[block] = b; + } else { + Chat_Add1("&eBlockEdit: &eUnknown property %s &e(See &a/client help blockedit&e)", prop); + return; + } + + Block_DefineCustom(block, false); +} + +static struct ChatCommand BlockEditCommand = { + "BlockEdit", BlockEditCommand_Execute, + COMMAND_FLAG_SINGLEPLAYER_ONLY | COMMAND_FLAG_UNSPLIT_ARGS, + { + "&a/client blockedit [block] [property] [value]", + "&eEdits the given property of the given block", + "&a/client blockedit properties", + "&eLists the editable block properties", + } +}; + + +/*########################################################################################################################* +*------------------------------------------------------Commands component-------------------------------------------------* +*#########################################################################################################################*/ +static void OnInit(void) { + Commands_Register(&GpuInfoCommand); + Commands_Register(&HelpCommand); + Commands_Register(&RenderTypeCommand); + Commands_Register(&ResolutionCommand); + Commands_Register(&ModelCommand); + Commands_Register(&TeleportCommand); + Commands_Register(&ClearDeniedCommand); + Commands_Register(&MotdCommand); + Commands_Register(&BlockEditCommand); + Commands_Register(&CuboidCommand); + Commands_Register(&ReplaceCommand); +} + +static void OnFree(void) { + cmds_head = NULL; +} + +struct IGameComponent Commands_Component = { + OnInit, /* Init */ + OnFree /* Free */ +}; |