Initial commit
commit
05a21c87f3
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
/world
|
||||||
|
/assets
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "minecraft"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tracing = "0.1.37"
|
||||||
|
tracing-subscriber = "0.3.16"
|
||||||
|
valence = { git = "https://github.com/valence-rs/valence" }
|
||||||
|
valence_protocol = { git = "https://github.com/valence-rs/valence" }
|
||||||
|
valence_anvil = { git = "https://github.com/valence-rs/valence" }
|
||||||
|
flume = "0.10.14"
|
||||||
|
anyhow = "1.0.70"
|
||||||
|
once_cell = "1.17.1"
|
@ -0,0 +1,38 @@
|
|||||||
|
use valence::prelude::{event::ChatMessage, *};
|
||||||
|
|
||||||
|
pub fn chat_handler(
|
||||||
|
mut clients: Query<(&mut Client, &Username)>,
|
||||||
|
mut messages: EventReader<ChatMessage>,
|
||||||
|
) {
|
||||||
|
for message in messages.iter() {
|
||||||
|
let username = match clients.get_component::<Username>(message.client) {
|
||||||
|
Ok(username) => username.to_string(),
|
||||||
|
_ => "Unknown".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_message = format!("[{}] ", username).not_bold().color(Color::GOLD)
|
||||||
|
+ message.message.to_string().not_bold().color(Color::WHITE);
|
||||||
|
|
||||||
|
for (mut client, _) in &mut clients {
|
||||||
|
client.send_message(new_message.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_joined(mut clients: Query<(&mut Client, &Username)>) {
|
||||||
|
let mut messages: Vec<Text> = vec![];
|
||||||
|
|
||||||
|
for (mut client, username) in &mut clients {
|
||||||
|
if client.is_added() {
|
||||||
|
messages.push(
|
||||||
|
format!("{} joined the game", username)
|
||||||
|
.italic()
|
||||||
|
.color(Color::GRAY),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in messages.iter() {
|
||||||
|
client.send_message(message.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
use valence::bevy_ecs;
|
||||||
|
use valence::prelude::{event::PlayerInteractBlock, *};
|
||||||
|
use valence_protocol::block::BlockEntityKind;
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct Chest {
|
||||||
|
pub inventory: Inventory,
|
||||||
|
pub block_pos: BlockPos,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Pos(BlockPos);
|
||||||
|
|
||||||
|
pub fn open_chests(
|
||||||
|
mut commands: Commands,
|
||||||
|
chests: Query<(Entity, &Inventory, &Pos), (With<Inventory>, With<Pos>)>,
|
||||||
|
instances: Query<&Instance>,
|
||||||
|
mut events: EventReader<PlayerInteractBlock>,
|
||||||
|
) {
|
||||||
|
let instance = instances.single();
|
||||||
|
for event in events.iter() {
|
||||||
|
if instance
|
||||||
|
.block(event.position)
|
||||||
|
.unwrap()
|
||||||
|
.state()
|
||||||
|
.block_entity_kind()
|
||||||
|
.or(None)
|
||||||
|
== Some(BlockEntityKind::Chest)
|
||||||
|
{
|
||||||
|
let chest = chests
|
||||||
|
.iter()
|
||||||
|
.filter(|chest| event.position == (*chest.2).0)
|
||||||
|
.collect::<Vec<(Entity, &Inventory, &Pos)>>();
|
||||||
|
|
||||||
|
let entity;
|
||||||
|
|
||||||
|
if chest.len() < 1 {
|
||||||
|
tracing::info!(
|
||||||
|
"Generating new chest at ({}, {}, {})",
|
||||||
|
event.position.x,
|
||||||
|
event.position.y,
|
||||||
|
event.position.z
|
||||||
|
);
|
||||||
|
let mut inventory = Inventory::new(InventoryKind::Generic9x3);
|
||||||
|
inventory.set_slot(0, ItemStack::new(ItemKind::IronSword, 1, None));
|
||||||
|
|
||||||
|
let chest_commands = commands.spawn((inventory, Pos(event.position)));
|
||||||
|
|
||||||
|
entity = Some(chest_commands.id());
|
||||||
|
} else {
|
||||||
|
entity = Some(chest[0].0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let open_inventory = OpenInventory::new(entity.unwrap());
|
||||||
|
commands.entity(event.client).insert(open_inventory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
use valence::prelude::{Client, GameMode};
|
||||||
|
|
||||||
|
use super::Command;
|
||||||
|
|
||||||
|
pub fn run(command: Command, client: &mut Client, gamemode: &mut GameMode) {
|
||||||
|
if let Some(args) = command.args {
|
||||||
|
match args[0].as_str() {
|
||||||
|
"survival" | "s" | "0" => *gamemode = GameMode::Survival,
|
||||||
|
"creative" | "c" | "1" => *gamemode = GameMode::Creative,
|
||||||
|
"adventure" | "a" | "2" => *gamemode = GameMode::Adventure,
|
||||||
|
"spectator" | "sp" | "3" => *gamemode = GameMode::Spectator,
|
||||||
|
_ => client.send_message(format!("Gamemode {} cannot be switched to", args[0])),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.send_message(format!(
|
||||||
|
"Your current gamemode is {}",
|
||||||
|
match *gamemode {
|
||||||
|
GameMode::Survival => "Survival",
|
||||||
|
GameMode::Creative => "Creative",
|
||||||
|
GameMode::Adventure => "Adventure",
|
||||||
|
GameMode::Spectator => "Spectator",
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
mod gamemode;
|
||||||
|
|
||||||
|
use valence::prelude::{
|
||||||
|
event::{CommandExecution, RequestCommandCompletions},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Command {
|
||||||
|
pub name: String,
|
||||||
|
pub args: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn parse(command: String) -> Self {
|
||||||
|
let command = command
|
||||||
|
.split(" ")
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let name = command[0].clone();
|
||||||
|
let args = if command.len() > 1 {
|
||||||
|
Some(command.as_slice()[1..].to_vec())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { name, args }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command_executor(
|
||||||
|
mut clients: Query<(&mut Client, &mut GameMode)>,
|
||||||
|
mut events: EventReader<CommandExecution>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let cmd = Command::parse(event.command.to_string());
|
||||||
|
let (mut client, mut gamemode) = clients.get_mut(event.client).unwrap();
|
||||||
|
|
||||||
|
match cmd.name.as_str() {
|
||||||
|
"gamemode" => gamemode::run(cmd, &mut client, &mut gamemode),
|
||||||
|
_ => client.send_message(format!("Command {} not found!", cmd.name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command_suggestor(
|
||||||
|
mut clients: Query<&mut Client>,
|
||||||
|
mut events: EventReader<RequestCommandCompletions>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let _client = clients.get_mut(event.client).unwrap();
|
||||||
|
println!("{:?}", event);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
mod chat;
|
||||||
|
mod chests;
|
||||||
|
mod commands;
|
||||||
|
mod player;
|
||||||
|
mod special;
|
||||||
|
mod world;
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use std::sync::atomic::AtomicI32;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use valence::client::default_event_handler;
|
||||||
|
use valence::entity::player::PlayerEntityBundle;
|
||||||
|
use valence::packet::WritePacket;
|
||||||
|
use valence::prelude::*;
|
||||||
|
|
||||||
|
use valence_protocol::packet::s2c::play::ChunkRenderDistanceCenterS2c;
|
||||||
|
use valence_protocol::var_int::VarInt;
|
||||||
|
use valence_protocol::Encode;
|
||||||
|
|
||||||
|
const STATIC_SPAWN_POS: DVec3 = DVec3::new(-221.0, 64.0, 299.0);
|
||||||
|
static PLAYER_COUNT: AtomicI32 = AtomicI32::new(0);
|
||||||
|
|
||||||
|
struct MyCallbacks;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AsyncCallbacks for MyCallbacks {
|
||||||
|
async fn server_list_ping(
|
||||||
|
&self,
|
||||||
|
_shared: &SharedServer,
|
||||||
|
_remote_addr: SocketAddr,
|
||||||
|
_protocol_version: i32,
|
||||||
|
) -> ServerListPing {
|
||||||
|
ServerListPing::Respond {
|
||||||
|
online_players: PLAYER_COUNT.load(Ordering::Relaxed),
|
||||||
|
max_players: 69420,
|
||||||
|
player_sample: vec![],
|
||||||
|
description: "Crazy! Insane! Et cetera!".into(),
|
||||||
|
favicon_png: include_bytes!("../assets/icon.png"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let server = ServerPlugin::new(MyCallbacks);
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.add_plugin(server)
|
||||||
|
.add_startup_system(world::setup)
|
||||||
|
.add_systems(
|
||||||
|
(
|
||||||
|
default_event_handler,
|
||||||
|
//special::tnt,
|
||||||
|
chat::chat_handler,
|
||||||
|
chests::open_chests,
|
||||||
|
commands::command_executor,
|
||||||
|
commands::command_suggestor,
|
||||||
|
player::fall_damage,
|
||||||
|
player::death,
|
||||||
|
)
|
||||||
|
.in_schedule(EventLoopSchedule),
|
||||||
|
)
|
||||||
|
.add_systems((
|
||||||
|
init_clients,
|
||||||
|
chat::player_joined,
|
||||||
|
despawn_disconnected_clients,
|
||||||
|
))
|
||||||
|
.add_systems(PlayerList::default_systems())
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_clients(
|
||||||
|
mut new_clients: Query<
|
||||||
|
(
|
||||||
|
&mut Position,
|
||||||
|
Entity,
|
||||||
|
&UniqueId,
|
||||||
|
&mut Client,
|
||||||
|
&mut GameMode,
|
||||||
|
&Username,
|
||||||
|
&mut HasRespawnScreen,
|
||||||
|
),
|
||||||
|
Added<Client>,
|
||||||
|
>,
|
||||||
|
instances: Query<Entity, With<Instance>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (_pos, entity, uuid, mut client, mut game_mode, username, mut has_respawn_screen) in
|
||||||
|
&mut new_clients
|
||||||
|
{
|
||||||
|
has_respawn_screen.0 = true;
|
||||||
|
*game_mode = GameMode::Creative;
|
||||||
|
commands.entity(entity).insert(PlayerEntityBundle {
|
||||||
|
location: Location(instances.single()),
|
||||||
|
position: Position(DVec3 {
|
||||||
|
x: -214.0,
|
||||||
|
y: 83.0,
|
||||||
|
z: 276.0,
|
||||||
|
}),
|
||||||
|
uuid: *uuid,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
client.write_packet(&ChunkRenderDistanceCenterS2c {
|
||||||
|
chunk_x: VarInt(-14),
|
||||||
|
chunk_z: VarInt(17),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
"Valence".encode(&mut buf).unwrap();
|
||||||
|
client.send_custom_payload(ident!("brand"), &buf);
|
||||||
|
PLAYER_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
tracing::info!("{username} joined the game");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn despawn_disconnected_clients(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut disconnected_clients: RemovedComponents<Client>,
|
||||||
|
) {
|
||||||
|
for entity in disconnected_clients.iter() {
|
||||||
|
if let Some(mut entity) = commands.get_entity(entity) {
|
||||||
|
entity.insert(Despawned);
|
||||||
|
}
|
||||||
|
PLAYER_COUNT.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
use std::{collections::HashMap, sync::Mutex};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use valence::{
|
||||||
|
entity::{axolotl::PlayingDead, living::Health, EntityId},
|
||||||
|
packet::WritePacket,
|
||||||
|
prelude::{event::PlayerMove, *},
|
||||||
|
};
|
||||||
|
use valence_protocol::{
|
||||||
|
item::FoodComponent,
|
||||||
|
packet::s2c::play::{DamageTiltS2c, EntityAnimationS2c, EntityDamageS2c, HealthUpdateS2c},
|
||||||
|
var_int::VarInt,
|
||||||
|
};
|
||||||
|
|
||||||
|
static PLAYER_HEIGHT_HISTORY: Lazy<Mutex<HashMap<i32, f64>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
pub fn fall_damage(
|
||||||
|
mut clients: Query<(&mut Client, &mut EntityId, &mut Health, &GameMode)>,
|
||||||
|
mut events: EventReader<PlayerMove>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let (mut client, id, mut health, gamemode) = clients.get_mut(event.client).unwrap();
|
||||||
|
|
||||||
|
if event.on_ground && *gamemode == GameMode::Survival {
|
||||||
|
let fall_distance = PLAYER_HEIGHT_HISTORY
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get(&id.get())
|
||||||
|
.unwrap_or(&-1.0)
|
||||||
|
- event.position.y;
|
||||||
|
|
||||||
|
if fall_distance > 3.0 {
|
||||||
|
health.0 -= (fall_distance - 3.0) as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAYER_HEIGHT_HISTORY
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(id.get(), event.position.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn death(mut healths: Query<(&mut Client, &mut Health), Changed<Health>>) {
|
||||||
|
for (mut client, health) in &mut healths {
|
||||||
|
dbg!(health.0);
|
||||||
|
if health.0 <= 0.0 {
|
||||||
|
tracing::debug!("Trying to kill player");
|
||||||
|
client.kill(None, "L bozo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
use valence::{
|
||||||
|
entity::tnt::TntEntityBundle,
|
||||||
|
prelude::{event::PlayerInteractBlock, *},
|
||||||
|
};
|
||||||
|
use valence_protocol::types::Hand;
|
||||||
|
|
||||||
|
/* pub fn tnt(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut clients: Query<(&mut Inventory, &GameMode, &PlayerInventoryState)>,
|
||||||
|
mut thingies: Query<Entity, With<Instance>>,
|
||||||
|
mut events: EventReader<PlayerInteractBlock>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let Ok((mut inventory, game_mode, inv_state)) = clients.get_mut(event.client) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if event.hand != Hand::Main {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let slot_id = inv_state.held_item_slot();
|
||||||
|
let Some(stack) = inventory.slot(slot_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(block_kind) = stack.item.to_block_kind() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if block_kind != BlockKind::Tnt {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if *game_mode == GameMode::Survival {
|
||||||
|
if stack.count() > 1 {
|
||||||
|
let count = stack.count();
|
||||||
|
inventory.set_slot_amount(slot_id, count - 1);
|
||||||
|
} else {
|
||||||
|
inventory.set_slot(slot_id, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let real_pos = event.position.get_in_direction(event.direction);
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Spawning TNT ({}, {}, {})!",
|
||||||
|
real_pos.x,
|
||||||
|
real_pos.y,
|
||||||
|
real_pos.z
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.spawn(TntEntityBundle {
|
||||||
|
location: Location(thingies.single()),
|
||||||
|
position: Position(DVec3 {
|
||||||
|
x: real_pos.x as f64 + 0.5,
|
||||||
|
y: real_pos.y as f64,
|
||||||
|
z: real_pos.z as f64 + 0.5,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} */
|
@ -0,0 +1,78 @@
|
|||||||
|
use valence::prelude::*;
|
||||||
|
use valence_anvil::{AnvilChunk, AnvilWorld};
|
||||||
|
|
||||||
|
pub fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
server: Res<Server>,
|
||||||
|
dimensions: Query<&DimensionType>,
|
||||||
|
biomes: Query<&Biome>,
|
||||||
|
) {
|
||||||
|
let mut anvil = AnvilWorld::new("world");
|
||||||
|
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||||
|
|
||||||
|
tracing::info!("Loading world...");
|
||||||
|
|
||||||
|
for z in -25..25 {
|
||||||
|
for x in -25..25 {
|
||||||
|
let Ok(Some(AnvilChunk {data, ..})) = anvil.read_chunk(x, z) else {
|
||||||
|
tracing::warn!("Failed to load chunk ({x}, {z}); could not read Anvil chunk");
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut chunk = Chunk::new(24);
|
||||||
|
|
||||||
|
let Ok(_) = valence_anvil::to_valence(&data, &mut chunk, 4, |_| BiomeId::default()) else {
|
||||||
|
tracing::warn!("Failed to load chunk ({x}, {z}); could not convert Anvil chunk to Valence chunk");
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
chunk.optimize();
|
||||||
|
|
||||||
|
instance.insert_chunk([x, z], chunk);
|
||||||
|
|
||||||
|
tracing::debug!("Successfully loaded chunk at ({x}, {z})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("Successfully loaded world!");
|
||||||
|
|
||||||
|
/* tracing::info!("Filling chests...");
|
||||||
|
|
||||||
|
for chunk in instance.chunks() {
|
||||||
|
for x in 0..16 {
|
||||||
|
for y in 0..256 {
|
||||||
|
for z in 0..16 {
|
||||||
|
let block = chunk.1.block(x, y, z);
|
||||||
|
if block.state() == BlockState::CHEST {
|
||||||
|
tracing::debug!(
|
||||||
|
"found chest: chunk ({},{}), offset ({}, {}, {}), calculated world pos ({}, {}, {})",
|
||||||
|
chunk.0.x,
|
||||||
|
chunk.0.z,
|
||||||
|
x as i32,
|
||||||
|
y as i32,
|
||||||
|
z as i32,
|
||||||
|
(chunk.0.x * 16) + x as i32,
|
||||||
|
y as i32 + 49,
|
||||||
|
(chunk.0.z * 16) + z as i32
|
||||||
|
);
|
||||||
|
let mut inventory = Inventory::new(InventoryKind::Generic9x3);
|
||||||
|
inventory.set_slot(0, ItemStack::new(ItemKind::IronSword, 1, None));
|
||||||
|
let chest = Chest {
|
||||||
|
inventory,
|
||||||
|
block_pos: BlockPos::new(
|
||||||
|
(chunk.0.x - 1) * 16 + x as i32,
|
||||||
|
y as i32 + 49,
|
||||||
|
(chunk.0.z + 1) * 16 + z as i32,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
commands.spawn(chest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("Filled all (found) chests!"); */
|
||||||
|
|
||||||
|
commands.spawn(instance);
|
||||||
|
}
|
Loading…
Reference in new issue