GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Commands by Iaiao · Pull Request #487 · feather-rs/feather

Conversation

@Iaiao

@Iaiao Iaiao commented Sep 26, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Commands

Status

  • Ready
  • Development
  • Hold

Description

This PR adds commands. I've started working on this with lieutenant, but I did't like regex because it's hard to write regexps for something like @e[advancements={"adv":{criteria:"true"}}], it required some refactoring to make compatible structure for Declare Commands packet, CommandCtx: Send + Sync wouldn't allow LifetimelessMut<Game> and has no concept of tab completion, so I decided to make my own dispatcher instead of rewriting 95% of the existing code.

Things to do:

  • Vanilla commands
    • /help (/?)
    • /advancement
    • /attribute
    • /ban
    • /ban-ip
    • /banlist
    • /bossbar
    • /clear
    • /clone
    • /data
    • /datapack
    • /debug
    • /defaultgamemode
    • /deop
    • /difficulty
    • /effect
    • /enchant
    • /execute
    • /experience (/xp)
    • /fill
    • /forceload
    • /function
    • /gamemode
    • /gamerule
    • /give
    • /item
    • /kick
    • /kill
    • /list
    • /locate
    • /locatebiome
    • /loot
    • /me
    • /op
    • /pardon
    • /pardon-ip
    • /particle
    • /perf
    • /playsound
    • /recipe
    • /reload
    • /save-all
    • /save-off
    • /save-on
    • /say
    • /schedule
    • /scoreboard
    • /seed
    • /setblock
    • /setidletimeout
    • /setworldspawn
    • /spawnpoint
    • /spectate
    • /spreadplayers
    • /stop
    • /stopsound
    • /summon
    • /tag
    • /team
    • /teammsg (/tm)
    • /teleport (/tp)
    • /tell (/msg, /w)
    • /tellraw
    • /time
    • /title
    • /trigger
    • /weather
    • /whitelist
    • /worldborder
  • Tab completions
    • Tooltips
    • Replace position (start and end of replacement)
  • Arguments
    • Integer (i32, i64)
    • Float (f32, f64)
    • Ranges
    • String
    • Entity selectors
      • Selector parsing (serde)
      • Finding matching entities
      • "tag" selector (serde)
      • Selection by name and uuid
    • Custom enum argument macro
    • Boolean
    • Block position
    • Vec3
    • Vec2
    • Block State
    • Block Predicate
    • Itemstack
    • Item Predicate
    • Chat color
    • Text Component
    • Message (string with selectors)
    • Nbt
    • Nbt path
    • Objective
    • Objective criteria
    • Operation
    • Particle
    • Rotation
    • Angle
    • Scoreboard slot
    • Swizzle
    • Team
    • Item Slot (name)
    • Resource Location (identifier)
    • Mob effect
    • Function
    • Entity anchor
    • Item enchantment
    • Entity summon
    • Dimension
    • Uuid
    • Nbt tag (partial, used in data modify command)
    • Nbt compound tag (full)
    • Time
  • Console
  • Plugin support (I've done the rust native API and it works, and tried to make it work with wasm32-wasi, but I'm new to FFI and wasm, so I'll definitely need some help with this)
    • A fully functional example plugin (bungeecord-servers)
  • Permissions support
  • Declare Commands packet (maybe do something better than LengthInferredVecU8?)

Things to discuss

  • The new command crate's name
  • Command macro design
  • Should the /help command be auto-generated?
  • How can we organize vanilla command implementations? All in one file, one file per command or group them somehow

Related issues

Checklist

  • Ran cargo fmt, cargo clippy --all-targets, cargo build --release and cargo test and fixed any generated errors!
  • Removed unnecessary commented out code
  • Used specific traces (if you trace actions please specify the cause i.e. the player)

Note: if you locally don't get any errors, but GitHub Actions fails (especially at clippy) you might want to check your rust toolchain version. You can then feel free to fix these warnings/errors in your PR.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Iaiao and others added 14 commits September 8, 2021 21:26
@Iaiao
Commands (part 1)
28e96f2
@Iaiao
Implement entity selectors
52f7be1
@Iaiao
Fix /clear, InventoryUpdateEvent, client.send_slot_item
06b37eb
@Iaiao
Move to commands (it's a temporary name)
2e7eba4
@Iaiao
Add Text::translate
ee976bd
@Iaiao
Change "Command not found" message to the same as vanilla
66702e1
@Iaiao
Merge branch main to commands
1332616
@Iaiao
Optimize dispatcher
f925bac
@Iaiao
Implement tab completion
18ef114
@Iaiao
Command support for plugins
c310616
Added command support for plugins, quill_sys::modify_command_executor, new bungeecord example, improved plugin messaging
@Iaiao
Better support for wasm, added plugin to CommandContext and some fixes
170495d
@Iaiao
Rename dispatcher.split to dispatcher.to_parts
d21a53f
@Iaiao
Update commands
4e3acde
@Iaiao
Update commands
00f9353
ambeeeeee
ambeeeeee suggested changes Sep 26, 2021
View reviewed changes

@ambeeeeee ambeeeeee left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not going to solve borrow checking issues with an unsafe magic pointer.

I would recommend cherry picking all your commits and reapplying over the current main, as this pr includes code that was unmerged.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread feather/commands/src/impls.rs Outdated
Comment on lines +31 to +868
// #[command(usage = "tp|teleport <destination>")]
// pub fn tp_1(ctx: &mut CommandCtx, destination: EntitySelector) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &destination.requirements)?;
// if let Some(first) = entities.first() {
// if let Ok(pos) = ctx.ecs.get::<Position>(*first).map(|r| *r) {
// teleport_entity_to_pos(&ctx.ecs, ctx.sender, pos);
// }
//
// Ok(Some(format!(
// "Teleported {0} to {1}",
// *ctx.ecs.get::<Name>(ctx.sender).unwrap(),
// *ctx.ecs.get::<Name>(*first).unwrap()
// )))
// } else {
// Err(TpError::NoMatchingEntities.into())
// }
// }
//
// #[command(usage = "tp|teleport <location>")]
// pub fn tp_2(ctx: &mut CommandCtx, location: Coordinates) -> anyhow::Result<Option<String>> {
// teleport_entity(&ctx.ecs, ctx.sender, location);
//
// let position = ctx.ecs.get::<Position>(ctx.sender).unwrap();
// Ok(Some(format!(
// "Teleported {0} to {1}, {2}, {3}",
// *ctx.ecs.get::<Name>(ctx.sender).unwrap(),
// position.x,
// position.y,
// position.z
// )))
// }
//
// #[command(usage = "tp|teleport <targets> <location>")]
// pub fn tp_3(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// location: Coordinates,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &targets.requirements)?;
// if entities.is_empty() {
// Err(TpError::NoMatchingEntities.into())
// } else {
// for entity in &entities {
// teleport_entity(&ctx.ecs, *entity, location);
// }
//
// let position = ctx.ecs.get::<Position>(*entities.first().unwrap()).unwrap();
// Ok(Some(format!(
// "Teleported {0} to {1}, {2}, {3}",
// targets.entities_to_string(ctx, &entities.into_vec(), false),
// position.x,
// position.y,
// position.z
// )))
// }
// }
//
// #[command(usage = "tp|teleport <targets> <destination>")]
// pub fn tp_4(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// destination: EntitySelector,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &targets.requirements)?;
// if entities.len() > 1 {
// Err(TpError::TooManyEntities.into())
// } else if let Some(Ok(location)) = entities
// .first()
// .map(|e| ctx.ecs.get::<Position>(*e).map(|r| *r))
// {
// if entities.is_empty() {
// Err(TpError::NoMatchingEntities.into())
// } else {
// for entity in &entities {
// teleport_entity_to_pos(&ctx.ecs, *entity, location);
// }
// let entities = entities.into_vec();
// Ok(Some(format!(
// "Teleported {0} to {1}",
// targets.entities_to_string(ctx, &entities, false),
// destination.entities_to_string(ctx, &entities, false)
// )))
// }
// } else {
// Err(TpError::NoMatchingEntities.into())
// }
// }
//
// fn teleport_entity(ecs: &Ecs, entity: Entity, location: Coordinates) {
// let new_pos = ecs
// .get::<Position>(entity)
// .map(|r| *r)
// .map(|relative_to| location.into_position(relative_to));
//
// if let Ok(new_pos) = new_pos {
// teleport_entity_to_pos(ecs, entity, new_pos);
// }
// }
//
// fn teleport_entity_to_pos(ecs: &Ecs, entity: Entity, pos: Position) {
// if let Ok(mut old_pos) = ecs.get_mut::<Position>(entity) {
// *old_pos = pos;
// }
// //let _ = game.ecs.entity(entity).add(Teleported);
// }
//
// #[command(usage = "gamemode <gamemode>")]
// pub fn gamemode_1(
// ctx: &mut CommandCtx,
// gamemode: ParsedGamemode,
// ) -> anyhow::Result<Option<String>> {
// update_gamemode(ctx, gamemode.0, ctx.sender);
// Ok(Some(format!("Set own gamemode to {:?} Mode", gamemode.0)))
// }
//
// #[command(usage = "gamemode <gamemode> <target>")]
// pub fn gamemode_2(
// ctx: &mut CommandCtx,
// gamemode: ParsedGamemode,
// target: EntitySelector,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &target.requirements)?;
// for entity in &entities {
// update_gamemode(ctx, gamemode.0, *entity)
// }
//
// if entities.len() == 1 && *entities.first().unwrap() == ctx.sender {
// return Ok(Some(format!("Set own gamemode to {:?} Mode", gamemode.0)));
// }
// Ok(Some(format!(
// "Changed gamemode of {} to {:?} Mode",
// target.entities_to_string(ctx, &entities.into_vec(), false),
// gamemode.0
// )))
// }
//
// fn update_gamemode(ctx: &mut CommandCtx, gamemode: Gamemode, entity: Entity) {
// let event = if let Ok(mut old) = ctx.ecs.get_mut::<Gamemode>(ctx.sender) {
// let old_val = *old;
// *old = gamemode;
//
// let event = GamemodeUpdateEvent {
// old: old_val,
// new: gamemode,
// };
// Some(event)
// } else {
// None
// };
//
// if let Some(event) = event {
// ctx.ecs.insert_entity_event(entity, event).unwrap();
// }
// }
//
// #[command(usage = "tell|msg|w <target> <message>")]
// pub fn whisper(
// ctx: &mut CommandCtx,
// target: EntitySelector,
// message: TextArgument,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &target.requirements)?;
// let sender_name = if let Ok(sender_name) = ctx.ecs.get::<Name>(ctx.sender) {
// (**sender_name).to_owned()
// } else {
// // Use a default value if the executor has no Name component
// String::from("Server")
// };
//
// // The message that is returned to the whisperer
// // You whisper to [player] (and [player]): [message]
// let mut response_message = String::from("You whisper to");
//
// // Tracks if there needs to be "and" before the next player added to the response message
// let mut needs_and = false;
//
// for entity in entities {
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// chat.send_system(
// Text::from(format!(
// "{} whispers to you: {}",
// sender_name,
// message.0.clone()
// ))
// .gray()
// .italic(),
// );
// } else {
// // If the entity doesn't have a message receiver it is not a player and there is no need to continue
// continue;
// };
//
// if let Ok(player_name) = ctx.ecs.get::<Name>(entity) {
// if needs_and {
// response_message += format!(" and {}", *player_name).as_str();
// } else {
// needs_and = true;
//
// response_message += format!(" {}", *player_name).as_str();
// }
// }
// }
//
// // Send the whisperer a confirmation message
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// response_message += format!(": {}", message.0).as_str();
// let return_text = Text::from(response_message).gray().italic();
//
// chat.send_system(return_text);
// }
//
// Ok(None)
// }
//
// #[command(usage = "say <message>")]
// pub fn say(ctx: &mut CommandCtx, message: TextArgument) -> anyhow::Result<Option<String>> {
// let name = ctx.ecs.get::<Name>(ctx.sender);
//
// let sender_name = if let Ok(name) = &name {
// &***name
// } else {
// "Server"
// };
//
// let command_output = Text::from(format!("[{}] {}", sender_name, message.0));
//
// drop(name);
//
// ctx.ecs
// .query::<&mut ChatBox>()
// .iter()
// .for_each(|(_, chat_box)| chat_box.send_chat(command_output.clone()));
//
// Ok(None)
// }
//
// #[command(usage = "me <action>")]
// pub fn me(ctx: &mut CommandCtx, action: TextArgument) -> anyhow::Result<Option<String>> {
// let command_output = {
// let name = ctx.ecs.get::<Name>(ctx.sender);
// let sender_name = name.as_deref().map_or("@", |n| n);
// Text::from(format!("* {} {}", sender_name, action.as_ref()))
// };
//
// ctx.ecs
// .query::<&mut ChatBox>()
// .iter()
// .for_each(|(_, chat_box)| chat_box.send_chat(command_output.clone()));
//
// Ok(None)
// }
//
// #[derive(Debug, Error)]
// pub enum KickError {
// #[error(
// "Only players may be affected by this command, but the provided selector includes entities"
// )]
// NoEntities,
// }
//
// #[command(usage = "kick <targets>")]
// pub fn kick_1(ctx: &mut CommandCtx, targets: EntitySelector) -> anyhow::Result<Option<String>> {
// kick_players(
// ctx,
// &targets,
// TextValue::translate("multiplayer.disconnect.kicked").into(),
// )
// }
//
// #[command(usage = "kick <targets> <reason>")]
// pub fn kick_2(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// reason: TextArgument,
// ) -> anyhow::Result<Option<String>> {
// kick_players(ctx, &targets, reason.0.into())
// }
//
// fn kick_players(
// ctx: &mut CommandCtx,
// targets: &EntitySelector,
// reason: Text,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &targets.requirements)?;
// for entity in &entities {
// if ctx.ecs.get::<Player>(*entity).is_err() {
// return Err(KickError::NoEntities.into());
// }
// }
//
// for entity in &entities {
// let name = (**ctx.ecs.get::<Name>(*entity).unwrap()).to_owned();
// let _client_id = ctx.ecs.get::<ClientId>(*entity).unwrap();
//
// // Send confirmation message
// // TODO Server ops should also see the message
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let kick_confirm = Text::translate_with(
// "commands.kick.success",
// vec![Text::from(name), reason.clone()],
// );
// chat.send_system(kick_confirm);
// }
// }
// Ok(None)
// }
//
// #[command(usage = "stop")]
// pub fn stop(ctx: &mut CommandCtx) -> anyhow::Result<Option<String>> {
// // Confirmation message
// // TODO Server ops should also see the message
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let text = Text::from(TextValue::translate("commands.stop.stopping"));
// chat.send_system(text);
// }
//
// //ctx.game
// // .resources
// // .get::<ShutdownChannels>()
// // .tx
// // .try_send(())?;
//
// Ok(None)
// }
//
// #[allow(clippy::enum_variant_names)]
// #[derive(Debug, Error)]
// pub enum ClearError {
// #[error("command has to be run from a player")]
// NotPlayer,
// #[error("No items were found on player {0}")]
// NoItems(String),
// #[error("No items were found on {0}")]
// NoItemsMultiplayer(String),
// #[error(
// "Only players may be affected by this command, but the provided selector includes entities"
// )]
// NoEntities,
// }
//
// #[command(usage = "clear")]
// pub fn clear_1(ctx: &mut CommandCtx) -> anyhow::Result<Option<String>> {
// if ctx.ecs.get::<Player>(ctx.sender).is_ok() {
// // Go through the player's inventory and set all the slots to no items.
// // Also, keep track of how many items we delete.
// let mut count = 0;
// clear_items(ctx, ctx.sender, None, i32::MAX, &mut count)?;
// // If count is zero, the player's inventory was empty and the command fails
// // "No items were found on player {0}."
// if count == 0 {
// let name = ctx.ecs.get::<Name>(ctx.sender).unwrap();
// return Err(ClearError::NoItems((**name).to_owned()).into());
// }
// // If the count is not zero, we return the count of items we deleted. Command succeeds.
// // "Removed {1} items from player {0}"
// Ok(Some(format!(
// "Removed {1} items from player {0}",
// *ctx.ecs.get::<Name>(ctx.sender).unwrap(),
// count
// )))
// } else {
// Err(ClearError::NotPlayer.into())
// }
// }
//
// #[command(usage = "clear <targets>")]
// pub fn clear_2(ctx: &mut CommandCtx, targets: EntitySelector) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &targets.requirements)?;
// let mut players = true;
// for entity in &entities {
// players &= ctx.ecs.get::<Player>(*entity).is_ok();
// }
// if players {
// let mut count = 0;
// for entity in &entities {
// clear_items(ctx, *entity, None, i32::MAX, &mut count)?;
// }
// // If count is zero, the everybody's inventory was empty and the command fails
// // "No items were found on {0} players."
// if count == 0 {
// return Err(ClearError::NoItemsMultiplayer(targets.entities_to_string(
// ctx,
// &entities.into_vec(),
// true,
// ))
// .into());
// }
// // If the count is not zero, we return the count of items we deleted. Command succeeds.
// // "Removed {1} items from {0} players"
// Ok(Some(format!(
// "Removed {1} items from {0}",
// targets.entities_to_string(ctx, &entities.into_vec(), true),
// count
// )))
// } else {
// Err(ClearError::NoEntities.into())
// }
// }
//
// #[command(usage = "clear <targets> <item>")]
// pub fn clear_3(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// item: ItemArgument,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &targets.requirements)?;
// let mut players = true;
// for entity in &entities {
// players &= ctx.ecs.get::<Player>(*entity).is_ok();
// }
// if players {
// let mut count = 0;
// for entity in &entities {
// clear_items(ctx, *entity, Some(item.0), i32::MAX, &mut count)?;
// }
// // If count is zero, the everybody's inventory was empty and the command fails
// // "No items were found on {0} players."
// if count == 0 {
// return Err(ClearError::NoItemsMultiplayer(targets.entities_to_string(
// ctx,
// &entities.into_vec(),
// true,
// ))
// .into());
// }
// // If the count is not zero, we return the count of items we deleted. Command succeeds.
// // "Removed {1} items from {0} players"
// Ok(Some(format!(
// "Removed {1} items from {0}",
// targets.entities_to_string(ctx, &entities.into_vec(), true),
// count
// )))
// } else {
// Err(ClearError::NoEntities.into())
// }
// }
//
// #[command(usage = "clear <targets> <item> <maxcount>")]
// pub fn clear_4(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// item: ItemArgument,
// maxcount: PositiveI32Argument,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &targets.requirements)?;
// let mut players = true;
// for entity in &entities {
// players &= ctx.ecs.get::<Player>(*entity).is_ok();
// }
// if players {
// let mut count = 0;
// for entity in &entities {
// clear_items(ctx, *entity, Some(item.0), maxcount.0, &mut count)?;
// }
// // If count is zero, the everybody's inventory was empty and the command fails
// // "No items were found on {0} players."
// if count == 0 {
// return Err(ClearError::NoItemsMultiplayer(targets.entities_to_string(
// ctx,
// &entities.into_vec(),
// true,
// ))
// .into());
// }
// // If maxcount is 0, we report not that we removed items, only that we found them.
// if maxcount.0 == 0 {
// Ok(Some(format!(
// "Found {1} matching items on {0}",
// targets.entities_to_string(ctx, &entities.into_vec(), true),
// count
// )))
// } else {
// // If the count is not zero, we return the count of items we deleted. Command succeeds.
// // "Removed {1} items from {0} players"
// Ok(Some(format!(
// "Removed {1} items from {0}",
// targets.entities_to_string(ctx, &entities.into_vec(), true),
// count
// )))
// }
// } else {
// Err(ClearError::NoEntities.into())
// }
// }
//
// /// Go through a player's inventory and set all the slots that match "item" to empty, up to maxcount items removed.
// /// Also, keep track of how many items we delete total in the variable count.
// /// Will panic if entity does not have an inventory
// fn clear_items(
// ctx: &mut CommandCtx,
// player: Entity,
// item: Option<Item>,
// maxcount: i32,
// count: &mut i32,
// ) -> anyhow::Result<()> {
// let inventory = ctx.ecs.get_mut::<Window>(player).unwrap();
// let mut changed_items: SmallVec<[usize; 2]> = SmallVec::new();
// for (index, slot) in inventory.inner().to_vec().into_iter().enumerate() {
// if let Some(mut stack) = slot {
// if let Some(item_inner) = item {
// if stack.item != item_inner {
// continue;
// }
// }
// if maxcount == 0 {
// *count += stack.count as i32;
// } else if (stack.count as i32) <= maxcount - *count {
// *count += stack.count as i32;
// inventory.set_item(index, None)?;
// changed_items.push(index);
// } else {
// stack.count -= (maxcount - *count) as u32;
// inventory.set_item(index, Some(stack))?;
// *count = maxcount;
// changed_items.push(index);
// break;
// }
// }
// }
//
// drop(inventory);
//
// if !changed_items.is_empty() {
// ctx.ecs
// .insert_entity_event(player, InventoryUpdateEvent(changed_items.into_vec()))?;
// }
//
// Ok(())
// }
//
// #[command(usage = "seed")]
// pub fn seed(ctx: &mut CommandCtx) -> anyhow::Result<Option<String>> {
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let world_seed = "TODO";
// chat.send_system(
// Text::from("Seed: [")
// + Text::from(world_seed.to_string())
// .green()
// .on_click_copy_to_clipboard(world_seed.to_string())
// .on_hover_show_text(TextValue::translate("chat.copy.click"))
// + Text::from("]"),
// );
// }
// Ok(None)
// }
//
// #[derive(Debug, Error)]
// pub enum BanError {
// #[error(
// "Only players may be affected by this command, but the provided selector includes entities"
// )]
// NotPlayer,
// #[error("Already banned")]
// NoTargets,
// }
//
// #[command(usage = "ban <targets> <reason>")]
// pub fn ban_withreason(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// reason: TextArgument,
// ) -> anyhow::Result<Option<String>> {
// ban_players(ctx, targets, reason.0, false)
// }
//
// #[command(usage = "ban <targets>")]
// pub fn ban_noreason(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// ) -> anyhow::Result<Option<String>> {
// ban_players(ctx, targets, "Banned by an operator.".to_owned(), false)
// }
//
// #[command(usage = "ban-ip <targets> <reason>")]
// pub fn banip_withreason(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// reason: TextArgument,
// ) -> anyhow::Result<Option<String>> {
// ban_players(ctx, targets, reason.0, true)
// }
//
// #[command(usage = "ban-ip <targets>")]
// pub fn banip_noreason(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// ) -> anyhow::Result<Option<String>> {
// ban_players(ctx, targets, "Banned by an operator.".to_owned(), true)
// }
//
// #[derive(Debug, Error)]
// pub enum BanIpError {
// #[error("Not a valid IP Address.")]
// InvalidIp,
// }
//
// #[command(usage = "ban-ip <ip> <reason>")]
// pub fn banip_withreason_ip(
// ctx: &mut CommandCtx,
// ip: String,
// reason: TextArgument,
// ) -> anyhow::Result<Option<String>> {
// ban_ip(ctx, ip, reason.0)
// }
//
// #[command(usage = "ban-ip <ip>")]
// pub fn banip_noreason_ip(ctx: &mut CommandCtx, ip: String) -> anyhow::Result<Option<String>> {
// ban_ip(ctx, ip, "IP Banned by an operator.".to_string())
// }
//
// pub fn ban_ip(ctx: &mut CommandCtx, ip: String, reason: String) -> anyhow::Result<Option<String>> {
// let ip = IpAddr::from_str(&ip).map_err(|_| BanIpError::InvalidIp)?;
//
// {
// // TODO
// //let mut bi_lock = ctx.game.resources.get_mut::<WrappedBanInfo>();
// //let mut ban_info = bi_lock.write().unwrap();
//
// //ban_info.ip_bans.insert(
// // ip,
// // Ban {
// // reason: reason.clone(),
// // expires_after: None,
// // },
// //);
// }
//
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let ban_confirm = Text::translate_with(
// "commands.ban.success",
// vec![Text::from(ip.to_string()), Text::from(reason)],
// );
// chat.send_system(ban_confirm);
// }
//
// let ent = ctx
// .ecs
// .query::<&SocketAddr>()
// .iter()
// .find(|(_, x)| x.ip() == ip)
// .map(|(e, _)| e);
//
// if let Some(_ent) = ent {
// //ctx.game.disconnect_and_log(
// // ent,
// // &mut ctx.ecs,
// // &Text::from(reason),
// // "Banned by an operator.",
// //);
// }
//
// Ok(None)
// }
//
// pub fn ban_players(
// ctx: &mut CommandCtx,
// targets: EntitySelector,
// reason: String,
// _by_ip: bool,
// ) -> anyhow::Result<Option<String>> {
// let entities = find_selected_entities(ctx, &targets.requirements)?;
// if entities.is_empty() {
// return Err(BanError::NoTargets.into());
// }
//
// for entity in &entities {
// if ctx.ecs.get::<Player>(*entity).is_ok() {
// return Err(BanError::NotPlayer.into());
// }
// }
//
// for entity in &entities {
// {
// //let mut bi_lock = ctx.game.resources.get_mut::<WrappedBanInfo>();
// //let mut ban_info = bi_lock.write().unwrap();
// //
// //if by_ip {
// // let ip = ctx.ecs.get::<SocketAddr>(*entity).unwrap();
// //
// // ban_info.ip_bans.insert(
// // ip.ip(),
// // Ban {
// // reason: reason.clone(),
// // expires_after: None,
// // },
// // );
// //} else {
// // let uuid = ctx.ecs.get::<Uuid>(*entity).unwrap();
// //
// // ban_info.uuid_bans.insert(
// // uuid.to_hyphenated_ref().to_string(),
// // Ban {
// // reason: reason.clone(),
// // expires_after: None,
// // },
// // );
// //}
// }
//
// let name = (**ctx.ecs.get::<Name>(*entity).unwrap()).to_owned();
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let ban_confirm = Text::translate_with(
// "commands.ban.success",
// vec![Text::from(name), Text::from(reason.clone())],
// );
// chat.send_system(ban_confirm);
// }
//
// //ctx.game.disconnect_and_log(
// // *entity,
// // &mut ctx.ecs,
// // &Text::from(reason.clone()),
// // "Banned by an operator.",
// //);
// }
//
// Ok(None)
// }
//
// #[derive(Debug, Error)]
// pub enum PardonError {
// #[error("Couldn't find that players UUID, Have they changed name?")]
// NotPlayer,
// }
//
// #[command(usage = "pardon <name>")]
// pub fn pardon(ctx: &mut CommandCtx, name: TextArgument) -> anyhow::Result<Option<String>> {
// // Get UUID from name
// //let online_mode = ctx.game.shared.config.server.online_mode;
// //let uuid = if online_mode {
// // Runtime::new()
// // .unwrap()
// // .block_on(name_to_uuid_online(&name.0))
// //} else {
// // Some(name_to_uuid_offline(&name.0))
// //};
//
// //let uuid = match uuid {
// // Some(uuid) => uuid,
// // None => return Err(PardonError::NotPlayer.into()),
// //};
//
// //{
// // let bi_lock = ctx.game.resources.get::<WrappedBanInfo>();
// // let mut ban_info = bi_lock.write().unwrap();
// // ban_info
// // .uuid_bans
// // .remove(&uuid.to_hyphenated_ref().to_string());
// //}
//
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let kick_confirm =
// Text::translate_with("commands.pardon.success", vec![Text::from(name.0)]);
// chat.send_system(kick_confirm);
// }
//
// Ok(None)
// }
//
// #[derive(Debug, Error)]
// pub enum PardonIpError {
// #[error("Invalid IP Address")]
// NotIp,
// }
//
// #[command(usage = "pardon-ip <ip>")]
// pub fn pardonip(ctx: &mut CommandCtx, ip: String) -> anyhow::Result<Option<String>> {
// // Try to parse ip
// let _addr = IpAddr::from_str(&ip).map_err(|_| PardonIpError::NotIp)?;
//
// //{
// // let mut bi_lock = ctx.game.resources.get_mut::<WrappedBanInfo>();
// // let mut ban_info = bi_lock.write().unwrap();
// // ban_info.ip_bans.remove(&addr);
// //}
//
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let kick_confirm = Text::translate_with("commands.pardon.success", vec![Text::from(ip)]);
// chat.send_system(kick_confirm);
// }
//
// Ok(None)
// }
//
// #[command(usage = "time query <info>")]
// pub fn time_query(
// ctx: &mut CommandCtx,
// info: TimeQueryInformation,
// ) -> anyhow::Result<Option<String>> {
// let time = match info {
// TimeQueryInformation::DayTime => ctx.world.time.time(),
// TimeQueryInformation::GameTime => ctx.world.time.world_age(),
// TimeQueryInformation::Day => ctx.world.time.days(),
// };
//
// if let Ok(mut chat) = ctx.ecs.get_mut::<ChatBox>(ctx.sender) {
// let message =
// Text::translate_with("commands.time.query", vec![Text::from(time.to_string())]);
// chat.send_system(message);
// }
//
// Ok(None)
// }
//
// #[command(usage = "time add <time>")]
// pub fn time_add(ctx: &mut CommandCtx, time: TimeArgument) -> anyhow::Result<Option<String>> {
// let time = ctx.world.time.time() + time.0;
// set_time(ctx, time);
// Ok(None)
// }
//
// #[command(usage = "time set <time>")]
// pub fn time_set_0(ctx: &mut CommandCtx, time: TimeArgument) -> anyhow::Result<Option<String>> {
// set_time(ctx, time.0);
// Ok(None)
// }
//
// #[command(usage = "time set <time_spec>")]
// pub fn time_set_1(ctx: &mut CommandCtx, time_spec: TimeSpec) -> anyhow::Result<Option<String>> {
// set_time(
// ctx,
// match time_spec {
// TimeSpec::Day => 1_000,
// TimeSpec::Noon => 6_000,
// TimeSpec::Night => 13_000,
// TimeSpec::Midnight => 18_000,
// },
// );
// Ok(None)
// }
//
// fn set_time(ctx: &mut CommandCtx, time: u64) {
// ctx.ecs.insert_event(TimeUpdateEvent {
// old: ctx.world.time.time(),
// new: time,
// });
// ctx.world.time.set_time(time);
// }

@ambeeeeee ambeeeeee Sep 26, 2021

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to look into a different way to organize commands instead of a monolithic implementation file.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread feather/commands/src/lib.rs Outdated
Comment on lines +15 to +44
/// Dumb workaround for a certain lifetime issue.
///
/// `CommandCtx` stores references to `Game`, and it
/// is used as the `C` parameter for `CommandDispatcher`,
/// This combination of lifetimes and storage in structs
/// prevents a lifetime-based `CommandCtx` from being stored
/// in `CommandState` without adding a lifetime parameter to `CommandState`.
///
/// Since `CommandCtx` is never actually _stored_ in `CommandState` (it's
/// only passed into a function), we can (hopefully) soundly erase
/// the lifetime parameters. FIXME: if someone has a better solution,
/// a PR is welcome :)
pub struct LifetimelessMut<T>(*mut T);

impl<T> Deref for LifetimelessMut<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
unsafe { &mut *self.0 }
}
}

impl<T> DerefMut for LifetimelessMut<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.0 }
}
}

unsafe impl<T> Send for LifetimelessMut<T> where T: Send {}
unsafe impl<T> Sync for LifetimelessMut<T> where T: Sync {}

@ambeeeeee ambeeeeee Sep 26, 2021

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q_Q Can we talk about this please

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ambeeeeee ambeeeeee Sep 26, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's basically 0 chance this will ever be merged as-is

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Iaiao added 15 commits September 26, 2021 11:34
@Iaiao
Remove unsafe send + sync impl for LifetimelessMut
e60cf5c
@Iaiao
Add plugins/ to .gitignore
49624b5
@Iaiao
Update commands
887cbc5
@Iaiao
Use translate_with in /me
8562a8d
@Iaiao
/gamemode + entity selector matching
a95dda4
@Iaiao
/clear
0a7d368
@Iaiao
merge branch main to commands
5a0b355
@Iaiao
Implement banned-players.json and banned-ips.json
0c7cfe2
@Iaiao
Add RealIp component
0272b8d
@Iaiao
FlyingAbilityChangeEvent, BuildingAbilityChangeEvent, Invulnerability…
3e01e21
…ChangeEvent
@Iaiao
/ban
2054d1d
@Iaiao
Send PlayerAbilities after changing gamemode
2fbf567
@Iaiao
/ban-ip
b75b6f2
@Iaiao
change string argument to message argument in /ban, /ban-ip
9f13f3f
@Iaiao
Kick banned players + better error handling
13c2d6b
Iaiao added 28 commits October 13, 2021 19:13
@Iaiao
Add messages for /gamemode command
a5cc104
@Iaiao
Remove LifetimelessMut
c0d7fec
@Iaiao
Fix clippy warnings
dbbec0f
@Iaiao
Create get_name() and some minor improvements
7877e30
@Iaiao
/say
3dfbe8e
@Iaiao
Fix clippy warning
7f8d4d3
@Iaiao
Console commands (tab-completion doesn't work)
2509f61
@Iaiao
Tab completion for console
fe0447e
@Iaiao
Tab completion start/end position
e7272f0
@Iaiao
Syntax highlighting
ed4867f
@Iaiao
Make the command input line bold
bae1014
@Iaiao
Fix purple and bold
50e93bf
@Iaiao
Use try_send on bounded tab-completion channels
7b92acf
@Iaiao
Added cli options edit_mode and tab_completion_type to config.toml
716f95d
@Iaiao
Update commands
675b760
@Iaiao
/execute
01ec330
@Iaiao
/execute at
ac1f6df
@Iaiao
/execute align
27a8bcb
@Iaiao
/execute anchored
e972d38
@Iaiao
Add tab completions for all existing commands
849a399
@Iaiao
Fix double-free after plugin command execution
4ff9674
@Iaiao
/banlist
dce41aa
@Iaiao
Update commands
b4ffc63
@Iaiao
/time
960e775
@Iaiao
/defaultgamemode
7e799a4
@Iaiao
/setblock
e1a1404
@Iaiao
/seed
7b37553
@Iaiao
Change seed type to i64 and rewrite hashcode implementation
210fed5
This was referenced Jan 16, 2022
Fixed config seed #516
Open
Add an ability to change gamemode from plugins #521
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

1 more reviewer
@ambeeeeee ambeeeeee ambeeeeee requested changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@Iaiao @ambeeeeee
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.