use pumpkin_util::text::TextComponent; use std::sync::atomic::Ordering; use crate::{ java::native_callbacks::CALLBACK_CONTEXT, proto::patchbukkit::{ common::{EmptyRequest, Uuid as ProtoUuid}, server::{ BanEntryProto, BroadcastMessageRequest, CreateWorldRequest, CreateWorldResponse, GetBanListRequest, GetBanListResponse, GetOperatorsResponse, GetWhitelistResponse, OperatorEntryProto, ServerInfoResponse, ServerTickInfoResponse, SetBanEntryRequest, SetOperatorRequest, SetServerDefaultGamemodeRequest, SetServerIdleTimeoutRequest, SetServerMaxPlayersRequest, SetServerMotdRequest, SetServerTickRateRequest, SetServerWhitelistEnforcedRequest, SetServerWhitelistRequest, SetWhitelistPlayerRequest, ShutdownServerRequest, UnloadWorldRequest, UnloadWorldResponse, WhitelistEntryProto, }, }, }; pub fn ffi_native_bridge_get_server_info_impl( _request: EmptyRequest, ) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let server = &ctx.plugin_context.server; let java_config = &server.advanced_config.networking.java; let motd = java_config.motd.clone(); let ip = java_config.address.ip().to_string(); let port = java_config.address.port() as i32; let max_players = java_config.max_players as i32; let view_distance = java_config.view_distance.get() as i32; let simulation_distance = java_config.simulation_distance.get() as i32; let allow_flight = true; let allow_nether = server.basic_config.allow_nether; let allow_end = server.basic_config.allow_end; let online_mode = java_config.online_mode; let hardcore = server.basic_config.hardcore; let pvp = server.advanced_config.pvp.enabled; let has_whitelist = server.white_list.load(Ordering::Relaxed); let is_whitelist_enforced = server.basic_config.enforce_whitelist; let spawn_protection = 16; let idle_timeout = server.player_idle_timeout.load(Ordering::Relaxed); let default_gamemode = if let Ok(gm) = server.defaultgamemode.try_lock() { format!("{:?}", gm.gamemode) } else { "SURVIVAL".to_string() }; Some(ServerInfoResponse { server_name: "Pumpkin".to_string(), version: "0.1.0".to_string(), bukkit_version: format!("{}-R0.1-SNAPSHOT", pumpkin_data::packet::CURRENT_MC_VERSION), minecraft_version: pumpkin_data::packet::CURRENT_MC_VERSION.to_string(), motd, ip, port, max_players, view_distance, simulation_distance, allow_flight, allow_nether, allow_end, online_mode, hardcore, pvp, has_whitelist, is_whitelist_enforced, spawn_protection, default_gamemode, idle_timeout, generate_structures: true, max_world_size: 29999984, }) } pub fn ffi_native_bridge_set_server_motd_impl(_request: SetServerMotdRequest) -> Option<()> { Some(()) } pub fn ffi_native_bridge_set_server_max_players_impl( _request: SetServerMaxPlayersRequest, ) -> Option<()> { Some(()) } pub fn ffi_native_bridge_set_server_whitelist_impl( request: SetServerWhitelistRequest, ) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; ctx.plugin_context .server .white_list .store(request.enabled, Ordering::Relaxed); Some(()) } pub fn ffi_native_bridge_set_server_whitelist_enforced_impl( _request: SetServerWhitelistEnforcedRequest, ) -> Option<()> { Some(()) } pub fn ffi_native_bridge_set_server_idle_timeout_impl( request: SetServerIdleTimeoutRequest, ) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; ctx.plugin_context .server .player_idle_timeout .store(request.timeout_minutes, Ordering::Relaxed); Some(()) } pub fn ffi_native_bridge_set_server_default_gamemode_impl( request: SetServerDefaultGamemodeRequest, ) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; if let Ok(mut gm) = ctx.plugin_context.server.defaultgamemode.try_lock() { let mode = match request.gamemode.to_uppercase().as_str() { "CREATIVE" => pumpkin_util::GameMode::Creative, "ADVENTURE" => pumpkin_util::GameMode::Adventure, "SPECTATOR" => pumpkin_util::GameMode::Spectator, _ => pumpkin_util::GameMode::Survival, }; gm.gamemode = mode; } Some(()) } pub fn ffi_native_bridge_get_server_tick_info_impl( _request: EmptyRequest, ) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let server = &ctx.plugin_context.server; let tick_rate = server.tick_rate_manager.tickrate(); let is_frozen = server.tick_rate_manager.is_frozen(); let is_sprinting = server.tick_rate_manager.is_sprinting(); let is_stepping = server.tick_rate_manager.is_stepping_forward(); let tick_count = server.tick_count.load(Ordering::Relaxed) as i64; let avg_nanos = server.aggregated_tick_times_nanos.load(Ordering::Relaxed); let average_tick_time = (avg_nanos as f64) / 1_000_000.0 / 100.0; let mut tick_times_nanos = Vec::new(); if let Ok(times) = server.tick_times_nanos.try_lock() { tick_times_nanos.extend_from_slice(&*times); } else { tick_times_nanos.resize(100, 50_000_000); } let tps_val = (1_000.0 / (average_tick_time.max(50.0))).min(tick_rate as f64); let tps = vec![tps_val, tps_val, tps_val]; Some(ServerTickInfoResponse { tps, tick_rate, is_frozen, is_sprinting, is_stepping, average_tick_time, tick_count, tick_times_nanos, }) } pub fn ffi_native_bridge_set_server_tick_rate_impl( request: SetServerTickRateRequest, ) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; let server = &ctx.plugin_context.server; if request.tick_rate > 0.0 { server .tick_rate_manager .set_tick_rate(server, request.tick_rate); } server.tick_rate_manager.set_frozen(server, request.frozen); Some(()) } pub fn ffi_native_bridge_shutdown_server_impl(request: ShutdownServerRequest) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; let server = ctx.plugin_context.server.clone(); ctx.runtime.spawn(async move { if request.save { let _ = server.save_all().await; } server.shutdown().await; }); Some(()) } pub fn ffi_native_bridge_broadcast_message_impl(request: BroadcastMessageRequest) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; let server = ctx.plugin_context.server.clone(); let text = TextComponent::text(request.message); let sender = TextComponent::text("Server"); server.broadcast_message(&text, &sender, 0, None); Some(()) } pub fn ffi_native_bridge_get_whitelist_impl( _request: EmptyRequest, ) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let mut players = Vec::new(); if let Ok(config) = ctx.plugin_context.server.data.whitelist_config.try_read() { for entry in &config.whitelist { players.push(WhitelistEntryProto { uuid: Some(ProtoUuid { value: entry.uuid.to_string(), }), name: entry.name.clone(), }); } } Some(GetWhitelistResponse { players }) } pub fn ffi_native_bridge_set_whitelist_player_impl( request: SetWhitelistPlayerRequest, ) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; let uuid_str = &request.uuid.as_ref()?.value; let target_uuid = uuid::Uuid::parse_str(uuid_str).ok()?; let target_name = request.name; let server = ctx.plugin_context.server.clone(); let mut config = server .data .whitelist_config .write() .unwrap_or_else(std::sync::PoisonError::into_inner); if request.whitelisted { if !config.whitelist.iter().any(|e| e.uuid == target_uuid) { config .whitelist .push(pumpkin_config::whitelist::WhitelistEntry { uuid: target_uuid, name: target_name, }); } } else { config.whitelist.retain(|e| e.uuid != target_uuid); } Some(()) } pub fn ffi_native_bridge_get_operators_impl( _request: EmptyRequest, ) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let mut operators = Vec::new(); if let Ok(config) = ctx.plugin_context.server.data.operator_config.try_read() { for op in &config.ops { operators.push(OperatorEntryProto { uuid: Some(ProtoUuid { value: op.uuid.to_string(), }), name: op.name.clone(), level: op.level as i32, bypasses_player_limit: op.bypasses_player_limit, }); } } Some(GetOperatorsResponse { operators }) } pub fn ffi_native_bridge_set_operator_impl(request: SetOperatorRequest) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; let uuid_str = &request.uuid.as_ref()?.value; let target_uuid = uuid::Uuid::parse_str(uuid_str).ok()?; let target_name = request.name; let level = match request.level { 1 => pumpkin_util::permission::PermissionLvl::One, 2 => pumpkin_util::permission::PermissionLvl::Two, 3 => pumpkin_util::permission::PermissionLvl::Three, _ => pumpkin_util::permission::PermissionLvl::Four, }; let server = ctx.plugin_context.server.clone(); let mut config = server .data .operator_config .write() .unwrap_or_else(std::sync::PoisonError::into_inner); if request.is_op { if let Some(op) = config.ops.iter_mut().find(|e| e.uuid == target_uuid) { op.level = level; } else { config.ops.push(pumpkin_config::op::Op { uuid: target_uuid, name: target_name, level, bypasses_player_limit: false, }); } } else { config.ops.retain(|e| e.uuid != target_uuid); } Some(()) } pub fn ffi_native_bridge_get_ban_list_impl( request: GetBanListRequest, ) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let mut entries = Vec::new(); if request.ban_type == "IP" { if let Ok(list) = ctx.plugin_context.server.data.banned_ip_list.try_read() { for entry in &list.banned_ips { let created = entry.created.unix_timestamp(); let expires = entry .expires .map_or(0, time::OffsetDateTime::unix_timestamp); entries.push(BanEntryProto { target: entry.ip.to_string(), source: entry.source.clone(), created, expires, reason: entry.reason.clone(), }); } } } else if let Ok(list) = ctx.plugin_context.server.data.banned_player_list.try_read() { for entry in &list.banned_players { let created = entry.created.unix_timestamp(); let expires = entry .expires .map_or(0, time::OffsetDateTime::unix_timestamp); entries.push(BanEntryProto { target: entry.name.clone(), source: entry.source.clone(), created, expires, reason: entry.reason.clone(), }); } } Some(GetBanListResponse { entries }) } pub fn ffi_native_bridge_set_ban_entry_impl(request: SetBanEntryRequest) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; let server = ctx.plugin_context.server.clone(); if let Some(entry) = request.entry { if request.ban_type == "IP" { if let Ok(ip) = entry.target.parse::() { let mut list = server .data .banned_ip_list .write() .unwrap_or_else(std::sync::PoisonError::into_inner); if request.remove { list.banned_ips.retain(|e| e.ip != ip); } else { list.banned_ips.retain(|e| e.ip != ip); let expires = if entry.expires > 0 { time::OffsetDateTime::from_unix_timestamp(entry.expires).ok() } else { None }; list.banned_ips .push(pumpkin::data::banlist_serializer::BannedIpEntry { ip, created: time::OffsetDateTime::now_utc(), source: entry.source, expires, reason: entry.reason, }); } } } else { let mut list = server .data .banned_player_list .write() .unwrap_or_else(std::sync::PoisonError::into_inner); let target_name = entry.target.clone(); if request.remove { list.banned_players .retain(|e| !e.name.eq_ignore_ascii_case(&target_name)); } else { list.banned_players .retain(|e| !e.name.eq_ignore_ascii_case(&target_name)); let expires = if entry.expires > 0 { time::OffsetDateTime::from_unix_timestamp(entry.expires).ok() } else { None }; list.banned_players .push(pumpkin::data::banlist_serializer::BannedPlayerEntry { uuid: uuid::Uuid::nil(), name: target_name, created: time::OffsetDateTime::now_utc(), source: entry.source, expires, reason: entry.reason, }); } } } Some(()) } pub fn ffi_native_bridge_create_world_impl( request: CreateWorldRequest, ) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let server = ctx.plugin_context.server.clone(); let name = request.name; let dim = match request.dimension.as_str() { "minecraft:the_nether" => pumpkin_data::dimension::Dimension::THE_NETHER, "minecraft:the_end" => pumpkin_data::dimension::Dimension::THE_END, _ => pumpkin_data::dimension::Dimension::OVERWORLD, }; let world_name = name.clone(); let (tx, rx) = tokio::sync::oneshot::channel(); ctx.runtime.spawn(async move { let world = server.create_world(world_name, dim); let _ = tx.send(world.uuid); }); let world_uuid = rx.blocking_recv().ok()?; Some(CreateWorldResponse { world_uuid: Some(ProtoUuid { value: world_uuid.to_string(), }), name, success: true, }) } pub fn ffi_native_bridge_unload_world_impl( request: UnloadWorldRequest, ) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let server = ctx.plugin_context.server.clone(); let name = request.world_name; let (tx, rx) = tokio::sync::oneshot::channel(); ctx.runtime.spawn(async move { let res = server.unload_world(&name).await; let _ = tx.send(res.is_ok()); }); let success = rx.blocking_recv().unwrap_or(false); Some(UnloadWorldResponse { success }) } pub fn ffi_native_bridge_save_all_worlds_impl(_request: EmptyRequest) -> Option<()> { let ctx = CALLBACK_CONTEXT.get()?; let server = ctx.plugin_context.server.clone(); ctx.runtime.spawn(async move { let _ = server.save_all().await; }); Some(()) }