|
| 1 | +use std::borrow::Cow; |
| 2 | + |
| 3 | +use clap::Parser; |
| 4 | +use flecs_ecs::core::{Entity, EntityViewGet, World, WorldGet}; |
| 5 | +use hyperion::{ |
| 6 | + egress::{ |
| 7 | + metadata::show_all, |
| 8 | + player_join::{PlayerListActions, PlayerListEntry, PlayerListS2c}, |
| 9 | + }, |
| 10 | + net::{Compose, DataBundle, NetworkStreamRef, agnostic}, |
| 11 | + simulation::{Pitch, Position, Yaw}, |
| 12 | + system_registry::SystemId, |
| 13 | + valence_ident::ident, |
| 14 | + valence_protocol::{ |
| 15 | + BlockPos, GameMode, VarInt, |
| 16 | + game_mode::OptGameMode, |
| 17 | + packets::{ |
| 18 | + play, |
| 19 | + play::{PlayerRespawnS2c, player_position_look_s2c::PlayerPositionLookFlags}, |
| 20 | + }, |
| 21 | + profile::Property, |
| 22 | + }, |
| 23 | +}; |
| 24 | +use hyperion_clap::{CommandPermission, MinecraftCommand}; |
| 25 | +use hyperion_rank_tree::{Class, Team}; |
| 26 | +use hyperion_utils::EntityExt; |
| 27 | + |
| 28 | +#[derive(Parser, CommandPermission, Debug)] |
| 29 | +#[command(name = "class")] |
| 30 | +#[command_permission(group = "Normal")] |
| 31 | +pub struct ClassCommand { |
| 32 | + class: Class, |
| 33 | + team: Team, |
| 34 | +} |
| 35 | +impl MinecraftCommand for ClassCommand { |
| 36 | + fn execute(self, world: &World, caller: Entity) { |
| 37 | + let class_param = self.class; |
| 38 | + let team_param = self.team; |
| 39 | + |
| 40 | + world.get::<&Compose>(|compose| { |
| 41 | + let caller = caller.entity_view(world); |
| 42 | + caller.get::<( |
| 43 | + &NetworkStreamRef, |
| 44 | + &hyperion::simulation::Uuid, |
| 45 | + &Position, |
| 46 | + &Yaw, |
| 47 | + &Pitch, |
| 48 | + &mut Team, |
| 49 | + &mut Class, |
| 50 | + )>(|(stream, uuid, position, yaw, pitch, team, class)| { |
| 51 | + if *team != team_param { |
| 52 | + *team = team_param; |
| 53 | + caller.modified::<Team>(); |
| 54 | + } |
| 55 | + |
| 56 | + if *class != class_param { |
| 57 | + *class = class_param; |
| 58 | + caller.modified::<Class>(); |
| 59 | + } |
| 60 | + |
| 61 | + let minecraft_id = caller.minecraft_id(); |
| 62 | + let mut bundle = DataBundle::new(compose); |
| 63 | + |
| 64 | + let mut position_block = position.floor().as_ivec3(); |
| 65 | + position_block.y -= 1; |
| 66 | + |
| 67 | + // Add bundle splitter so these are all received at once |
| 68 | + bundle.add_packet(&play::BundleSplitterS2c, world).unwrap(); |
| 69 | + |
| 70 | + // Set respawn position to player's position |
| 71 | + bundle |
| 72 | + .add_packet( |
| 73 | + &play::PlayerSpawnPositionS2c { |
| 74 | + position: BlockPos::new( |
| 75 | + position_block.x, |
| 76 | + position_block.y, |
| 77 | + position_block.z, |
| 78 | + ), |
| 79 | + // todo: seems to not do anything; perhaps angle is different than yaw? |
| 80 | + // regardless doesn't matter as we teleport to the correct position |
| 81 | + // later anyway |
| 82 | + angle: **yaw, |
| 83 | + }, |
| 84 | + world, |
| 85 | + ) |
| 86 | + .unwrap(); |
| 87 | + |
| 88 | + // Remove player info |
| 89 | + bundle |
| 90 | + .add_packet( |
| 91 | + &play::PlayerRemoveS2c { |
| 92 | + uuids: Cow::Borrowed(&[uuid.0]), |
| 93 | + }, |
| 94 | + world, |
| 95 | + ) |
| 96 | + .unwrap(); |
| 97 | + |
| 98 | + // Destroy player entity |
| 99 | + bundle |
| 100 | + .add_packet( |
| 101 | + &play::EntitiesDestroyS2c { |
| 102 | + entity_ids: Cow::Borrowed(&[VarInt(minecraft_id)]), |
| 103 | + }, |
| 104 | + world, |
| 105 | + ) |
| 106 | + .unwrap(); |
| 107 | + |
| 108 | + let skin = class.skin(); |
| 109 | + let property = Property { |
| 110 | + name: "textures".to_string(), |
| 111 | + value: skin.textures.clone(), |
| 112 | + signature: Some(skin.signature.clone()), |
| 113 | + }; |
| 114 | + |
| 115 | + let property = &[property]; |
| 116 | + |
| 117 | + // Add player back with new skin |
| 118 | + bundle |
| 119 | + .add_packet( |
| 120 | + &PlayerListS2c { |
| 121 | + actions: PlayerListActions::default().with_add_player(true), |
| 122 | + entries: Cow::Borrowed(&[PlayerListEntry { |
| 123 | + player_uuid: uuid.0, |
| 124 | + username: Cow::Borrowed("Player"), |
| 125 | + properties: Cow::Borrowed(property), |
| 126 | + chat_data: None, |
| 127 | + listed: true, |
| 128 | + ping: 20, |
| 129 | + game_mode: GameMode::Survival, |
| 130 | + display_name: None, |
| 131 | + }]), |
| 132 | + }, |
| 133 | + world, |
| 134 | + ) |
| 135 | + .unwrap(); |
| 136 | + |
| 137 | + // Respawn player |
| 138 | + bundle |
| 139 | + .add_packet( |
| 140 | + &PlayerRespawnS2c { |
| 141 | + dimension_type_name: ident!("minecraft:overworld").into(), |
| 142 | + dimension_name: ident!("minecraft:overworld").into(), |
| 143 | + hashed_seed: 0, |
| 144 | + game_mode: GameMode::Survival, |
| 145 | + previous_game_mode: OptGameMode::default(), |
| 146 | + is_debug: false, |
| 147 | + is_flat: false, |
| 148 | + copy_metadata: false, |
| 149 | + last_death_location: None, |
| 150 | + portal_cooldown: VarInt::default(), |
| 151 | + }, |
| 152 | + world, |
| 153 | + ) |
| 154 | + .unwrap(); |
| 155 | + |
| 156 | + // look and teleport to more accurate position than full-block respawn position |
| 157 | + bundle |
| 158 | + .add_packet( |
| 159 | + &play::PlayerPositionLookS2c { |
| 160 | + position: position.as_dvec3(), |
| 161 | + yaw: **yaw, |
| 162 | + pitch: **pitch, |
| 163 | + flags: PlayerPositionLookFlags::default(), |
| 164 | + teleport_id: VarInt(fastrand::i32(..)), |
| 165 | + }, |
| 166 | + world, |
| 167 | + ) |
| 168 | + .unwrap(); |
| 169 | + |
| 170 | + let msg = format!("Setting rank to {class:?} with yaw {yaw:?}"); |
| 171 | + let chat = agnostic::chat(msg); |
| 172 | + bundle.add_packet(&chat, world).unwrap(); |
| 173 | + |
| 174 | + let show_all = show_all(minecraft_id); |
| 175 | + bundle.add_packet(show_all.borrow_packet(), world).unwrap(); |
| 176 | + |
| 177 | + bundle.add_packet(&play::BundleSplitterS2c, world).unwrap(); |
| 178 | + |
| 179 | + bundle.send(world, *stream, SystemId(0)).unwrap(); |
| 180 | + }); |
| 181 | + }); |
| 182 | + } |
| 183 | +} |
0 commit comments