Initial commit

master
Drake 1 year ago
commit 05a21c87f3

3
.gitignore vendored

@ -0,0 +1,3 @@
/target
/world
/assets

2182
Cargo.lock generated

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…
Cancel
Save