mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-12 09:06:55 -04:00
Break up Chat module into Chat and Commands modules
This commit is contained in:
parent
27a583b521
commit
e58642e566
529
src/Chat.c
529
src/Chat.c
@ -1,21 +1,13 @@
|
||||
#include "Chat.h"
|
||||
#include "Commands.h"
|
||||
#include "String.h"
|
||||
#include "Stream.h"
|
||||
#include "Platform.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"
|
||||
|
||||
@ -252,510 +244,6 @@ void Chat_AddOf(const cc_string* text, int msgType) {
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*---------------------------------------------------------Commands--------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
#define COMMANDS_PREFIX "/client"
|
||||
#define COMMANDS_PREFIX_SPACE "/client "
|
||||
static struct ChatCommand* cmds_head;
|
||||
static struct ChatCommand* cmds_tail;
|
||||
|
||||
static cc_bool Commands_IsCommandPrefix(const cc_string* str) {
|
||||
static const cc_string prefixSpace = String_FromConst(COMMANDS_PREFIX_SPACE);
|
||||
static const cc_string prefix = String_FromConst(COMMANDS_PREFIX);
|
||||
|
||||
if (!str->length) return false;
|
||||
if (Server.IsSinglePlayer && str->buffer[0] == '/') return true;
|
||||
|
||||
return String_CaselessStarts(str, &prefixSpace)
|
||||
|| String_CaselessEquals(str, &prefix);
|
||||
}
|
||||
|
||||
void Commands_Register(struct ChatCommand* cmd) {
|
||||
LinkedList_Append(cmd, cmds_head, cmds_tail);
|
||||
}
|
||||
|
||||
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]");
|
||||
}
|
||||
|
||||
static void 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 = *input;
|
||||
|
||||
struct ChatCommand* cmd;
|
||||
int offset, count;
|
||||
cc_string name, value;
|
||||
cc_string args[50];
|
||||
|
||||
if (String_CaselessStarts(&text, &prefixSpace)) { /* /client command args */
|
||||
offset = prefixSpace.length;
|
||||
} else if (String_CaselessStarts(&text, &prefix)) { /* /clientcommand args */
|
||||
offset = prefix.length;
|
||||
} else { /* /command args */
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
text = String_UNSAFE_SubstringAt(&text, offset);
|
||||
/* Check for only / or /client */
|
||||
if (!text.length) { Commands_PrintDefault(); return; }
|
||||
|
||||
String_UNSAFE_Separate(&text, ' ', &name, &value);
|
||||
cmd = Commands_FindMatch(&name);
|
||||
if (!cmd) return;
|
||||
|
||||
if ((cmd->flags & COMMAND_FLAG_SINGLEPLAYER_ONLY) && !Server.IsSinglePlayer) {
|
||||
Chat_Add1("&e/client: \"&f%s&e\" can only be used in singleplayer.", &name);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*------------------------------------------------------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",
|
||||
&WindowInfo.Width, &DisplayInfo.ScaleX, &WindowInfo.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(&LocalPlayer_Instance.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",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-------------------------------------------------------CuboidCommand-----------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static int cuboid_block = -1;
|
||||
static IVec3 cuboid_mark1, cuboid_mark2;
|
||||
static cc_bool cuboid_persist, cuboid_hooked, cuboid_hasMark1;
|
||||
static const cc_string cuboid_msg = String_FromConst("&eCuboid: &fPlace or delete a block.");
|
||||
static const cc_string yes_string = String_FromConst("yes");
|
||||
|
||||
static void CuboidCommand_DoCuboid(void) {
|
||||
IVec3 min, max;
|
||||
BlockID toPlace;
|
||||
int x, y, z;
|
||||
|
||||
IVec3_Min(&min, &cuboid_mark1, &cuboid_mark2);
|
||||
IVec3_Max(&max, &cuboid_mark1, &cuboid_mark2);
|
||||
if (!World_Contains(min.X, min.Y, min.Z)) return;
|
||||
if (!World_Contains(max.X, max.Y, max.Z)) return;
|
||||
|
||||
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_BlockChanged(void* obj, IVec3 coords, BlockID old, BlockID now) {
|
||||
cc_string msg; char msgBuffer[STRING_SIZE];
|
||||
String_InitArray(msg, msgBuffer);
|
||||
|
||||
if (!cuboid_hasMark1) {
|
||||
cuboid_mark1 = coords;
|
||||
cuboid_hasMark1 = true;
|
||||
Game_UpdateBlock(coords.X, coords.Y, coords.Z, old);
|
||||
|
||||
String_Format3(&msg, "&eCuboid: &fMark 1 placed at (%i, %i, %i), place mark 2.", &coords.X, &coords.Y, &coords.Z);
|
||||
Chat_AddOf(&msg, MSG_TYPE_CLIENTSTATUS_1);
|
||||
} else {
|
||||
cuboid_mark2 = coords;
|
||||
CuboidCommand_DoCuboid();
|
||||
|
||||
if (!cuboid_persist) {
|
||||
Event_Unregister_(&UserEvents.BlockChanged, NULL, CuboidCommand_BlockChanged);
|
||||
cuboid_hooked = false;
|
||||
Chat_AddOf(&String_Empty, MSG_TYPE_CLIENTSTATUS_1);
|
||||
} else {
|
||||
cuboid_hasMark1 = false;
|
||||
Chat_AddOf(&cuboid_msg, MSG_TYPE_CLIENTSTATUS_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static cc_bool CuboidCommand_ParseArgs(const cc_string* args) {
|
||||
cc_string value = *args;
|
||||
int block;
|
||||
|
||||
/* Check for /cuboid [block] yes */
|
||||
if (String_CaselessEnds(&value, &yes_string)) {
|
||||
cuboid_persist = true;
|
||||
value.length -= 3;
|
||||
String_UNSAFE_TrimEnd(&value);
|
||||
|
||||
/* special case "/cuboid yes" */
|
||||
if (!value.length) return true;
|
||||
}
|
||||
|
||||
block = Block_Parse(&value);
|
||||
if (block == -1) {
|
||||
Chat_Add1("&eCuboid: &c\"%s\" is not a valid block name or id.", &value); return false;
|
||||
}
|
||||
|
||||
if (block > Game_Version.MaxCoreBlock && !Block_IsCustomDefined(block)) {
|
||||
Chat_Add1("&eCuboid: &cThere is no block with id \"%s\".", &value); return false;
|
||||
}
|
||||
|
||||
cuboid_block = block;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CuboidCommand_Execute(const cc_string* args, int argsCount) {
|
||||
if (cuboid_hooked) {
|
||||
Event_Unregister_(&UserEvents.BlockChanged, NULL, CuboidCommand_BlockChanged);
|
||||
cuboid_hooked = false;
|
||||
}
|
||||
|
||||
cuboid_block = -1;
|
||||
cuboid_hasMark1 = false;
|
||||
cuboid_persist = false;
|
||||
if (argsCount && !CuboidCommand_ParseArgs(args)) return;
|
||||
|
||||
Chat_AddOf(&cuboid_msg, MSG_TYPE_CLIENTSTATUS_1);
|
||||
Event_Register_(&UserEvents.BlockChanged, NULL, CuboidCommand_BlockChanged);
|
||||
cuboid_hooked = true;
|
||||
}
|
||||
|
||||
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.",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*------------------------------------------------------TeleportCommand----------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static void TeleportCommand_Execute(const cc_string* args, int argsCount) {
|
||||
struct Entity* e = &LocalPlayer_Instance.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 void BlockEditCommand_Execute(const cc_string* args, int argsCount__) {
|
||||
cc_string parts[3];
|
||||
cc_string* prop;
|
||||
cc_string* value;
|
||||
int argsCount, block, v;
|
||||
|
||||
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");
|
||||
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 {
|
||||
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",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-------------------------------------------------------Generic chat------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
@ -775,24 +263,12 @@ void Chat_Send(const cc_string* text, cc_bool logUsage) {
|
||||
Event_RaiseChat(&ChatEvents.ChatSending, text, 0);
|
||||
if (logUsage) LogInputUsage(text);
|
||||
|
||||
if (Commands_IsCommandPrefix(text)) {
|
||||
Commands_Execute(text);
|
||||
} else {
|
||||
if (!Commands_Execute(text)) {
|
||||
Server.SendChat(text);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnInit(void) {
|
||||
Commands_Register(&GpuInfoCommand);
|
||||
Commands_Register(&HelpCommand);
|
||||
Commands_Register(&RenderTypeCommand);
|
||||
Commands_Register(&ResolutionCommand);
|
||||
Commands_Register(&ModelCommand);
|
||||
Commands_Register(&CuboidCommand);
|
||||
Commands_Register(&TeleportCommand);
|
||||
Commands_Register(&ClearDeniedCommand);
|
||||
Commands_Register(&BlockEditCommand);
|
||||
|
||||
#if defined CC_BUILD_MOBILE || defined CC_BUILD_WEB
|
||||
/* Better to not log chat by default on mobile/web, */
|
||||
/* since it's not easily visible to end users */
|
||||
@ -823,7 +299,6 @@ static void OnReset(void) {
|
||||
static void OnFree(void) {
|
||||
CloseLogFile();
|
||||
ClearCPEMessages();
|
||||
cmds_head = NULL;
|
||||
|
||||
ClearChatLogs();
|
||||
StringsBuffer_Clear(&Chat_InputLog);
|
||||
|
21
src/Chat.h
21
src/Chat.h
@ -40,30 +40,11 @@ extern cc_string Chat_Status[5], Chat_BottomRight[3], Chat_ClientStatus[2];
|
||||
extern cc_string Chat_Announcement, Chat_BigAnnouncement, Chat_SmallAnnouncement;
|
||||
/* All chat messages received */
|
||||
extern struct StringsBuffer Chat_Log;
|
||||
/* All chat entered by the user */
|
||||
/* All chat input entered by the user */
|
||||
extern struct StringsBuffer Chat_InputLog;
|
||||
/* Whether chat messages are logged to disc */
|
||||
extern cc_bool Chat_Logging;
|
||||
|
||||
/* This command is only available in singleplayer */
|
||||
#define COMMAND_FLAG_SINGLEPLAYER_ONLY 0x01
|
||||
/* args is passed as a single string instead of being split by spaces */
|
||||
#define COMMAND_FLAG_UNSPLIT_ARGS 0x02
|
||||
|
||||
struct ChatCommand;
|
||||
/* Represents a client-side command/action. */
|
||||
struct ChatCommand {
|
||||
const char* name; /* Full name of this command */
|
||||
/* Function pointer for the actual action the command performs */
|
||||
void (*Execute)(const cc_string* args, int argsCount);
|
||||
cc_uint8 flags; /* Flags for handling this command (see COMMAND_FLAG defines) */
|
||||
const char* help[5]; /* Messages to show when a player uses /help on this command */
|
||||
struct ChatCommand* next; /* Next command in linked-list of client commands */
|
||||
};
|
||||
/* Registers a client-side command, allowing it to be used with /client [cmd name] */
|
||||
CC_API void Commands_Register( struct ChatCommand* cmd);
|
||||
typedef void (*FP_Commands_Register)(struct ChatCommand* cmd);
|
||||
|
||||
/* Sets the name of log file (no .txt, so e.g. just "singleplayer") */
|
||||
/* NOTE: This can only be set once. */
|
||||
void Chat_SetLogName(const cc_string* name);
|
||||
|
@ -373,6 +373,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Animations.h" />
|
||||
<ClInclude Include="Commands.h" />
|
||||
<ClInclude Include="EntityRenderers.h" />
|
||||
<ClInclude Include="LBackend.h" />
|
||||
<ClInclude Include="Http.h" />
|
||||
@ -447,6 +448,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Animations.c" />
|
||||
<ClCompile Include="Commands.c" />
|
||||
<ClCompile Include="EntityRenderers.c" />
|
||||
<ClCompile Include="GameVersion.c" />
|
||||
<ClCompile Include="Graphics_3DS.c" />
|
||||
|
@ -345,6 +345,9 @@
|
||||
<ClInclude Include="EntityRenderers.h">
|
||||
<Filter>Header Files\Entities</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Commands.h">
|
||||
<Filter>Header Files\Game</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="String.c">
|
||||
@ -698,6 +701,9 @@
|
||||
<ClCompile Include="Graphics_PS2.c">
|
||||
<Filter>Source Files\Graphics</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Commands.c">
|
||||
<Filter>Source Files\Game</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="..\misc\windows\CCicon.rc">
|
||||
|
544
src/Commands.c
Normal file
544
src/Commands.c
Normal file
@ -0,0 +1,544 @@
|
||||
#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]");
|
||||
}
|
||||
|
||||
static cc_bool IsSingleplayerCommandPrefix(const cc_string* input) {
|
||||
return input->length && Server.IsSinglePlayer && input->buffer[0] == '/';
|
||||
}
|
||||
|
||||
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_CaselessStarts(input, &prefix)) { /* /clientcommand args */
|
||||
offset = prefix.length;
|
||||
} else if (IsSingleplayerCommandPrefix(input)) { /* /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",
|
||||
&WindowInfo.Width, &DisplayInfo.ScaleX, &WindowInfo.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(&LocalPlayer_Instance.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",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-------------------------------------------------------CuboidCommand-----------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static int cuboid_block = -1;
|
||||
static IVec3 cuboid_mark1, cuboid_mark2;
|
||||
static cc_bool cuboid_persist, cuboid_hooked, cuboid_hasMark1;
|
||||
static const cc_string cuboid_msg = String_FromConst("&eCuboid: &fPlace or delete a block.");
|
||||
static const cc_string yes_string = String_FromConst("yes");
|
||||
|
||||
static void CuboidCommand_DoCuboid(void) {
|
||||
IVec3 min, max;
|
||||
BlockID toPlace;
|
||||
int x, y, z;
|
||||
|
||||
IVec3_Min(&min, &cuboid_mark1, &cuboid_mark2);
|
||||
IVec3_Max(&max, &cuboid_mark1, &cuboid_mark2);
|
||||
if (!World_Contains(min.X, min.Y, min.Z)) return;
|
||||
if (!World_Contains(max.X, max.Y, max.Z)) return;
|
||||
|
||||
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_BlockChanged(void* obj, IVec3 coords, BlockID old, BlockID now) {
|
||||
cc_string msg; char msgBuffer[STRING_SIZE];
|
||||
String_InitArray(msg, msgBuffer);
|
||||
|
||||
if (!cuboid_hasMark1) {
|
||||
cuboid_mark1 = coords;
|
||||
cuboid_hasMark1 = true;
|
||||
Game_UpdateBlock(coords.X, coords.Y, coords.Z, old);
|
||||
|
||||
String_Format3(&msg, "&eCuboid: &fMark 1 placed at (%i, %i, %i), place mark 2.", &coords.X, &coords.Y, &coords.Z);
|
||||
Chat_AddOf(&msg, MSG_TYPE_CLIENTSTATUS_1);
|
||||
} else {
|
||||
cuboid_mark2 = coords;
|
||||
CuboidCommand_DoCuboid();
|
||||
|
||||
if (!cuboid_persist) {
|
||||
Event_Unregister_(&UserEvents.BlockChanged, NULL, CuboidCommand_BlockChanged);
|
||||
cuboid_hooked = false;
|
||||
Chat_AddOf(&String_Empty, MSG_TYPE_CLIENTSTATUS_1);
|
||||
} else {
|
||||
cuboid_hasMark1 = false;
|
||||
Chat_AddOf(&cuboid_msg, MSG_TYPE_CLIENTSTATUS_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static cc_bool CuboidCommand_ParseArgs(const cc_string* args) {
|
||||
cc_string value = *args;
|
||||
int block;
|
||||
|
||||
/* Check for /cuboid [block] yes */
|
||||
if (String_CaselessEnds(&value, &yes_string)) {
|
||||
cuboid_persist = true;
|
||||
value.length -= 3;
|
||||
String_UNSAFE_TrimEnd(&value);
|
||||
|
||||
/* special case "/cuboid yes" */
|
||||
if (!value.length) return true;
|
||||
}
|
||||
|
||||
block = Block_Parse(&value);
|
||||
if (block == -1) {
|
||||
Chat_Add1("&eCuboid: &c\"%s\" is not a valid block name or id.", &value); return false;
|
||||
}
|
||||
|
||||
if (block > Game_Version.MaxCoreBlock && !Block_IsCustomDefined(block)) {
|
||||
Chat_Add1("&eCuboid: &cThere is no block with id \"%s\".", &value); return false;
|
||||
}
|
||||
|
||||
cuboid_block = block;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CuboidCommand_Execute(const cc_string* args, int argsCount) {
|
||||
if (cuboid_hooked) {
|
||||
Event_Unregister_(&UserEvents.BlockChanged, NULL, CuboidCommand_BlockChanged);
|
||||
cuboid_hooked = false;
|
||||
}
|
||||
|
||||
cuboid_block = -1;
|
||||
cuboid_hasMark1 = false;
|
||||
cuboid_persist = false;
|
||||
if (argsCount && !CuboidCommand_ParseArgs(args)) return;
|
||||
|
||||
Chat_AddOf(&cuboid_msg, MSG_TYPE_CLIENTSTATUS_1);
|
||||
Event_Register_(&UserEvents.BlockChanged, NULL, CuboidCommand_BlockChanged);
|
||||
cuboid_hooked = true;
|
||||
}
|
||||
|
||||
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.",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*------------------------------------------------------TeleportCommand----------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static void TeleportCommand_Execute(const cc_string* args, int argsCount) {
|
||||
struct Entity* e = &LocalPlayer_Instance.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 void BlockEditCommand_Execute(const cc_string* args, int argsCount__) {
|
||||
cc_string parts[3];
|
||||
cc_string* prop;
|
||||
cc_string* value;
|
||||
int argsCount, block, v;
|
||||
|
||||
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");
|
||||
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 {
|
||||
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(&CuboidCommand);
|
||||
Commands_Register(&TeleportCommand);
|
||||
Commands_Register(&ClearDeniedCommand);
|
||||
Commands_Register(&BlockEditCommand);
|
||||
}
|
||||
|
||||
static void OnFree(void) {
|
||||
cmds_head = NULL;
|
||||
}
|
||||
|
||||
struct IGameComponent Commands_Component = {
|
||||
OnInit, /* Init */
|
||||
OnFree /* Free */
|
||||
};
|
31
src/Commands.h
Normal file
31
src/Commands.h
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef CC_COMMANDS_H
|
||||
#define CC_COMMANDS_H
|
||||
#include "Core.h"
|
||||
/* Executes actions in response to certain chat input
|
||||
Copyright 2014-2023 ClassiCube | Licensed under BSD-3
|
||||
*/
|
||||
struct IGameComponent;
|
||||
extern struct IGameComponent Commands_Component;
|
||||
|
||||
cc_bool Commands_Execute(const cc_string* input);
|
||||
|
||||
/* This command is only available in singleplayer */
|
||||
#define COMMAND_FLAG_SINGLEPLAYER_ONLY 0x01
|
||||
/* args is passed as a single string instead of being split by spaces */
|
||||
#define COMMAND_FLAG_UNSPLIT_ARGS 0x02
|
||||
|
||||
struct ChatCommand;
|
||||
/* Represents a client-side command/action */
|
||||
struct ChatCommand {
|
||||
const char* name; /* Full name of this command */
|
||||
/* Function pointer for the actual action the command performs */
|
||||
void (*Execute)(const cc_string* args, int argsCount);
|
||||
cc_uint8 flags; /* Flags for handling this command (see COMMAND_FLAG defines) */
|
||||
const char* help[5]; /* Messages to show when a player uses /help on this command */
|
||||
struct ChatCommand* next; /* Next command in linked-list of client commands */
|
||||
};
|
||||
|
||||
/* Registers a client-side command, allowing it to be used with /client [cmd name] */
|
||||
CC_API void Commands_Register( struct ChatCommand* cmd);
|
||||
typedef void (*FP_Commands_Register)(struct ChatCommand* cmd);
|
||||
#endif
|
@ -15,6 +15,7 @@
|
||||
#include "Logger.h"
|
||||
#include "Entity.h"
|
||||
#include "Chat.h"
|
||||
#include "Commands.h"
|
||||
#include "Drawer2D.h"
|
||||
#include "Model.h"
|
||||
#include "Particle.h"
|
||||
@ -410,6 +411,7 @@ static void Game_Load(void) {
|
||||
Game_AddComponent(&SystemFonts_Component);
|
||||
|
||||
Game_AddComponent(&Chat_Component);
|
||||
Game_AddComponent(&Commands_Component);
|
||||
Game_AddComponent(&Particles_Component);
|
||||
Game_AddComponent(&TabList_Component);
|
||||
Game_AddComponent(&Models_Component);
|
||||
|
Loading…
x
Reference in New Issue
Block a user