Skip to content

Commit

Permalink
add client side command argument suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
user622628252416 committed Nov 8, 2024
1 parent 5b1cece commit 5f466cc
Show file tree
Hide file tree
Showing 18 changed files with 522 additions and 84 deletions.
297 changes: 261 additions & 36 deletions pumpkin-protocol/src/client/play/c_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use pumpkin_macros::client_packet;

use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt};

/// this is a bit ugly, but ClientPacket depends on CommandTree in pumpkin(bin), from where ClientPacket cannot be implemented
#[client_packet("play:commands")]
pub struct CCommands<'a> {
pub nodes: Vec<ProtoNode<'a>>,
Expand Down Expand Up @@ -32,44 +31,26 @@ pub struct ProtoNode<'a> {
pub node_type: ProtoNodeType<'a>,
}

#[derive(Debug)]
pub enum ProtoNodeType<'a> {
Root,
Literal { name: &'a str, is_executable: bool },
Argument { name: &'a str, is_executable: bool },
Literal {
name: &'a str,
is_executable: bool,
},
Argument {
name: &'a str,
is_executable: bool,
parser: ProtoCmdArgParser<'a>,
override_suggestion_type: Option<ProtoCmdArgSuggestionType>,
},
}

impl<'a> ProtoNode<'a> {
const FLAG_IS_EXECUTABLE: i8 = 4;
const FLAG_HAS_REDIRECT: i8 = 8;
const FLAG_HAS_SUGGESTION_TYPE: i8 = 16;

pub fn new_root(children: Vec<VarInt>) -> Self {
Self {
children,
node_type: ProtoNodeType::Root,
}
}

pub fn new_literal(children: Vec<VarInt>, name: &'a str) -> Self {
Self {
children,
node_type: ProtoNodeType::Literal {
name,
is_executable: true,
},
}
}

pub fn new_argument(children: Vec<VarInt>, name: &'a str) -> Self {
Self {
children,
node_type: ProtoNodeType::Argument {
name,
is_executable: true, // todo
},
}
}

/// https://wiki.vg/Command_Data
pub fn write_to(&self, bytebuf: &mut ByteBuffer) {
// flags
Expand All @@ -88,8 +69,13 @@ impl<'a> ProtoNode<'a> {
ProtoNodeType::Argument {
name: _,
is_executable,
parser: _,
override_suggestion_type: override_suggestion_tpye,
} => {
let mut n = 2 | Self::FLAG_HAS_SUGGESTION_TYPE;
let mut n = 2;
if override_suggestion_tpye.is_some() {
n |= Self::FLAG_HAS_SUGGESTION_TYPE
}
if is_executable {
n |= Self::FLAG_IS_EXECUTABLE
}
Expand All @@ -115,14 +101,253 @@ impl<'a> ProtoNode<'a> {
}

// parser id + properties
if let ProtoNodeType::Argument { .. } = self.node_type {
bytebuf.put_var_int(&5.into()); // string arg type has id 5
bytebuf.put_var_int(&0.into()); // match single word only
if let ProtoNodeType::Argument {
name: _,
is_executable: _,
parser,
override_suggestion_type: _,
} = &self.node_type
{
parser.write_to_buffer(bytebuf)
}

// suggestion type
if flags & Self::FLAG_HAS_SUGGESTION_TYPE != 0 {
bytebuf.put_string("minecraft:ask_server");
match &self.node_type {
ProtoNodeType::Argument { name: _, is_executable: _, parser: _, override_suggestion_type } => {
// suggestion type
let suggestion_type = &override_suggestion_type.expect("ProtoNode::FLAG_HAS_SUGGESTION_TYPE should only be set if override_suggestion_type is not None.");
bytebuf.put_string(suggestion_type.identifier());
},
_ => unimplemented!("ProtoNode::FLAG_HAS_SUGGESTION_TYPE is only implemented for ProtoNodeType::Argument"),
}
}
}
}

/// see https://wiki.vg/Command_Data for meaning of flags/enums/etc.
#[derive(Debug)]
pub enum ProtoCmdArgParser<'a> {
Bool,
Float { min: Option<f32>, max: Option<f32> },
Double { min: Option<f64>, max: Option<f64> },
Integer { min: Option<i32>, max: Option<i32> },
Long { min: Option<i64>, max: Option<i64> },
String(StringProtoArgBehavior),
Entity { flags: i8 },
GameProfile,
BlockPos,
ColumnPos,
Vec3,
Vec2,
BlockState,
BlockPredicate,
ItemStack,
ItemPredicate,
Color,
Component,
Style,
Message,
Nbt,
NbtTag,
NbtPath,
Objective,
ObjectiveCriteria,
Operation,
Particle,
Angle,
Rotation,
ScoreboardSlot,
ScoreHolder { flags: i8 },
Swizzle,
Team,
ItemSlot,
ResourceLocation,
Function,
EntityAnchor,
IntRange,
FloatRange,
Dimension,
Gamemode,
Time { min: i32 },
ResourceOrTag { identifier: &'a str },
ResourceOrTagKey { identifier: &'a str },
Resource { identifier: &'a str },
ResourceKey { identifier: &'a str },
TemplateMirror,
TemplateRotation,
Heightmap,
Uuid,
}

impl<'a> ProtoCmdArgParser<'a> {
pub const ENTITY_FLAG_ONLY_SINGLE: i8 = 1;
pub const ENTITY_FLAG_PLAYERS_ONLY: i8 = 2;

pub const SCORE_HOLDER_FLAG_ALLOW_MULTIPLE: i8 = 1;

pub fn write_to_buffer(&self, bytebuf: &mut ByteBuffer) {
match self {
Self::Bool => bytebuf.put_var_int(&0.into()),
Self::Float { min, max } => Self::write_number_arg(&1.into(), *min, *max, bytebuf),
Self::Double { min, max } => Self::write_number_arg(&2.into(), *min, *max, bytebuf),
Self::Integer { min, max } => Self::write_number_arg(&3.into(), *min, *max, bytebuf),
Self::Long { min, max } => Self::write_number_arg(&4.into(), *min, *max, bytebuf),
Self::String(behavior) => {
bytebuf.put_var_int(&5.into());
let i = match behavior {
StringProtoArgBehavior::SingleWord => 0,
StringProtoArgBehavior::QuotablePhrase => 1,
StringProtoArgBehavior::GreedyPhrase => 2,
};
bytebuf.put_var_int(&i.into());
}
Self::Entity { flags } => Self::write_with_flags(&6.into(), *flags, bytebuf),
Self::GameProfile => bytebuf.put_var_int(&7.into()),
Self::BlockPos => bytebuf.put_var_int(&8.into()),
Self::ColumnPos => bytebuf.put_var_int(&9.into()),
Self::Vec3 => bytebuf.put_var_int(&10.into()),
Self::Vec2 => bytebuf.put_var_int(&11.into()),
Self::BlockState => bytebuf.put_var_int(&12.into()),
Self::BlockPredicate => bytebuf.put_var_int(&13.into()),
Self::ItemStack => bytebuf.put_var_int(&14.into()),
Self::ItemPredicate => bytebuf.put_var_int(&15.into()),
Self::Color => bytebuf.put_var_int(&16.into()),
Self::Component => bytebuf.put_var_int(&17.into()),
Self::Style => bytebuf.put_var_int(&18.into()),
Self::Message => bytebuf.put_var_int(&19.into()),
Self::Nbt => bytebuf.put_var_int(&20.into()),
Self::NbtTag => bytebuf.put_var_int(&21.into()),
Self::NbtPath => bytebuf.put_var_int(&22.into()),
Self::Objective => bytebuf.put_var_int(&23.into()),
Self::ObjectiveCriteria => bytebuf.put_var_int(&24.into()),
Self::Operation => bytebuf.put_var_int(&25.into()),
Self::Particle => bytebuf.put_var_int(&26.into()),
Self::Angle => bytebuf.put_var_int(&27.into()),
Self::Rotation => bytebuf.put_var_int(&28.into()),
Self::ScoreboardSlot => bytebuf.put_var_int(&29.into()),
Self::ScoreHolder { flags } => Self::write_with_flags(&30.into(), *flags, bytebuf),
Self::Swizzle => bytebuf.put_var_int(&31.into()),
Self::Team => bytebuf.put_var_int(&32.into()),
Self::ItemSlot => bytebuf.put_var_int(&33.into()),
Self::ResourceLocation => bytebuf.put_var_int(&34.into()),
Self::Function => bytebuf.put_var_int(&35.into()),
Self::EntityAnchor => bytebuf.put_var_int(&36.into()),
Self::IntRange => bytebuf.put_var_int(&37.into()),
Self::FloatRange => bytebuf.put_var_int(&38.into()),
Self::Dimension => bytebuf.put_var_int(&39.into()),
Self::Gamemode => bytebuf.put_var_int(&40.into()),
Self::Time { min } => {
bytebuf.put_var_int(&41.into());
bytebuf.put_i32(*min);
}
Self::ResourceOrTag { identifier } => {
Self::write_with_identifier(&42.into(), identifier, bytebuf)
}
Self::ResourceOrTagKey { identifier } => {
Self::write_with_identifier(&43.into(), identifier, bytebuf)
}
Self::Resource { identifier } => {
Self::write_with_identifier(&44.into(), identifier, bytebuf)
}
Self::ResourceKey { identifier } => {
Self::write_with_identifier(&45.into(), identifier, bytebuf)
}
Self::TemplateMirror => bytebuf.put_var_int(&46.into()),
Self::TemplateRotation => bytebuf.put_var_int(&47.into()),
Self::Heightmap => bytebuf.put_var_int(&48.into()),
Self::Uuid => bytebuf.put_var_int(&49.into()),
}
}

fn write_number_arg<T: NumberCmdArg>(
id: &VarInt,
min: Option<T>,
max: Option<T>,
bytebuf: &mut ByteBuffer,
) {
let mut flags: i8 = 0;
if min.is_some() {
flags |= 1
}
if max.is_some() {
flags |= 2
}

bytebuf.put_var_int(id);

bytebuf.put_i8(flags);
if let Some(min) = min {
min.write(bytebuf);
}
if let Some(max) = max {
max.write(bytebuf);
}
}

fn write_with_flags(id: &VarInt, flags: i8, bytebuf: &mut ByteBuffer) {
bytebuf.put_var_int(id);

bytebuf.put_i8(flags);
}

fn write_with_identifier(id: &VarInt, extra_identifier: &str, bytebuf: &mut ByteBuffer) {
bytebuf.put_var_int(id);

bytebuf.put_string(extra_identifier);
}
}

#[derive(Debug, Clone, Copy)]
pub enum StringProtoArgBehavior {
SingleWord,
QuotablePhrase,
/// does not stop after a space
GreedyPhrase,
}

trait NumberCmdArg {
fn write(self, bytebuf: &mut ByteBuffer);
}

impl NumberCmdArg for f32 {
fn write(self, bytebuf: &mut ByteBuffer) {
bytebuf.put_f32(self);
}
}

impl NumberCmdArg for f64 {
fn write(self, bytebuf: &mut ByteBuffer) {
bytebuf.put_f64(self);
}
}

impl NumberCmdArg for i32 {
fn write(self, bytebuf: &mut ByteBuffer) {
bytebuf.put_i32(self);
}
}

impl NumberCmdArg for i64 {
fn write(self, bytebuf: &mut ByteBuffer) {
bytebuf.put_i64(self);
}
}

#[derive(Debug, Clone, Copy)]
pub enum ProtoCmdArgSuggestionType {
AskServer,
AllRecipes,
AvailableSounds,
SummonableEntities,
}

impl ProtoCmdArgSuggestionType {
fn identifier(&self) -> &'static str {
match self {
Self::AskServer => "minecraft:ask_server",
Self::AllRecipes => "minecraft:all_recipes",
Self::AvailableSounds => "minecraft:available_sounds",
Self::SummonableEntities => "minecraft:summonable_entities",
}
}
}
Loading

0 comments on commit 5f466cc

Please sign in to comment.