Extractor: Parse and use screens.json

This commit is contained in:
Snowiiii
2024-11-03 11:25:15 +01:00
parent 9e7adec16b
commit 8f90b29cfe
16 changed files with 288 additions and 153 deletions

102
assets/screens.json Normal file
View File

@@ -0,0 +1,102 @@
[
{
"id": 0,
"name": "minecraft:generic_9x1"
},
{
"id": 1,
"name": "minecraft:generic_9x2"
},
{
"id": 2,
"name": "minecraft:generic_9x3"
},
{
"id": 3,
"name": "minecraft:generic_9x4"
},
{
"id": 4,
"name": "minecraft:generic_9x5"
},
{
"id": 5,
"name": "minecraft:generic_9x6"
},
{
"id": 6,
"name": "minecraft:generic_3x3"
},
{
"id": 7,
"name": "minecraft:crafter_3x3"
},
{
"id": 8,
"name": "minecraft:anvil"
},
{
"id": 9,
"name": "minecraft:beacon"
},
{
"id": 10,
"name": "minecraft:blast_furnace"
},
{
"id": 11,
"name": "minecraft:brewing_stand"
},
{
"id": 12,
"name": "minecraft:crafting"
},
{
"id": 13,
"name": "minecraft:enchantment"
},
{
"id": 14,
"name": "minecraft:furnace"
},
{
"id": 15,
"name": "minecraft:grindstone"
},
{
"id": 16,
"name": "minecraft:hopper"
},
{
"id": 17,
"name": "minecraft:lectern"
},
{
"id": 18,
"name": "minecraft:loom"
},
{
"id": 19,
"name": "minecraft:merchant"
},
{
"id": 20,
"name": "minecraft:shulker_box"
},
{
"id": 21,
"name": "minecraft:smithing"
},
{
"id": 22,
"name": "minecraft:smoker"
},
{
"id": 23,
"name": "minecraft:cartography_table"
},
{
"id": 24,
"name": "minecraft:stonecutter"
}
]

View File

@@ -28,6 +28,7 @@ class Extractor : ModInitializer {
Particles(),
SyncedRegistries(),
Packet(),
Screen(),
Items(),
Blocks(),
)

View File

@@ -4,7 +4,6 @@ import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import de.snowii.extractor.Extractor
import net.minecraft.network.packet.s2c.play.InventoryS2CPacket
import net.minecraft.registry.Registries
import net.minecraft.server.MinecraftServer

View File

@@ -0,0 +1,26 @@
package de.snowii.extractor.extractors
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import de.snowii.extractor.Extractor
import net.minecraft.registry.Registries
import net.minecraft.server.MinecraftServer
class Screen : Extractor.Extractor {
override fun fileName(): String {
return "screens.json"
}
override fun extract(server: MinecraftServer): JsonElement {
val screensJson = JsonArray()
for (screen in Registries.SCREEN_HANDLER) {
val screenJson = JsonObject()
screenJson.addProperty("id", Registries.SCREEN_HANDLER.getRawId(screen))
screenJson.addProperty("name", Registries.SCREEN_HANDLER.getId(screen)!!.toString())
screensJson.add(screenJson)
}
return screensJson
}
}

View File

@@ -6,6 +6,7 @@ edition.workspace = true
[dependencies]
# For items
pumpkin-world = { path = "../pumpkin-world" }
pumpkin-macros = { path = "../pumpkin-macros" }
log.workspace = true
itertools.workspace = true

View File

@@ -1,6 +1,7 @@
use crate::container_click::MouseClick;
use crate::player::PlayerInventory;
use num_derive::{FromPrimitive, ToPrimitive};
use pumpkin_macros::screen;
use pumpkin_world::item::ItemStack;
pub mod container_click;
@@ -15,42 +16,43 @@ pub use open_container::OpenContainer;
/// https://wiki.vg/Inventory
#[derive(Debug, ToPrimitive, FromPrimitive, Clone, Copy, Eq, PartialEq)]
#[repr(u16)]
pub enum WindowType {
// not used
Generic9x1,
Generic9x1 = screen!("minecraft:generic_9x1"),
// not used
Generic9x2,
Generic9x2 = screen!("minecraft:generic_9x2"),
// General-purpose 3-row inventory. Used by Chest, minecart with chest, ender chest, and barrel
Generic9x3,
Generic9x3 = screen!("minecraft:generic_9x3"),
// not used
Generic9x4,
Generic9x4 = screen!("minecraft:generic_9x4"),
// not used
Generic9x5,
Generic9x5 = screen!("minecraft:generic_9x5"),
// Used by large chests
Generic9x6,
Generic9x6 = screen!("minecraft:generic_9x6"),
// General-purpose 3-by-3 square inventory, used by Dispenser and Dropper
Generic3x3,
Generic3x3 = screen!("minecraft:generic_3x3"),
// General-purpose 3-by-3 square inventory, used by the Crafter
Craft3x3,
Anvil,
Beacon,
BlastFurnace,
BrewingStand,
CraftingTable,
EnchantmentTable,
Furnace,
Grindstone,
Craft3x3 = screen!("minecraft:crafter_3x3"),
Anvil = screen!("minecraft:anvil"),
Beacon = screen!("minecraft:beacon"),
BlastFurnace = screen!("minecraft:blast_furnace"),
BrewingStand = screen!("minecraft:brewing_stand"),
CraftingTable = screen!("minecraft:crafting"),
EnchantmentTable = screen!("minecraft:enchantment"),
Furnace = screen!("minecraft:furnace"),
Grindstone = screen!("minecraft:grindstone"),
// Hopper or minecart with hopper
Hopper,
Lectern,
Loom,
Hopper = screen!("minecraft:hopper"),
Lectern = screen!("minecraft:lectern"),
Loom = screen!("minecraft:loom"),
// Villager, Wandering Trader
Merchant,
ShulkerBox,
SmithingTable,
Smoker,
CartographyTable,
Stonecutter,
Merchant = screen!("minecraft:merchant"),
ShulkerBox = screen!("minecraft:shulker_box"),
SmithingTable = screen!("minecraft:smithing"),
Smoker = screen!("minecraft:smoker"),
CartographyTable = screen!("minecraft:cartography_table"),
Stonecutter = screen!("minecraft:stonecutter"),
}
pub struct ContainerStruct<const SLOTS: usize>([Option<ItemStack>; SLOTS]);

View File

@@ -25,6 +25,11 @@ pub fn client_packet(input: TokenStream, item: TokenStream) -> TokenStream {
gen.into()
}
mod screen;
#[proc_macro]
pub fn screen(item: TokenStream) -> TokenStream {
screen::screen_impl(item)
}
mod sound;
#[proc_macro]
pub fn sound(item: TokenStream) -> TokenStream {

View File

@@ -0,0 +1,27 @@
use std::{collections::HashMap, sync::LazyLock};
use proc_macro::TokenStream;
use quote::quote;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Screen {
name: String,
id: u16,
}
static SCREENS: LazyLock<HashMap<String, u16>> = LazyLock::new(|| {
serde_json::from_str::<Vec<Screen>>(include_str!("../../assets/screens.json"))
.expect("Could not parse screens.json registry.")
.into_iter()
.map(|val| (val.name, val.id))
.collect()
});
pub(crate) fn screen_impl(item: TokenStream) -> TokenStream {
let input_string = item.to_string();
let screen_name = input_string.trim_matches('"');
let id = SCREENS.get(screen_name).expect("Invalid screen");
quote! { #id }.into()
}

View File

@@ -60,7 +60,7 @@ impl AttackType {
}
}
pub(super) async fn handle_knockback(
pub async fn handle_knockback(
attacker_entity: &Entity,
victim: &Player,
victim_entity: &Entity,
@@ -93,11 +93,7 @@ pub(super) async fn handle_knockback(
victim.client.send_packet(packet).await;
}
pub(super) async fn spawn_sweep_particle(
attacker_entity: &Entity,
world: &World,
pos: &Vector3<f64>,
) {
pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3<f64>) {
let yaw = attacker_entity.yaw.load();
let d = -f64::from((yaw * (PI / 180.0)).sin());
let e = f64::from((yaw * (PI / 180.0)).cos());
@@ -123,11 +119,7 @@ pub(super) async fn spawn_sweep_particle(
.await;
}
pub(super) async fn player_attack_sound(
pos: &Vector3<f64>,
world: &World,
attack_type: AttackType,
) {
pub async fn player_attack_sound(pos: &Vector3<f64>, world: &World, attack_type: AttackType) {
match attack_type {
AttackType::Knockback => {
world

View File

@@ -8,8 +8,8 @@ use pumpkin_inventory::container_click::{
};
use pumpkin_inventory::drag_handler::DragHandler;
use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
use pumpkin_inventory::Container;
use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer};
use pumpkin_inventory::{Container, WindowType};
use pumpkin_protocol::client::play::{
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
};
@@ -22,7 +22,7 @@ use std::sync::Arc;
#[expect(unused)]
impl Player {
pub async fn open_container(&self, server: &Server, minecraft_menu_id: &str) {
pub async fn open_container(&self, server: &Server, window_type: WindowType) {
let inventory = self.inventory.lock().await;
inventory
.state_id
@@ -30,17 +30,7 @@ impl Player {
let total_opened_containers = inventory.total_opened_containers;
let container = self.get_open_container(server);
let container = container.as_ref().map(|container| container.lock());
// let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY
// .get("minecraft:menu")
// .unwrap()
// .entries
// .get(minecraft_menu_id)
// .expect("Should be a valid menu id")
// .get("protocol_id")
// .unwrap())
// .into();
// TODO
let menu_protocol_id = VarInt(0);
let window_title = match container {
Some(container) => container.await.window_name(),
None => inventory.window_name(),
@@ -50,7 +40,7 @@ impl Player {
self.client
.send_packet(&COpenScreen::new(
total_opened_containers.into(),
menu_protocol_id,
VarInt(window_type as i32),
title,
))
.await;

View File

@@ -43,7 +43,7 @@ use thiserror::Error;
pub mod authentication;
mod client_packet;
mod combat;
pub mod combat;
mod container;
pub mod player_packet;

View File

@@ -7,7 +7,7 @@ use crate::{
world::player_chunker,
};
use num_traits::FromPrimitive;
use pumpkin_config::{PVPConfig, ADVANCED_CONFIG};
use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_core::math::position::WorldPosition;
use pumpkin_core::{
math::{vector3::Vector3, wrap_degrees},
@@ -15,12 +15,11 @@ use pumpkin_core::{
GameMode,
};
use pumpkin_inventory::{InventoryError, WindowType};
use pumpkin_macros::sound;
use pumpkin_protocol::server::play::{SCloseContainer, SKeepAlive, SSetPlayerGround, SUseItem};
use pumpkin_protocol::{
client::play::{
Animation, CAcknowledgeBlockChange, CEntityAnimation, CHeadRot, CHurtAnimation,
CPingResponse, CPlayerChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot,
FilterType,
Animation, CAcknowledgeBlockChange, CEntityAnimation, CHeadRot, CPingResponse,
CPlayerChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, FilterType,
},
server::play::{
Action, ActionType, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport,
@@ -28,18 +27,10 @@ use pumpkin_protocol::{
SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem,
SSwingArm, SUseItemOn, Status,
},
SoundCategory,
};
use pumpkin_protocol::{
server::play::{SCloseContainer, SKeepAlive, SSetPlayerGround, SUseItem},
VarInt,
};
use pumpkin_world::block::{block_registry::get_block_by_item, BlockFace};
use super::{
combat::{self, player_attack_sound, AttackType},
PlayerConfig,
};
use super::PlayerConfig;
fn modulus(a: f32, b: f32) -> f32 {
((a % b) + b) % b
@@ -435,7 +426,7 @@ impl Player {
return;
};
self.attack(&victim, config).await;
self.attack(&victim).await;
}
ActionType::Interact | ActionType::InteractAt => {
log::debug!("todo");
@@ -443,72 +434,6 @@ impl Player {
}
}
pub async fn attack(&self, victim: &Arc<Self>, config: &PVPConfig) {
let world = &self.living_entity.entity.world;
let victim_entity = &victim.living_entity.entity;
let attacker_entity = &self.living_entity.entity;
let pos = victim_entity.pos.load();
let attack_cooldown_progress = self.get_attack_cooldown_progress(0.5);
self.last_attacked_ticks
.store(0, std::sync::atomic::Ordering::Relaxed);
// TODO: attack damage attribute and deal damage
let damage = 2.0;
if !victim.living_entity.damage(damage)
|| (config.protect_creative && victim.gamemode.load() == GameMode::Creative)
{
world
.play_sound(
sound!("minecraft:entity.player.attack.nodamage"),
SoundCategory::Players,
&pos,
)
.await;
return;
}
world
.play_sound(
sound!("minecraft:entity.player.hurt"),
SoundCategory::Players,
&pos,
)
.await;
let attack_type = AttackType::new(self, attack_cooldown_progress).await;
player_attack_sound(&pos, world, attack_type).await;
// if is_crit {
// damage *= 1.5;
// }
let mut knockback_strength = 1.0;
match attack_type {
AttackType::Knockback => knockback_strength += 1.0,
AttackType::Sweeping => {
combat::spawn_sweep_particle(attacker_entity, world, &pos).await;
}
_ => {}
};
if config.knockback {
combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength)
.await;
}
if config.hurt_animation {
let entity_id = VarInt(victim_entity.entity_id);
world
.broadcast_packet_all(&CHurtAnimation::new(&entity_id, attacker_entity.yaw.load()))
.await;
}
if config.swing {}
}
pub async fn handle_player_action(&self, player_action: SPlayerAction) {
match Status::from_i32(player_action.status.0) {
Some(status) => match status {
@@ -674,6 +599,10 @@ impl Player {
// This function will in the future be used to keep track of if the client is in a valid state.
// But this is not possible yet
pub async fn handle_close_container(&self, server: &Server, packet: SCloseContainer) {
let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else {
self.kick(TextComponent::text("Invalid window ID")).await;
return;
};
// window_id 0 represents both 9x1 Generic AND inventory here
self.inventory
.lock()
@@ -688,9 +617,5 @@ impl Player {
}
self.open_container.store(None);
}
let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else {
self.kick(TextComponent::text("Invalid window ID")).await;
return;
};
}
}

View File

@@ -32,7 +32,9 @@ impl CommandExecutor for EchestExecutor {
open_containers.insert(0, open_container);
}
}
player.open_container(server, "minecraft:generic_9x3").await;
player
.open_container(server, pumpkin_inventory::WindowType::Generic9x3)
.await;
}
Ok(())

View File

@@ -11,6 +11,7 @@ use crossbeam::atomic::AtomicCell;
use itertools::Itertools;
use num_derive::FromPrimitive;
use num_traits::{FromPrimitive, ToPrimitive};
use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_core::{
math::{boundingbox::BoundingBox, position::WorldPosition, vector2::Vector2, vector3::Vector3},
text::TextComponent,
@@ -18,11 +19,13 @@ use pumpkin_core::{
};
use pumpkin_entity::{entity_type::EntityType, EntityId};
use pumpkin_inventory::player::PlayerInventory;
use pumpkin_macros::sound;
use pumpkin_protocol::{
bytebuf::DeserializerError,
client::play::{
CGameEvent, CKeepAlive, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CSetHealth,
CSyncPlayerPosition, CSystemChatMessage, GameEvent, PlayerAction,
CGameEvent, CHurtAnimation, CKeepAlive, CPlayDisconnect, CPlayerAbilities,
CPlayerInfoUpdate, CSetHealth, CSyncPlayerPosition, CSystemChatMessage, GameEvent,
PlayerAction,
},
server::play::{
SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract,
@@ -30,7 +33,7 @@ use pumpkin_protocol::{
SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem,
SUseItemOn, ServerboundPlayPackets,
},
RawPacket, ServerPacket, VarInt,
RawPacket, ServerPacket, SoundCategory, VarInt,
};
use tokio::sync::{Mutex, Notify};
use tokio::task::JoinHandle;
@@ -40,7 +43,11 @@ use pumpkin_world::{cylindrical_chunk_iterator::Cylindrical, item::ItemStack};
use super::Entity;
use crate::{
client::{authentication::GameProfile, Client, PlayerConfig},
client::{
authentication::GameProfile,
combat::{self, player_attack_sound, AttackType},
Client, PlayerConfig,
},
server::Server,
world::World,
};
@@ -331,6 +338,73 @@ impl Player {
//self.living_entity.entity.world.level.list_cached();
}
pub async fn attack(&self, victim: &Arc<Self>) {
let world = &self.living_entity.entity.world;
let victim_entity = &victim.living_entity.entity;
let attacker_entity = &self.living_entity.entity;
let config = &ADVANCED_CONFIG.pvp;
let pos = victim_entity.pos.load();
let attack_cooldown_progress = self.get_attack_cooldown_progress(0.5);
self.last_attacked_ticks
.store(0, std::sync::atomic::Ordering::Relaxed);
// TODO: attack damage attribute and deal damage
let damage = 2.0;
if !victim.living_entity.damage(damage)
|| (config.protect_creative && victim.gamemode.load() == GameMode::Creative)
{
world
.play_sound(
sound!("minecraft:entity.player.attack.nodamage"),
SoundCategory::Players,
&pos,
)
.await;
return;
}
world
.play_sound(
sound!("minecraft:entity.player.hurt"),
SoundCategory::Players,
&pos,
)
.await;
let attack_type = AttackType::new(self, attack_cooldown_progress).await;
player_attack_sound(&pos, world, attack_type).await;
// if is_crit {
// damage *= 1.5;
// }
let mut knockback_strength = 1.0;
match attack_type {
AttackType::Knockback => knockback_strength += 1.0,
AttackType::Sweeping => {
combat::spawn_sweep_particle(attacker_entity, world, &pos).await;
}
_ => {}
};
if config.knockback {
combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength)
.await;
}
if config.hurt_animation {
let entity_id = VarInt(victim_entity.entity_id);
world
.broadcast_packet_all(&CHurtAnimation::new(&entity_id, attacker_entity.yaw.load()))
.await;
}
if config.swing {}
}
pub async fn await_cancel(&self) {
self.cancel_tasks.notified().await;
}

View File

@@ -3,23 +3,12 @@ use std::net::SocketAddr;
use packet::{ClientboundPacket, Packet, PacketError, ServerboundPacket};
use pumpkin_config::{RCONConfig, ADVANCED_CONFIG};
use std::sync::Arc;
use thiserror::Error;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::server::Server;
mod packet;
#[derive(Debug, Error)]
pub enum RCONError {
#[error("authentication failed")]
Auth,
#[error("command exceeds the maximum length")]
CommandTooLong,
#[error("{}", _0)]
Io(std::io::Error),
}
pub struct RCONServer;
impl RCONServer {

View File

@@ -147,7 +147,7 @@ impl Server {
}
pub async fn remove_player(&self) {
// TODO: Config if we want increase online
// TODO: Config if we want decrease online
self.server_listing.lock().await.remove_player();
}