220 lines
7.1 KiB
Rust
220 lines
7.1 KiB
Rust
mod common;
|
|
|
|
use std::fs;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use common::bot::BotClient;
|
|
use patchbukkit::{
|
|
PatchBukkitPlugin, commands::SimpleCommandSender, directories::setup_directories,
|
|
java::jvm::commands::JvmCommand,
|
|
};
|
|
use pumpkin::{PumpkinServer, data::VanillaData, plugin::Context};
|
|
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration, TelemetryConfig};
|
|
use tokio::sync::oneshot;
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
async fn test_fake_bot_join_and_interaction() {
|
|
let _ = pumpkin::LOGGER_IMPL.set(None);
|
|
let _ = tracing_subscriber::fmt().try_init();
|
|
let temp_dir = tempfile::tempdir().expect("Failed to create tempdir");
|
|
let prev_dir = std::env::current_dir().expect("Failed to get current dir");
|
|
std::env::set_current_dir(temp_dir.path()).expect("Failed to set current dir");
|
|
|
|
let basic_config = BasicConfiguration {
|
|
default_level_name: "test_bot_world".to_string(),
|
|
..Default::default()
|
|
};
|
|
|
|
let mut advanced_config = AdvancedConfiguration::default();
|
|
advanced_config.networking.java.enabled = true;
|
|
advanced_config.networking.java.address = "127.0.0.1:0".parse().unwrap();
|
|
advanced_config.networking.java.online_mode = false;
|
|
advanced_config.networking.java.encryption = false;
|
|
advanced_config.networking.bedrock.enabled = false;
|
|
advanced_config.networking.rcon.enabled = false;
|
|
advanced_config.commands.use_console = false;
|
|
|
|
let vanilla_data = VanillaData::load();
|
|
let telemetry_config = TelemetryConfig::default();
|
|
let pumpkin_server = Arc::new(
|
|
PumpkinServer::new(
|
|
basic_config,
|
|
advanced_config,
|
|
telemetry_config,
|
|
vanilla_data,
|
|
)
|
|
.await,
|
|
);
|
|
|
|
let server_addr = pumpkin_server
|
|
.tcp_listener
|
|
.as_ref()
|
|
.expect("TCP listener should be bound")
|
|
.local_addr()
|
|
.expect("Should have local address");
|
|
|
|
println!("Pumpkin TCP server bound to ephemeral port: {server_addr}");
|
|
|
|
let metadata = pumpkin::plugin::PluginMetadata {
|
|
name: "patchbukkit".to_string(),
|
|
version: "0.1.0".to_string(),
|
|
authors: vec!["PumpkinMC".to_string()],
|
|
description: "PatchBukkit".to_string(),
|
|
dependencies: vec![],
|
|
permissions: vec![],
|
|
};
|
|
|
|
let handlers = Arc::new(arc_swap::ArcSwap::from_pointee(
|
|
std::collections::HashMap::new(),
|
|
));
|
|
let context = Arc::new(Context::new(
|
|
metadata,
|
|
pumpkin_server.server.clone(),
|
|
handlers,
|
|
Arc::clone(&pumpkin_server.server.plugin_manager),
|
|
Arc::clone(&pumpkin::LOGGER_IMPL),
|
|
));
|
|
|
|
// Setup directories
|
|
let dirs = setup_directories(&context, "patchbukkit-plugins")
|
|
.expect("Failed to setup PatchBukkit directories");
|
|
|
|
// Copy test plugins if present
|
|
let test_plugins_dir = if prev_dir.join("java").exists() {
|
|
prev_dir
|
|
.join("java")
|
|
.join("patchbukkit")
|
|
.join("build")
|
|
.join("test-plugins")
|
|
} else if let Some(parent) = prev_dir.parent() {
|
|
parent
|
|
.join("java")
|
|
.join("patchbukkit")
|
|
.join("build")
|
|
.join("test-plugins")
|
|
} else {
|
|
std::path::PathBuf::from("java/patchbukkit/build/test-plugins")
|
|
};
|
|
|
|
let protocollib_source = test_plugins_dir.join("ProtocolLib.jar");
|
|
if protocollib_source.exists() {
|
|
let _ = fs::copy(&protocollib_source, dirs.plugins.join("ProtocolLib.jar"));
|
|
}
|
|
|
|
let plugin = PatchBukkitPlugin::new();
|
|
patchbukkit::on_load_inner(&plugin, context.clone())
|
|
.await
|
|
.expect("Failed to on_load PatchBukkit");
|
|
|
|
// Wait for JVM initialization
|
|
for _ in 0..100 {
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
let (tx, rx) = oneshot::channel();
|
|
if plugin
|
|
.command_tx
|
|
.send(JvmCommand::TriggerCommand {
|
|
full_command: "version".to_string(),
|
|
command_sender: SimpleCommandSender::Console,
|
|
respond_to: tx,
|
|
})
|
|
.await
|
|
.is_ok()
|
|
&& let Ok(res) = rx.await
|
|
&& res.is_ok()
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Wait for ProtocolLib to finish initializing if present
|
|
if protocollib_source.exists() {
|
|
for _ in 0..150 {
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
let (tx, rx) = oneshot::channel();
|
|
if plugin
|
|
.command_tx
|
|
.send(JvmCommand::TriggerCommand {
|
|
full_command: "protocol".to_string(),
|
|
command_sender: SimpleCommandSender::Console,
|
|
respond_to: tx,
|
|
})
|
|
.await
|
|
.is_ok()
|
|
&& let Ok(res) = rx.await
|
|
&& res.is_ok()
|
|
{
|
|
println!("ProtocolLib command responded successfully!");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start Pumpkin server network accept loop in background
|
|
let server_run = pumpkin_server.clone();
|
|
let server_task = tokio::spawn(async move {
|
|
server_run.start().await;
|
|
});
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
|
|
// Connect fake bot
|
|
println!("Connecting fake bot 'TestBot_1' to {server_addr}...");
|
|
let bot = BotClient::connect(server_addr, "TestBot_1")
|
|
.await
|
|
.expect("Failed to connect bot");
|
|
|
|
// Await transition into Play state (completes Handshake -> Login -> Config -> Play)
|
|
println!("Waiting for bot to enter Play state...");
|
|
bot.wait_for_play(Duration::from_secs(30))
|
|
.await
|
|
.expect("Bot should enter Play state successfully");
|
|
println!("Bot successfully in Play state!");
|
|
|
|
// Verify player is registered on server
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
let players = pumpkin_server.server.get_all_players();
|
|
assert!(
|
|
!players.is_empty(),
|
|
"Server should have at least 1 player connected"
|
|
);
|
|
let player = players
|
|
.iter()
|
|
.find(|p| p.gameprofile.name == "TestBot_1")
|
|
.expect("TestBot_1 should be registered on the server");
|
|
assert_eq!(player.gameprofile.name, "TestBot_1");
|
|
|
|
// Send arm swing packet from bot
|
|
bot.swing_arm()
|
|
.await
|
|
.expect("Bot should be able to send SSwingArm");
|
|
|
|
// Send movement packet from bot
|
|
bot.move_to(0.0, 64.0, 0.0)
|
|
.await
|
|
.expect("Bot should be able to send SPlayerPosition");
|
|
|
|
// Send command packet from bot
|
|
bot.send_command("version")
|
|
.await
|
|
.expect("Bot should be able to send SChatCommand");
|
|
|
|
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
|
|
// Disconnect bot
|
|
bot.disconnect().await;
|
|
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
|
|
// Gracefully stop server
|
|
pumpkin::stop_server();
|
|
let _ = server_task.await;
|
|
|
|
// Gracefully unload PatchBukkit
|
|
let _ = patchbukkit::on_unload_inner(&plugin, context.clone()).await;
|
|
|
|
// Restore working directory
|
|
let _ = std::env::set_current_dir(prev_dir);
|
|
tokio::task::spawn_blocking(move || drop(plugin))
|
|
.await
|
|
.unwrap();
|
|
}
|