Skip to content

Commit

Permalink
Added spawn egg functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
OfficialKris committed Nov 29, 2024
1 parent 796341b commit 3884181
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 41 deletions.
1 change: 1 addition & 0 deletions assets/entities.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["acacia_boat","acacia_chest_boat","allay","area_effect_cloud","armadillo","armor_stand","arrow","axolotl","bamboo_chest_raft","bamboo_raft","bat","bee","birch_boat","birch_chest_boat","blaze","block_display","bogged","breeze","breeze_wind_charge","camel","cat","cave_spider","cherry_boat","cherry_chest_boat","chest_minecart","chicken","cod","command_block_minecart","cow","creaking","creaking_transient","creeper","dark_oak_boat","dark_oak_chest_boat","dolphin","donkey","dragon_fireball","drowned","egg","elder_guardian","enderman","endermite","ender_dragon","ender_pearl","end_crystal","evoker","evoker_fangs","experience_bottle","experience_orb","eye_of_ender","falling_block","fireball","firework_rocket","fox","frog","furnace_minecart","ghast","giant","glow_item_frame","glow_squid","goat","guardian","hoglin","hopper_minecart","horse","husk","illusioner","interaction","iron_golem","item","item_display","item_frame","jungle_boat","jungle_chest_boat","leash_knot","lightning_bolt","llama","llama_spit","magma_cube","mangrove_boat","mangrove_chest_boat","marker","minecart","mooshroom","mule","oak_boat","oak_chest_boat","ocelot","ominous_item_spawner","painting","pale_oak_boat","pale_oak_chest_boat","panda","parrot","phantom","pig","piglin","piglin_brute","pillager","polar_bear","potion","pufferfish","rabbit","ravager","salmon","sheep","shulker","shulker_bullet","silverfish","skeleton","skeleton_horse","slime","small_fireball","sniffer","snowball","snow_golem","spawner_minecart","spectral_arrow","spider","spruce_boat","spruce_chest_boat","squid","stray","strider","tadpole","text_display","tnt","tnt_minecart","trader_llama","trident","tropical_fish","turtle","vex","villager","vindicator","wandering_trader","warden","wind_charge","witch","wither","wither_skeleton","wither_skull","wolf","zoglin","zombie","zombie_horse","zombie_villager","zombified_piglin","player","fishing_bobber"]
1 change: 1 addition & 0 deletions extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Extractor : ModInitializer {
Tags(),
Items(),
Blocks(),
Entities(),
)

val outputDirectory: Path
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.snowii.extractor.extractors

import com.google.gson.JsonArray
import com.google.gson.JsonElement
import de.snowii.extractor.Extractor
import net.minecraft.registry.Registries
import net.minecraft.server.MinecraftServer


class Entities : Extractor.Extractor {
override fun fileName(): String {
return "entities.json"
}

override fun extract(server: MinecraftServer): JsonElement {
val entitiesJson = JsonArray()
for (entity in Registries.ENTITY_TYPE) {
entitiesJson.add(
Registries.ENTITY_TYPE.getId(entity)!!.path,
)
}

return entitiesJson
}
}
19 changes: 19 additions & 0 deletions pumpkin-world/src/entity/entity_registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::{collections::HashMap, sync::LazyLock};

const ENTITIES_JSON: &str = include_str!("../../../assets/entities.json");

pub static ENTITIES: LazyLock<Vec<String>> = LazyLock::new(|| {
serde_json::from_str(ENTITIES_JSON).expect("Could not parse entity.json registry.")
});

pub static ENTITIES_BY_ID: LazyLock<HashMap<String, u16>> = LazyLock::new(|| {
let mut map = HashMap::new();
for (i, entity_name) in ENTITIES.iter().enumerate() {
map.insert(entity_name.clone(), i as u16);
}
map
});

pub fn get_entity_id(name: &str) -> Option<&u16> {
ENTITIES_BY_ID.get(&name.replace("minecraft:", ""))
}
1 change: 1 addition & 0 deletions pumpkin-world/src/entity/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod entity_registry;
23 changes: 23 additions & 0 deletions pumpkin-world/src/item/item_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,33 @@ pub static ITEMS: LazyLock<HashMap<String, Item>> = LazyLock::new(|| {
serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry.")
});

pub static ITEMS_REGISTRY_ID_BY_ID: LazyLock<HashMap<u16, String>> = LazyLock::new(|| {
let mut map = HashMap::new();
for item in ITEMS.clone() {
map.insert(item.1.id, item.0.clone());
}
map
});

pub fn get_item(name: &str) -> Option<&Item> {
ITEMS.get(&name.replace("minecraft:", ""))
}

pub fn get_item_by_id<'a>(item_id: u16) -> Option<&'a Item> {
ITEMS.values().find(|&item| item.id == item_id)
}

pub fn get_spawn_egg(item_id: u16) -> Option<String> {
if let Some(item_name) = ITEMS_REGISTRY_ID_BY_ID.get(&item_id) {
if item_name.ends_with("_spawn_egg") {
if let Some(res) = item_name.strip_suffix("_spawn_egg") {
return Some(res.to_owned());
}
}
};
None
}

#[derive(Deserialize, Clone, Debug)]
pub struct Item {
pub id: u16,
Expand Down
1 change: 1 addition & 0 deletions pumpkin-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod chunk;
pub mod coordinates;
pub mod cylindrical_chunk_iterator;
pub mod dimension;
pub mod entity;
pub mod item;
pub mod level;
mod world_gen;
Expand Down
155 changes: 115 additions & 40 deletions pumpkin/src/client/player_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ use pumpkin_core::{
text::TextComponent,
GameMode,
};
use pumpkin_entity::entity_type::EntityType;
use pumpkin_inventory::{InventoryError, WindowType};
use pumpkin_protocol::server::play::SCookieResponse as SPCookieResponse;
use pumpkin_protocol::{
client::play::CCommandSuggestions,
server::play::{SCloseContainer, SCommandSuggestion, SKeepAlive, SSetPlayerGround, SUseItem},
VarInt,
};
use pumpkin_protocol::{
client::play::CSpawnEntity, server::play::SCookieResponse as SPCookieResponse,
};
use pumpkin_protocol::{
client::play::{
Animation, CAcknowledgeBlockChange, CEntityAnimation, CHeadRot, CPingResponse,
Expand All @@ -34,7 +37,11 @@ use pumpkin_protocol::{
SSetCreativeSlot, SSetHeldItem, SSwingArm, SUseItemOn, Status,
},
};
use pumpkin_world::block::{block_registry::get_block_by_item, BlockFace};
use pumpkin_world::{
block::{block_registry::get_block_by_item, BlockFace},
entity::entity_registry::get_entity_id,
item::item_registry::get_spawn_egg,
};
use thiserror::Error;

use super::PlayerConfig;
Expand Down Expand Up @@ -608,9 +615,11 @@ impl Player {

pub async fn handle_use_item_on(
&self,
server: &Arc<Server>,
use_item_on: SUseItemOn,
) -> Result<(), Box<dyn PumpkinError>> {
let location = use_item_on.location;
let mut should_try_decrement: bool = false;

if !self.can_interact_with_block_at(&location, 1.0) {
// TODO: maybe log?
Expand All @@ -621,12 +630,20 @@ impl Player {
let mut inventory = self.inventory.lock().await;
let item_slot = inventory.held_item_mut();
if let Some(item) = item_slot {
let block = get_block_by_item(item.item_id);
// check if item is a block, Because Not every item can be placed :D
if let Some(block) = block {
let entity = &self.living_entity.entity;
let world = &entity.world;
if let Some(block) = get_block_by_item(item.item_id) {
should_try_decrement = self
.run_is_block_place(block.default_state_id, use_item_on, location, &face)
.await?;
}
// check if item is a spawn egg
if let Some(item_t) = get_spawn_egg(item.item_id) {
should_try_decrement = self
.run_is_spawn_egg(item_t, server, location, &face)
.await?;
};

if should_try_decrement {
// TODO: Config
// Decrease Block count
if self.gamemode.load() != GameMode::Creative {
Expand All @@ -635,49 +652,107 @@ impl Player {
*item_slot = None;
}
}
}
}
Ok(())
} else {
Err(BlockPlacingError::InvalidBlockFace.into())
}
}

async fn run_is_spawn_egg(
&self,
item_t: String,
server: &Server,
location: WorldPosition,
face: &BlockFace,
) -> Result<bool, Box<dyn PumpkinError>> {
// check if spawn egg has corresponding entity name
if let Some(spawn_item_name) = get_entity_id(&item_t) {
// TODO: should be facing player
let yaw = 10.0;
let pitch = 10.0;
// TODO: should be at precise spot not just the block location
let world_pos = WorldPosition(location.0 + face.to_offset());

let (mob, _world, uuid) = server.add_mob(EntityType::Chicken);

self.client
.send_packet(&CSpawnEntity::new(
VarInt(mob.entity.entity_id),
uuid,
// VarInt(server.new_entity_id()),
// uuid::Uuid::new_v4(),
VarInt((*spawn_item_name).into()),
// (EntityType::Sheep as i32).into(),
world_pos.0.x.into(),
world_pos.0.y.into(),
world_pos.0.z.into(),
pitch,
yaw,
yaw,
0.into(),
0.0,
0.0,
0.0,
))
.await;

let clicked_world_pos = WorldPosition(location.0);
let clicked_block_state = world.get_block_state(clicked_world_pos).await?;
// TODO: send update inventory command?

let world_pos = if clicked_block_state.replaceable {
clicked_world_pos
} else {
let world_pos = WorldPosition(location.0 + face.to_offset());
let previous_block_state = world.get_block_state(world_pos).await?;
// TODO: send/configure additional commands/data based on type of entity (horse, slime, etc)
} else {
// TODO: fix
return Err(BlockPlacingError::BlockOutOfWorld.into());
};

if !previous_block_state.replaceable {
return Ok(());
}
Ok(true)
}

world_pos
};
async fn run_is_block_place(
&self,
block: u16,
use_item_on: SUseItemOn,
location: WorldPosition,
face: &BlockFace,
) -> Result<bool, Box<dyn PumpkinError>> {
let entity = &self.living_entity.entity;
let world = &entity.world;

//check max world build height
if world_pos.0.y > 319 {
self.client
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
.await;
return Err(BlockPlacingError::BlockOutOfWorld.into());
}
let clicked_world_pos = WorldPosition(location.0);
let clicked_block_state = world.get_block_state(clicked_world_pos).await?;

let block_bounding_box = BoundingBox::from_block(&world_pos);
let bounding_box = entity.bounding_box.load();
//TODO: Make this check for every entity in that posistion
if !bounding_box.intersects(&block_bounding_box) {
world
.set_block_state(world_pos, block.default_state_id)
.await;
}
}
self.client
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
.await;
let world_pos = if clicked_block_state.replaceable {
clicked_world_pos
} else {
let world_pos = WorldPosition(location.0 + face.to_offset());
let previous_block_state = world.get_block_state(world_pos).await?;

if !previous_block_state.replaceable {
return Ok(true);
}

Ok(())
} else {
Err(BlockPlacingError::InvalidBlockFace.into())
world_pos
};

//check max world build height
if world_pos.0.y > 319 {
self.client
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
.await;
return Err(BlockPlacingError::BlockOutOfWorld.into());
}

let block_bounding_box = BoundingBox::from_block(&world_pos);
let bounding_box = entity.bounding_box.load();
//TODO: Make this check for every entity in that posistion
if !bounding_box.intersects(&block_bounding_box) {
world.set_block_state(world_pos, block).await;
}
self.client
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
.await;
Ok(true)
}

pub fn handle_use_item(&self, _use_item: &SUseItem) {
Expand Down
3 changes: 2 additions & 1 deletion pumpkin/src/entity/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,8 @@ impl Player {
self.handle_swing_arm(SSwingArm::read(bytebuf)?).await;
}
SUseItemOn::PACKET_ID => {
self.handle_use_item_on(SUseItemOn::read(bytebuf)?).await?;
self.handle_use_item_on(server, SUseItemOn::read(bytebuf)?)
.await?;
}
SUseItem::PACKET_ID => self.handle_use_item(&SUseItem::read(bytebuf)?),
SCommandSuggestion::PACKET_ID => {
Expand Down
31 changes: 31 additions & 0 deletions pumpkin/src/server/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use connection_cache::{CachedBranding, CachedStatus};
use crossbeam::atomic::AtomicCell;
use key_store::KeyStore;
use pumpkin_config::BASIC_CONFIG;
use pumpkin_core::math::boundingbox::{BoundingBox, BoundingBoxSize};
use pumpkin_core::GameMode;
use pumpkin_entity::entity_type::EntityType;
use pumpkin_entity::EntityId;
use pumpkin_inventory::drag_handler::DragHandler;
use pumpkin_inventory::{Container, OpenContainer};
Expand All @@ -19,8 +22,11 @@ use std::{
time::Duration,
};
use tokio::sync::{Mutex, RwLock};
use uuid::Uuid;

use crate::client::EncryptionError;
use crate::entity::living::LivingEntity;
use crate::entity::Entity;
use crate::world::custom_bossbar::CustomBossbars;
use crate::{
client::Client,
Expand Down Expand Up @@ -166,6 +172,31 @@ impl Server {
self.server_listing.lock().await.remove_player();
}

// TODO: move to world
pub fn add_mob(&self, entity_type: EntityType) -> (Arc<LivingEntity>, Arc<World>, Uuid) {
let entity_id = self.new_entity_id();
// Basically the default world
// TODO: select default from config
let world = &self.worlds[0];

// TODO: set per each mob
let bounding_box_size = BoundingBoxSize {
width: 0.6,
height: 1.8,
};

let mob = Arc::new(LivingEntity::new(Entity::new(
entity_id,
world.clone(),
entity_type,
1.62,
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
AtomicCell::new(bounding_box_size),
)));

(mob, world.clone(), uuid::Uuid::new_v4())
}

pub async fn try_get_container(
&self,
player_id: EntityId,
Expand Down

0 comments on commit 3884181

Please sign in to comment.