mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: add map creation
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -639,6 +639,7 @@ pub struct Block {
|
||||
pub hardness: f32,
|
||||
/// Blast resistance against explosions.
|
||||
pub blast_resistance: f32,
|
||||
pub map_color: u8,
|
||||
/// Numeric ID of the corresponding item, if any.
|
||||
pub item_id: u16,
|
||||
/// Flammability data, present only if the block can catch fire.
|
||||
@@ -668,6 +669,8 @@ impl ToTokens for Block {
|
||||
//let translation_key = LitStr::new(&self.translation_key, Span::call_site());
|
||||
let hardness = &self.hardness;
|
||||
let blast_resistance = &self.blast_resistance;
|
||||
let map_color = &self.map_color;
|
||||
|
||||
let item_id = LitInt::new(&self.item_id.to_string(), Span::call_site());
|
||||
let slipperiness = &self.slipperiness;
|
||||
let velocity_multiplier = &self.velocity_multiplier;
|
||||
@@ -707,6 +710,7 @@ impl ToTokens for Block {
|
||||
name: #name,
|
||||
hardness: #hardness,
|
||||
blast_resistance: #blast_resistance,
|
||||
map_color: #map_color,
|
||||
slipperiness: #slipperiness,
|
||||
velocity_multiplier: #velocity_multiplier,
|
||||
jump_velocity_multiplier: #jump_velocity_multiplier,
|
||||
|
||||
@@ -24,6 +24,7 @@ pub struct Block {
|
||||
pub hardness: f32,
|
||||
/// The block's resistance to explosions.
|
||||
pub blast_resistance: f32,
|
||||
pub map_color: u8,
|
||||
/// The friction coefficient. Default is 0.6; Ice is 0.98.
|
||||
pub slipperiness: f32,
|
||||
/// How much this block affects the speed of an entity walking on it (e.g., Soul Sand).
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::data_component::DataComponent;
|
||||
use crate::data_component::DataComponent::{
|
||||
AttributeModifiers, BlocksAttacks, Consumable, CustomData, CustomName, Damage, DamageResistant,
|
||||
DeathProtection, Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food,
|
||||
ItemModel, ItemName, JukeboxPlayable, MaxDamage, MaxStackSize, PotionContents,
|
||||
ItemModel, ItemName, JukeboxPlayable, MapId, MaxDamage, MaxStackSize, PotionContents,
|
||||
StoredEnchantments, Tool, Unbreakable, UseCooldown, Weapon,
|
||||
};
|
||||
use crate::effect::{self, StatusEffect};
|
||||
@@ -69,6 +69,7 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option<Box<dyn DataCompone
|
||||
Equippable => Some(EquippableImpl::read_data(data)?.to_dyn()),
|
||||
StoredEnchantments => Some(StoredEnchantmentsImpl::read_data(data)?.to_dyn()),
|
||||
UseCooldown => Some(UseCooldownImpl::read_data(data)?.to_dyn()),
|
||||
MapId => Some(MapIdImpl::read_data(data)?.to_dyn()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -83,6 +84,7 @@ pub fn read_data_pnbt(
|
||||
Enchantments => Some(EnchantmentsImpl::read_data_pnbt(nbt)?.to_dyn()),
|
||||
Damage => Some(DamageImpl::read_data_pnbt(nbt)?.to_dyn()),
|
||||
Unbreakable => Some(UnbreakableImpl::read_data_pnbt(nbt)?.to_dyn()),
|
||||
MapId => Some(MapIdImpl::read_data_pnbt(nbt)?.to_dyn()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -414,6 +416,7 @@ fn hash() {
|
||||
-1580618251i32
|
||||
);
|
||||
assert_eq!(MaxStackSizeImpl { size: 99 }.get_hash(), -1632321551i32);
|
||||
assert_eq!(MapIdImpl { id: 10 }.get_hash(), -919192125i32);
|
||||
}
|
||||
|
||||
impl DataComponentImpl for EnchantmentsImpl {
|
||||
@@ -1557,7 +1560,35 @@ pub struct DyedColorImpl;
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MapColorImpl;
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MapIdImpl;
|
||||
pub struct MapIdImpl {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
impl MapIdImpl {
|
||||
fn read_data(data: &NbtTag) -> Option<Self> {
|
||||
data.extract_int().map(|id| Self { id })
|
||||
}
|
||||
|
||||
fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option<Self> {
|
||||
nbt.get_i32().ok().map(|id| Self { id })
|
||||
}
|
||||
}
|
||||
|
||||
impl DataComponentImpl for MapIdImpl {
|
||||
fn write_data(&self) -> NbtTag {
|
||||
NbtTag::Int(self.id)
|
||||
}
|
||||
|
||||
fn write_data_pnbt(&self, nbt: &mut PNbtCompound) {
|
||||
nbt.put_i32(self.id);
|
||||
}
|
||||
|
||||
fn get_hash(&self) -> i32 {
|
||||
get_i32_hash(self.id) as i32
|
||||
}
|
||||
|
||||
default_impl!(MapId);
|
||||
}
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MapDecorationsImpl;
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@ use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::data_component_impl::{
|
||||
ConsumableImpl, ConsumeAnimation, ConsumeEffect, CustomNameImpl, DamageImpl, DataComponentImpl,
|
||||
EnchantmentsImpl, EquipmentSlot, EquippableImpl, FireworkExplosionImpl, FireworkExplosionShape,
|
||||
FireworksImpl, IDSet, IDSetContent, IdOr, ItemModelImpl, MaxStackSizeImpl, PotionContentsImpl,
|
||||
SoundEvent, StatusEffectInstance, StoredEnchantmentsImpl, UnbreakableImpl, UseCooldownImpl,
|
||||
get,
|
||||
FireworksImpl, IDSet, IDSetContent, IdOr, ItemModelImpl, MapIdImpl, MaxStackSizeImpl,
|
||||
PotionContentsImpl, SoundEvent, StatusEffectInstance, StoredEnchantmentsImpl, UnbreakableImpl,
|
||||
UseCooldownImpl, get,
|
||||
};
|
||||
use pumpkin_data::effect::StatusEffect;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
@@ -844,6 +844,7 @@ pub fn deserialize<'a, A: SeqAccess<'a>>(
|
||||
DataComponent::Equippable => Ok(EquippableImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::StoredEnchantments => Ok(StoredEnchantmentsImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::UseCooldown => Ok(UseCooldownImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::MapId => Ok(MapIdImpl::deserialize(seq)?.to_dyn()),
|
||||
_ => Err(serde::de::Error::custom(format!("{id:?} (TODO)"))),
|
||||
}
|
||||
}
|
||||
@@ -866,10 +867,25 @@ pub fn serialize<T: SerializeStruct>(
|
||||
DataComponent::Equippable => get::<EquippableImpl>(value).serialize(seq),
|
||||
DataComponent::StoredEnchantments => get::<StoredEnchantmentsImpl>(value).serialize(seq),
|
||||
DataComponent::UseCooldown => get::<UseCooldownImpl>(value).serialize(seq),
|
||||
DataComponent::MapId => get::<MapIdImpl>(value).serialize(seq),
|
||||
_ => todo!("{} not yet implemented", id.to_name()),
|
||||
}
|
||||
}
|
||||
|
||||
impl DataComponentCodec<Self> for MapIdImpl {
|
||||
fn serialize<T: SerializeStruct>(&self, seq: &mut T) -> Result<(), T::Error> {
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(self.id))
|
||||
}
|
||||
|
||||
fn deserialize<'a, A: SeqAccess<'a>>(seq: &mut A) -> Result<Self, A::Error> {
|
||||
let id = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("No MapId VarInt!"))?
|
||||
.0;
|
||||
Ok(Self { id })
|
||||
}
|
||||
}
|
||||
|
||||
impl DataComponentCodec<Self> for UseCooldownImpl {
|
||||
fn serialize<T: SerializeStruct>(&self, seq: &mut T) -> Result<(), T::Error> {
|
||||
seq.serialize_field::<f32>("", &self.seconds)?;
|
||||
|
||||
75
pumpkin-protocol/src/java/client/play/map_item_data.rs
Normal file
75
pumpkin-protocol/src/java/client/play/map_item_data.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::{ClientPacket, VarInt, WritingError, ser::NetworkWriteExt};
|
||||
use pumpkin_data::packet::clientbound::PLAY_MAP_ITEM_DATA;
|
||||
use pumpkin_macros::java_packet;
|
||||
use std::io::Write;
|
||||
|
||||
#[java_packet(PLAY_MAP_ITEM_DATA)]
|
||||
pub struct CMapItemData<'a> {
|
||||
pub map_id: VarInt,
|
||||
pub scale: i8,
|
||||
pub locked: bool,
|
||||
pub icons: Option<&'a [MapIcon]>,
|
||||
pub data: Option<MapPatch<'a>>,
|
||||
}
|
||||
|
||||
pub struct MapIcon {
|
||||
pub icon_type: VarInt,
|
||||
pub x: i8,
|
||||
pub z: i8,
|
||||
pub direction: i8,
|
||||
pub display_name: Option<String>,
|
||||
}
|
||||
|
||||
pub struct MapPatch<'a> {
|
||||
pub columns: u8,
|
||||
pub rows: u8,
|
||||
pub x: i8,
|
||||
pub z: i8,
|
||||
pub data: &'a [u8],
|
||||
}
|
||||
|
||||
impl ClientPacket for CMapItemData<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl Write,
|
||||
_version: &pumpkin_util::version::MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
write.write_var_int(&self.map_id)?;
|
||||
write.write_i8(self.scale)?;
|
||||
write.write_bool(self.locked)?;
|
||||
|
||||
if let Some(icons) = self.icons {
|
||||
write.write_bool(true)?;
|
||||
write.write_var_int(&VarInt(icons.len() as i32))?;
|
||||
for icon in icons {
|
||||
write.write_var_int(&icon.icon_type)?;
|
||||
write.write_i8(icon.x)?;
|
||||
write.write_i8(icon.z)?;
|
||||
write.write_i8(icon.direction)?;
|
||||
if let Some(name) = &icon.display_name {
|
||||
write.write_bool(true)?;
|
||||
write.write_string(&format!("{{\"text\":\"{name}\"}}"))?;
|
||||
} else {
|
||||
write.write_bool(false)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
write.write_bool(false)?;
|
||||
}
|
||||
|
||||
if let Some(patch) = &self.data {
|
||||
write.write_u8(patch.columns)?;
|
||||
if patch.columns > 0 {
|
||||
write.write_u8(patch.rows)?;
|
||||
write.write_i8(patch.x)?;
|
||||
write.write_i8(patch.z)?;
|
||||
write.write_var_int(&VarInt(patch.data.len() as i32))?;
|
||||
write.write_all(patch.data).map_err(WritingError::IoError)?;
|
||||
}
|
||||
} else {
|
||||
write.write_u8(0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ mod keep_alive;
|
||||
mod level_event;
|
||||
mod light_update;
|
||||
mod login;
|
||||
mod map_item_data;
|
||||
mod merchant_offers;
|
||||
mod multi_block_update;
|
||||
mod open_screen;
|
||||
@@ -139,6 +140,7 @@ pub use keep_alive::*;
|
||||
pub use level_event::*;
|
||||
pub use light_update::*;
|
||||
pub use login::*;
|
||||
pub use map_item_data::*;
|
||||
pub use merchant_offers::*;
|
||||
pub use multi_block_update::*;
|
||||
pub use open_screen::*;
|
||||
|
||||
@@ -237,6 +237,7 @@ mod test {
|
||||
snapshot: false,
|
||||
series: "main".to_string(),
|
||||
},
|
||||
map_id: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ pub struct LevelData {
|
||||
pub world_version: WorldVersion,
|
||||
#[serde(rename = "version", default = "default_level_version")]
|
||||
pub level_version: i32,
|
||||
#[serde(rename = "map_id", default)]
|
||||
pub map_id: i32,
|
||||
}
|
||||
|
||||
const DEFAULT_BORDER_DAMAGE_PER_BLOCK: f64 = 0.2;
|
||||
@@ -325,6 +327,7 @@ impl LevelData {
|
||||
spawn_pitch: 0.0,
|
||||
world_version: WorldVersion::default(),
|
||||
level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION,
|
||||
map_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,12 +61,12 @@ use pumpkin_protocol::java::client::play::{
|
||||
Animation, CAcknowledgeBlockChange, CActionBar, CChangeDifficulty, CChunkBatchEnd,
|
||||
CChunkBatchStart, CChunkData, CCloseContainer, CCombatDeath, CCustomPayload,
|
||||
CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, CGameEvent, CItemCooldown,
|
||||
CKeepAlive, COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition,
|
||||
CPlayerSpawnPosition, CRespawn, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
|
||||
CSetCursorItem, CSetEquipment, CSetExperience, CSetHealth, CSetPlayerInventory,
|
||||
CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle, CSystemChatMessage, CTabList,
|
||||
CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent, Metadata,
|
||||
PlayerAction, PlayerInfoFlags, PreviousMessage,
|
||||
CKeepAlive, CMapItemData, COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate,
|
||||
CPlayerPosition, CPlayerSpawnPosition, CRespawn, CSetContainerContent, CSetContainerProperty,
|
||||
CSetContainerSlot, CSetCursorItem, CSetEquipment, CSetExperience, CSetHealth,
|
||||
CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle, CSystemChatMessage,
|
||||
CTabList, CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent,
|
||||
MapIcon, MapPatch, Metadata, PlayerAction, PlayerInfoFlags, PreviousMessage,
|
||||
};
|
||||
use pumpkin_protocol::java::server::play::{
|
||||
SClickSlot, SContainerButtonClick, SRenameItem, SlotActionType,
|
||||
@@ -1803,6 +1803,7 @@ impl Player {
|
||||
// experience handling
|
||||
self.tick_experience().await;
|
||||
self.tick_health().await;
|
||||
self.tick_maps(server).await;
|
||||
|
||||
// Timeout/keep alive handling
|
||||
self.tick_client_load_timeout();
|
||||
@@ -2825,6 +2826,67 @@ impl Player {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn tick_maps(&self, server: &Server) {
|
||||
use pumpkin_data::data_component_impl::MapIdImpl;
|
||||
use pumpkin_data::item::Item;
|
||||
|
||||
for hand in Hand::all() {
|
||||
let item_in_hand = self.inventory.get_stack_in_hand(hand).await;
|
||||
|
||||
let stack = item_in_hand.lock().await;
|
||||
if stack.item.id == Item::FILLED_MAP.id
|
||||
&& let Some(map_id_comp) = stack.get_data_component::<MapIdImpl>() {
|
||||
let map_id = map_id_comp.id;
|
||||
if let Some(map_data_arc) = server.map_manager.get_map(map_id) {
|
||||
let mut map_data = map_data_arc.lock().await;
|
||||
map_data.update(self).await;
|
||||
|
||||
let tick_count = self.tick_counter.load(Ordering::Relaxed);
|
||||
if map_data.dirty || tick_count % 10 == 0 {
|
||||
let scale = 1 << map_data.scale;
|
||||
let pos = self.position();
|
||||
let dx = pos.x - map_data.center_x as f64;
|
||||
let dz = pos.z - map_data.center_z as f64;
|
||||
|
||||
let icon_x = (dx / scale as f64 * 2.0).clamp(-128.0, 127.0) as i8;
|
||||
let icon_z = (dz / scale as f64 * 2.0).clamp(-128.0, 127.0) as i8;
|
||||
|
||||
let yaw = self.living_entity.entity.yaw.load();
|
||||
let icon_direction =
|
||||
((((yaw * 16.0 / 360.0).round() as i32 + 8) % 16 + 16) % 16) as i8;
|
||||
|
||||
let icons = [MapIcon {
|
||||
icon_type: VarInt(0), // White pointer
|
||||
x: icon_x,
|
||||
z: icon_z,
|
||||
direction: icon_direction,
|
||||
display_name: None,
|
||||
}];
|
||||
|
||||
let data = map_data.dirty.then(|| MapPatch {
|
||||
columns: 128,
|
||||
rows: 128,
|
||||
x: 0,
|
||||
z: 0,
|
||||
data: &*map_data.colors,
|
||||
});
|
||||
|
||||
self.client
|
||||
.enqueue_packet(&CMapItemData {
|
||||
map_id: VarInt(map_id),
|
||||
scale: map_data.scale,
|
||||
locked: map_data.locked,
|
||||
icons: Some(&icons),
|
||||
data,
|
||||
})
|
||||
.await;
|
||||
map_data.dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the player's experience level and notifies the client.
|
||||
pub async fn set_experience(&self, level: i32, progress: f32, points: i32) {
|
||||
// TODO: These should be atomic together, not isolated; make a struct containing these. can cause ABA issues
|
||||
|
||||
75
pumpkin/src/item/items/map.rs
Normal file
75
pumpkin/src/item/items/map.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::ItemBehaviour;
|
||||
use crate::item::ItemMetadata;
|
||||
use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::data_component_impl::DataComponentImpl;
|
||||
use pumpkin_data::data_component_impl::MapIdImpl;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_util::GameMode;
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
pub struct MapItem;
|
||||
|
||||
impl ItemMetadata for MapItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
[Item::MAP.id].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for MapItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let server = player.world().server.upgrade().unwrap();
|
||||
|
||||
let inventory = player.inventory();
|
||||
let main_hand_item = inventory.held_item();
|
||||
let off_hand_item = inventory.off_hand_item().await;
|
||||
let mut hand_stack = main_hand_item.lock().await;
|
||||
|
||||
let found = if !hand_stack.is_empty() && hand_stack.item.id == Item::MAP.id {
|
||||
true
|
||||
} else {
|
||||
drop(hand_stack);
|
||||
hand_stack = off_hand_item.lock().await;
|
||||
!hand_stack.is_empty() && hand_stack.item.id == Item::MAP.id
|
||||
};
|
||||
|
||||
if found {
|
||||
let map_id = server.next_map_id();
|
||||
let _ = server.map_manager.create_map(
|
||||
map_id,
|
||||
player.world().dimension,
|
||||
player.position().x as i32,
|
||||
player.position().z as i32,
|
||||
0, // Default scale
|
||||
);
|
||||
|
||||
let mut filled_map = ItemStack::new(1, &Item::FILLED_MAP);
|
||||
filled_map.patch.push((
|
||||
DataComponent::MapId,
|
||||
Some(MapIdImpl { id: map_id }.to_dyn()),
|
||||
));
|
||||
|
||||
let gamemode = player.gamemode.load();
|
||||
if hand_stack.item_count == 1 && gamemode != GameMode::Creative {
|
||||
*hand_stack = filled_map;
|
||||
} else {
|
||||
hand_stack.decrement_unless_creative(gamemode, 1);
|
||||
drop(hand_stack);
|
||||
inventory.offer_or_drop_stack(filled_map, player).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ pub mod honeycomb;
|
||||
pub mod ignite;
|
||||
pub mod ink_sac;
|
||||
pub mod mace;
|
||||
pub mod map;
|
||||
pub mod minecart;
|
||||
pub mod name_tag;
|
||||
pub mod potions;
|
||||
@@ -30,6 +31,7 @@ use crate::item::items::armor_stand::ArmorStandItem;
|
||||
use crate::item::items::boat::BoatItem;
|
||||
use crate::item::items::end_crystal::EndCrystalItem;
|
||||
use crate::item::items::firework_rocket::FireworkRocketItem;
|
||||
use crate::item::items::map::MapItem;
|
||||
use crate::item::items::minecart::MinecartItem;
|
||||
use crate::item::items::name_tag::NameTagItem;
|
||||
use crate::item::items::spawn_egg::SpawnEggItem;
|
||||
@@ -87,6 +89,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
||||
manager.register(EnderPearlItem);
|
||||
manager.register(FireChargeItem);
|
||||
manager.register(DyeItem);
|
||||
manager.register(MapItem);
|
||||
manager.register(FireworkRocketItem);
|
||||
manager.register(InkSacItem);
|
||||
manager.register(GlowingInkSacItem);
|
||||
|
||||
@@ -12,7 +12,10 @@ use crate::plugin::player::player_login::PlayerLoginEvent;
|
||||
use crate::plugin::server::server_broadcast::ServerBroadcastEvent;
|
||||
use crate::server::tick_rate_manager::ServerTickRateManager;
|
||||
use crate::world::custom_bossbar::CustomBossbars;
|
||||
use crate::{command::node::dispatcher::CommandDispatcher, entity::player::Player, world::World};
|
||||
use crate::{
|
||||
command::node::dispatcher::CommandDispatcher, entity::player::Player, world::World,
|
||||
world::map::MapManager,
|
||||
};
|
||||
use arc_swap::ArcSwap;
|
||||
use connection_cache::{CachedBranding, CachedStatus};
|
||||
use key_store::KeyStore;
|
||||
@@ -94,11 +97,15 @@ pub struct Server {
|
||||
pub dimensions: Vec<Dimension>,
|
||||
/// Assigns unique IDs to containers.
|
||||
container_id: AtomicU32,
|
||||
/// Assigns unique IDs to maps.
|
||||
map_id: AtomicI32,
|
||||
/// Mojang's public keys, used for chat session signing
|
||||
/// Pulled from Mojang API on startup
|
||||
pub mojang_public_keys: ArcSwap<Vec<RsaPublicKey>>,
|
||||
/// The server's custom bossbars
|
||||
pub bossbars: Mutex<CustomBossbars>,
|
||||
/// Manages all maps on the server
|
||||
pub map_manager: MapManager,
|
||||
/// The default gamemode when a player joins the server (reset every restart)
|
||||
pub defaultgamemode: Mutex<DefaultGamemode>,
|
||||
/// Manages player data storage
|
||||
@@ -214,6 +221,7 @@ impl Server {
|
||||
))),
|
||||
permission_registry,
|
||||
container_id: 0.into(),
|
||||
map_id: level_info.load().map_id.into(),
|
||||
worlds: ArcSwap::from_pointee(vec![]),
|
||||
dimensions: vec![
|
||||
Dimension::OVERWORLD,
|
||||
@@ -228,6 +236,7 @@ impl Server {
|
||||
listing,
|
||||
branding: CachedBranding::new(),
|
||||
bossbars: Mutex::new(CustomBossbars::new()),
|
||||
map_manager: MapManager::new(),
|
||||
defaultgamemode,
|
||||
player_data_storage,
|
||||
white_list,
|
||||
@@ -761,6 +770,17 @@ impl Server {
|
||||
self.container_id.fetch_add(1, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Generates a new map id.
|
||||
pub fn next_map_id(&self) -> i32 {
|
||||
let id = self.map_id.fetch_add(1, Ordering::SeqCst);
|
||||
self.level_info.rcu(|level_info| {
|
||||
let mut new_level_info = (**level_info).clone();
|
||||
new_level_info.map_id = self.map_id.load(Ordering::SeqCst);
|
||||
new_level_info
|
||||
});
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get_branding(&self) -> CPluginMessage<'_> {
|
||||
self.branding.get_branding()
|
||||
}
|
||||
|
||||
134
pumpkin/src/world/map.rs
Normal file
134
pumpkin/src/world/map.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use crate::entity::player::Player;
|
||||
use dashmap::DashMap;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct MapManager {
|
||||
pub maps: DashMap<i32, Arc<Mutex<MapData>>>,
|
||||
}
|
||||
|
||||
impl Default for MapManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MapManager {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
maps: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_map(&self, id: i32) -> Option<Arc<Mutex<MapData>>> {
|
||||
self.maps.get(&id).map(|m| m.clone())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn create_map(
|
||||
&self,
|
||||
id: i32,
|
||||
dimension: Dimension,
|
||||
x: i32,
|
||||
z: i32,
|
||||
scale: i8,
|
||||
) -> Arc<Mutex<MapData>> {
|
||||
let map = Arc::new(Mutex::new(MapData::new(dimension, x, z, scale)));
|
||||
self.maps.insert(id, map.clone());
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapData {
|
||||
pub scale: i8,
|
||||
pub locked: bool,
|
||||
pub dimension: Dimension,
|
||||
pub center_x: i32,
|
||||
pub center_z: i32,
|
||||
pub colors: Box<[u8; 128 * 128]>,
|
||||
pub dirty: bool,
|
||||
pub fully_updated: bool,
|
||||
}
|
||||
|
||||
impl MapData {
|
||||
#[must_use]
|
||||
pub fn new(dimension: Dimension, x: i32, z: i32, scale: i8) -> Self {
|
||||
Self {
|
||||
scale,
|
||||
locked: false,
|
||||
dimension,
|
||||
center_x: x,
|
||||
center_z: z,
|
||||
colors: Box::new([0; 128 * 128]),
|
||||
dirty: true,
|
||||
fully_updated: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, x: usize, z: usize, color: u8) {
|
||||
if x < 128 && z < 128 {
|
||||
let idx = z * 128 + x;
|
||||
if self.colors[idx] != color {
|
||||
self.colors[idx] = color;
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(&mut self, player: &Player) {
|
||||
let world = player.world();
|
||||
let scale = 1 << self.scale;
|
||||
let center_x = self.center_x;
|
||||
let center_z = self.center_z;
|
||||
|
||||
let player_pos = player.position();
|
||||
let player_x = player_pos.x as i32;
|
||||
let player_z = player_pos.z as i32;
|
||||
|
||||
let start_img_x = ((player_x - center_x) / scale + 64).clamp(0, 127) as usize;
|
||||
let start_img_z = ((player_z - center_z) / scale + 64).clamp(0, 127) as usize;
|
||||
|
||||
let radius = 16;
|
||||
let (range_x, range_z) = if self.fully_updated {
|
||||
(
|
||||
(start_img_x.saturating_sub(radius))..(start_img_x + radius).min(128),
|
||||
(start_img_z.saturating_sub(radius))..(start_img_z + radius).min(128),
|
||||
)
|
||||
} else {
|
||||
self.fully_updated = true;
|
||||
(0..128, 0..128)
|
||||
};
|
||||
|
||||
for img_x in range_x {
|
||||
let mut prev_y = -1;
|
||||
for img_z in range_z.clone() {
|
||||
let world_x = (img_x as i32 - 64) * scale + center_x;
|
||||
let world_z = (img_z as i32 - 64) * scale + center_z;
|
||||
|
||||
let top_y = world.get_top_block(Vector2::new(world_x, world_z)).await;
|
||||
let block = world
|
||||
.get_block(&BlockPos::new(world_x, top_y, world_z))
|
||||
.await;
|
||||
|
||||
let color_base = block.map_color;
|
||||
|
||||
let mut brightness = 2; // Normal
|
||||
if prev_y != -1 {
|
||||
if top_y > prev_y {
|
||||
brightness = 3; // High
|
||||
} else if top_y < prev_y {
|
||||
brightness = 1; // Low
|
||||
}
|
||||
}
|
||||
prev_y = top_y;
|
||||
|
||||
let color = color_base * 4 + brightness;
|
||||
self.set_color(img_x, img_z, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use tracing::{debug, error, info, trace, warn};
|
||||
pub mod chunker;
|
||||
pub mod explosion;
|
||||
pub mod loot;
|
||||
pub mod map;
|
||||
pub mod portal;
|
||||
pub mod time;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user