Skip to content

Commit 77f1ace

Browse files
authored
Add an ability to change gamemode from plugins (#521)
* Add GamemodeEvent * Ignore updates to the same gamemode * Dispatch GamemodeEvent on join * Simplify abilities change logic * format * Rewrite ChangeGameState packet * Rename ability change events * Remove if statement
1 parent 3779032 commit 77f1ace

File tree

9 files changed

+394
-29
lines changed

9 files changed

+394
-29
lines changed

feather/protocol/src/packets/server/play.rs

Lines changed: 128 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use anyhow::bail;
1+
use std::io::Cursor;
2+
3+
use anyhow::{anyhow, bail};
24

35
use base::{
46
BlockState, EntityMetadata, Gamemode, ParticleKind, ProfileProperty, ValidBlockPosition,
@@ -7,7 +9,7 @@ pub use chunk_data::{ChunkData, ChunkDataKind};
79
use quill_common::components::PreviousGamemode;
810
pub use update_light::UpdateLight;
911

10-
use crate::{io::VarLong, Readable, Writeable};
12+
use crate::{io::VarLong, ProtocolVersion, Readable, Writeable};
1113

1214
use super::*;
1315

@@ -334,8 +336,7 @@ packets! {
334336
}
335337

336338
ChangeGameState {
337-
reason StateReason;
338-
value f32;
339+
state_change GameStateChange;
339340
}
340341

341342
OpenHorseWindow {
@@ -356,22 +357,131 @@ packets! {
356357
}
357358
}
358359

359-
def_enum! {
360-
StateReason (i8) {
361-
0 = NoRespawnBlock,
362-
1 = EndRaining,
363-
2 = BeginningRain,
364-
3 = ChangeGameMode,
365-
4 = WinGame,
366-
5 = DemoEvent,
367-
6 = ArrowHitPlayer,
368-
7 = RainLevelChange,
369-
8 = ThunderLevelChange,
370-
9 = PufferfishSting,
371-
10 = ElderGuardianAppearance,
372-
11 = EnableRespawnScreen,
360+
#[derive(Debug, Clone)]
361+
pub enum GameStateChange {
362+
/// Sends block.minecraft.spawn.not_valid to client
363+
SendNoRespawnBlockAvailableMessage,
364+
EndRaining,
365+
BeginRaining,
366+
ChangeGamemode {
367+
gamemode: Gamemode,
368+
},
369+
/// Sent when the player enters an end portal from minecraft:the_end to minecraft:overworld
370+
WinGame {
371+
show_credits: bool,
372+
},
373+
/// See https://help.minecraft.net/hc/en-us/articles/4408948974989-Minecraft-Java-Edition-Demo-Mode-
374+
DemoEvent(DemoEventType),
375+
/// Sent when any player is struck by an arrow.
376+
ArrowHitAnyPlayer,
377+
/// Seems to change both skycolor and lightning.
378+
RainLevelChange {
379+
/// Possible values are from 0 to 1
380+
rain_level: f64,
381+
},
382+
/// Seems to change both skycolor and lightning (same as Rain level change, but doesn't start rain).
383+
/// It also requires rain to render by notchian client.
384+
ThunderLevelChange {
385+
/// Possible values are from 0 to 1
386+
thunder_level: f64,
387+
},
388+
PlayPufferfishStingSound,
389+
PlayElderGuardianAppearance,
390+
/// Send when doImmediateRespawn gamerule changes.
391+
EnableRespawnScreen {
392+
enable: bool,
393+
},
394+
}
395+
396+
#[derive(Debug, Clone)]
397+
pub enum DemoEventType {
398+
ShowWelcomeToDemoScreen,
399+
TellMovementControls,
400+
TellJumpControl,
401+
TellInventoryControl,
402+
TellDemoIsOver,
403+
}
404+
405+
impl Writeable for GameStateChange {
406+
fn write(&self, buffer: &mut Vec<u8>, version: ProtocolVersion) -> anyhow::Result<()> {
407+
// Reason
408+
match self {
409+
GameStateChange::SendNoRespawnBlockAvailableMessage => 0u8,
410+
GameStateChange::EndRaining => 1,
411+
GameStateChange::BeginRaining => 2,
412+
GameStateChange::ChangeGamemode { .. } => 3,
413+
GameStateChange::WinGame { .. } => 4,
414+
GameStateChange::DemoEvent(_) => 5,
415+
GameStateChange::ArrowHitAnyPlayer => 6,
416+
GameStateChange::RainLevelChange { .. } => 7,
417+
GameStateChange::ThunderLevelChange { .. } => 8,
418+
GameStateChange::PlayPufferfishStingSound => 9,
419+
GameStateChange::PlayElderGuardianAppearance => 10,
420+
GameStateChange::EnableRespawnScreen { .. } => 11,
421+
}
422+
.write(buffer, version)?;
423+
424+
// Value
425+
match self {
426+
GameStateChange::ChangeGamemode { gamemode } => *gamemode as u8 as f64,
427+
GameStateChange::WinGame { show_credits } => *show_credits as u8 as f64,
428+
GameStateChange::DemoEvent(DemoEventType::ShowWelcomeToDemoScreen) => 0.0,
429+
GameStateChange::DemoEvent(DemoEventType::TellMovementControls) => 101.0,
430+
GameStateChange::DemoEvent(DemoEventType::TellJumpControl) => 102.0,
431+
GameStateChange::DemoEvent(DemoEventType::TellInventoryControl) => 103.0,
432+
GameStateChange::DemoEvent(DemoEventType::TellDemoIsOver) => 104.0,
433+
GameStateChange::RainLevelChange { rain_level } => *rain_level,
434+
GameStateChange::ThunderLevelChange { thunder_level } => *thunder_level,
435+
GameStateChange::EnableRespawnScreen { enable } => !enable as u8 as f64,
436+
_ => 0.0,
437+
}
438+
.write(buffer, version)?;
439+
440+
Ok(())
373441
}
374442
}
443+
444+
impl Readable for GameStateChange {
445+
fn read(buffer: &mut Cursor<&[u8]>, version: ProtocolVersion) -> anyhow::Result<Self>
446+
where
447+
Self: Sized,
448+
{
449+
let reason = u8::read(buffer, version)?;
450+
let value = f64::read(buffer, version)?;
451+
Ok(match reason {
452+
0 => GameStateChange::SendNoRespawnBlockAvailableMessage,
453+
1 => GameStateChange::EndRaining,
454+
2 => GameStateChange::BeginRaining,
455+
3 => GameStateChange::ChangeGamemode {
456+
gamemode: Gamemode::from_id(value as u8)
457+
.ok_or(anyhow!("Unsupported gamemode ID"))?,
458+
},
459+
4 => GameStateChange::WinGame {
460+
show_credits: value as u8 != 0,
461+
},
462+
5 => GameStateChange::DemoEvent(match value as u8 {
463+
0 => DemoEventType::ShowWelcomeToDemoScreen,
464+
101 => DemoEventType::TellMovementControls,
465+
102 => DemoEventType::TellJumpControl,
466+
103 => DemoEventType::TellInventoryControl,
467+
104 => DemoEventType::TellDemoIsOver,
468+
other => bail!("Invalid demo event type: {}", other),
469+
}),
470+
6 => GameStateChange::ArrowHitAnyPlayer,
471+
7 => GameStateChange::RainLevelChange { rain_level: value },
472+
8 => GameStateChange::ThunderLevelChange {
473+
thunder_level: value,
474+
},
475+
9 => GameStateChange::PlayPufferfishStingSound,
476+
10 => GameStateChange::PlayElderGuardianAppearance,
477+
11 => GameStateChange::EnableRespawnScreen {
478+
enable: value as u8 == 0,
479+
},
480+
other => bail!("Invalid game state change reason: {}", other),
481+
})
482+
}
483+
}
484+
375485
packets! {
376486
JoinGame {
377487
entity_id i32;

feather/server/src/client.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{
77

88
use ahash::AHashSet;
99
use flume::{Receiver, Sender};
10+
use slab::Slab;
1011
use uuid::Uuid;
1112

1213
use base::{
@@ -20,7 +21,8 @@ use common::{
2021
use libcraft_items::InventorySlot;
2122
use packets::server::{Particle, SetSlot, SpawnLivingEntity, UpdateLight, WindowConfirmation};
2223
use protocol::packets::server::{
23-
EntityPosition, EntityPositionAndRotation, EntityTeleport, HeldItemChange, PlayerAbilities,
24+
ChangeGameState, EntityPosition, EntityPositionAndRotation, EntityTeleport, GameStateChange,
25+
HeldItemChange, PlayerAbilities,
2426
};
2527
use protocol::{
2628
packets::{
@@ -42,7 +44,6 @@ use crate::{
4244
network_id_registry::NetworkId,
4345
Options,
4446
};
45-
use slab::Slab;
4647

4748
/// Max number of chunks to send to a client per tick.
4849
const MAX_CHUNKS_PER_TICK: usize = 10;
@@ -331,6 +332,10 @@ impl Client {
331332
self.send_packet(PlayerInfo::RemovePlayers(vec![uuid]));
332333
}
333334

335+
pub fn change_player_tablist_gamemode(&self, uuid: Uuid, gamemode: Gamemode) {
336+
self.send_packet(PlayerInfo::UpdateGamemodes(vec![(uuid, gamemode)]));
337+
}
338+
334339
pub fn unload_entity(&self, id: NetworkId) {
335340
log::trace!("Unloading {:?} on {}", id, self.username);
336341
self.sent_entities.borrow_mut().remove(&id);
@@ -602,6 +607,12 @@ impl Client {
602607
self.send_packet(HeldItemChange { slot });
603608
}
604609

610+
pub fn change_gamemode(&self, gamemode: Gamemode) {
611+
self.send_packet(ChangeGameState {
612+
state_change: GameStateChange::ChangeGamemode { gamemode },
613+
})
614+
}
615+
605616
fn register_entity(&self, network_id: NetworkId) {
606617
self.sent_entities.borrow_mut().insert(network_id);
607618
}

feather/server/src/systems.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
mod block;
44
mod chat;
55
mod entity;
6+
mod gamemode;
67
mod particle;
78
mod player_join;
89
mod player_leave;
@@ -36,6 +37,7 @@ pub fn register(server: Server, game: &mut Game, systems: &mut SystemExecutor<Ga
3637
chat::register(game, systems);
3738
particle::register(systems);
3839
plugin_message::register(systems);
40+
gamemode::register(systems);
3941

4042
systems.group::<Server>().add_system(tick_clients);
4143
}

0 commit comments

Comments
 (0)