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