mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-13 09:35:23 -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 "Chat.h"
|
||||||
|
#include "Commands.h"
|
||||||
#include "String.h"
|
#include "String.h"
|
||||||
#include "Stream.h"
|
#include "Stream.h"
|
||||||
#include "Platform.h"
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "Server.h"
|
#include "Server.h"
|
||||||
#include "World.h"
|
|
||||||
#include "Inventory.h"
|
|
||||||
#include "Entity.h"
|
|
||||||
#include "Window.h"
|
|
||||||
#include "Graphics.h"
|
|
||||||
#include "Funcs.h"
|
#include "Funcs.h"
|
||||||
#include "Block.h"
|
|
||||||
#include "EnvRenderer.h"
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "TexturePack.h"
|
|
||||||
#include "Options.h"
|
#include "Options.h"
|
||||||
#include "Drawer2D.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------------------------------------------------------*
|
*-------------------------------------------------------Generic chat------------------------------------------------------*
|
||||||
*#########################################################################################################################*/
|
*#########################################################################################################################*/
|
||||||
@ -775,24 +263,12 @@ void Chat_Send(const cc_string* text, cc_bool logUsage) {
|
|||||||
Event_RaiseChat(&ChatEvents.ChatSending, text, 0);
|
Event_RaiseChat(&ChatEvents.ChatSending, text, 0);
|
||||||
if (logUsage) LogInputUsage(text);
|
if (logUsage) LogInputUsage(text);
|
||||||
|
|
||||||
if (Commands_IsCommandPrefix(text)) {
|
if (!Commands_Execute(text)) {
|
||||||
Commands_Execute(text);
|
|
||||||
} else {
|
|
||||||
Server.SendChat(text);
|
Server.SendChat(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnInit(void) {
|
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
|
#if defined CC_BUILD_MOBILE || defined CC_BUILD_WEB
|
||||||
/* Better to not log chat by default on mobile/web, */
|
/* Better to not log chat by default on mobile/web, */
|
||||||
/* since it's not easily visible to end users */
|
/* since it's not easily visible to end users */
|
||||||
@ -823,7 +299,6 @@ static void OnReset(void) {
|
|||||||
static void OnFree(void) {
|
static void OnFree(void) {
|
||||||
CloseLogFile();
|
CloseLogFile();
|
||||||
ClearCPEMessages();
|
ClearCPEMessages();
|
||||||
cmds_head = NULL;
|
|
||||||
|
|
||||||
ClearChatLogs();
|
ClearChatLogs();
|
||||||
StringsBuffer_Clear(&Chat_InputLog);
|
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;
|
extern cc_string Chat_Announcement, Chat_BigAnnouncement, Chat_SmallAnnouncement;
|
||||||
/* All chat messages received */
|
/* All chat messages received */
|
||||||
extern struct StringsBuffer Chat_Log;
|
extern struct StringsBuffer Chat_Log;
|
||||||
/* All chat entered by the user */
|
/* All chat input entered by the user */
|
||||||
extern struct StringsBuffer Chat_InputLog;
|
extern struct StringsBuffer Chat_InputLog;
|
||||||
/* Whether chat messages are logged to disc */
|
/* Whether chat messages are logged to disc */
|
||||||
extern cc_bool Chat_Logging;
|
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") */
|
/* Sets the name of log file (no .txt, so e.g. just "singleplayer") */
|
||||||
/* NOTE: This can only be set once. */
|
/* NOTE: This can only be set once. */
|
||||||
void Chat_SetLogName(const cc_string* name);
|
void Chat_SetLogName(const cc_string* name);
|
||||||
|
@ -373,6 +373,7 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Animations.h" />
|
<ClInclude Include="Animations.h" />
|
||||||
|
<ClInclude Include="Commands.h" />
|
||||||
<ClInclude Include="EntityRenderers.h" />
|
<ClInclude Include="EntityRenderers.h" />
|
||||||
<ClInclude Include="LBackend.h" />
|
<ClInclude Include="LBackend.h" />
|
||||||
<ClInclude Include="Http.h" />
|
<ClInclude Include="Http.h" />
|
||||||
@ -447,6 +448,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Animations.c" />
|
<ClCompile Include="Animations.c" />
|
||||||
|
<ClCompile Include="Commands.c" />
|
||||||
<ClCompile Include="EntityRenderers.c" />
|
<ClCompile Include="EntityRenderers.c" />
|
||||||
<ClCompile Include="GameVersion.c" />
|
<ClCompile Include="GameVersion.c" />
|
||||||
<ClCompile Include="Graphics_3DS.c" />
|
<ClCompile Include="Graphics_3DS.c" />
|
||||||
|
@ -345,6 +345,9 @@
|
|||||||
<ClInclude Include="EntityRenderers.h">
|
<ClInclude Include="EntityRenderers.h">
|
||||||
<Filter>Header Files\Entities</Filter>
|
<Filter>Header Files\Entities</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Commands.h">
|
||||||
|
<Filter>Header Files\Game</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="String.c">
|
<ClCompile Include="String.c">
|
||||||
@ -698,6 +701,9 @@
|
|||||||
<ClCompile Include="Graphics_PS2.c">
|
<ClCompile Include="Graphics_PS2.c">
|
||||||
<Filter>Source Files\Graphics</Filter>
|
<Filter>Source Files\Graphics</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="Commands.c">
|
||||||
|
<Filter>Source Files\Game</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="..\misc\windows\CCicon.rc">
|
<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 "Logger.h"
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Chat.h"
|
#include "Chat.h"
|
||||||
|
#include "Commands.h"
|
||||||
#include "Drawer2D.h"
|
#include "Drawer2D.h"
|
||||||
#include "Model.h"
|
#include "Model.h"
|
||||||
#include "Particle.h"
|
#include "Particle.h"
|
||||||
@ -410,6 +411,7 @@ static void Game_Load(void) {
|
|||||||
Game_AddComponent(&SystemFonts_Component);
|
Game_AddComponent(&SystemFonts_Component);
|
||||||
|
|
||||||
Game_AddComponent(&Chat_Component);
|
Game_AddComponent(&Chat_Component);
|
||||||
|
Game_AddComponent(&Commands_Component);
|
||||||
Game_AddComponent(&Particles_Component);
|
Game_AddComponent(&Particles_Component);
|
||||||
Game_AddComponent(&TabList_Component);
|
Game_AddComponent(&TabList_Component);
|
||||||
Game_AddComponent(&Models_Component);
|
Game_AddComponent(&Models_Component);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user