Initial commit

master
Tymon 1 year ago
commit 9102608b37

2
.gitignore vendored

@ -0,0 +1,2 @@
/target
example.config.toml

2195
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,16 @@
[package]
name = "spleef"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
once_cell = "1.17.1"
rand = "0.8.5"
serde = { version = "1.0.159", features = ["derive"] }
toml = "0.7.3"
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" }

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

@ -0,0 +1,2 @@
# Spleef
Spleef made with Valence. That's all it is.

@ -0,0 +1,9 @@
# TODO
- Implement waiting room system [✔️]
- Chat [✔️]
- Make the game automatically start
- Make spectator mode work properly
- Maybe make the floors use a random block each round?
- Leaderboards using a database, probably Surreal
- Maybe some kind of events that can randomly happen? Could be a cool mechanic that other spleef servers (probably) don't have
- Eventually add world support for nicer looking arenas

@ -0,0 +1,11 @@
no_auto_stop = true
spawn_y = 60
platforms = 2
platform_gap = 5
platform_size = 15
operators = [
"dc524fc6-bdba-4f55-9f7b-cefc824cc77e"
]

@ -0,0 +1,19 @@
use valence::prelude::{event::ChatMessage, *};
pub fn handle_messages(
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 message = format!("{}: {}", username, message.message);
for (mut client, _) in &mut clients {
client.send_message(message.clone());
}
}
}

@ -0,0 +1,33 @@
use valence::prelude::*;
use crate::{GameState, CONFIG};
use super::Command;
pub fn run(
command: Command,
client: &mut Client,
uuid: Uuid,
next_state: &mut ResMut<NextState<GameState>>,
state: &Res<State<GameState>>
) {
if CONFIG.operators.contains(&uuid) {
if let Some(args) = command.args {
match args[0].as_str() {
"start" => next_state.set(GameState::Started),
"stop" => next_state.set(GameState::Stopped),
_ => client.send_message("Invalid game state!"),
}
} else {
client.send_message(format!(
"The game is currently {}",
match state.0 {
GameState::Started => "started",
GameState::Stopped => "stopped",
}
));
}
} else {
client.send_message("You do not have the permission to execute this command.");
}
}

@ -0,0 +1,47 @@
mod game;
use valence::prelude::{event::CommandExecution, *};
use crate::GameState;
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>,
uuids: Query<&UniqueId, With<Client>>,
mut events: EventReader<CommandExecution>,
mut next_state: ResMut<NextState<GameState>>,
state: Res<State<GameState>>
) {
for event in events.iter() {
let cmd = Command::parse(event.command.to_string());
let mut client = clients.get_mut(event.client).unwrap();
let uuid = uuids.get(event.client).unwrap();
match cmd.name.as_str() {
"game" => game::run(cmd, &mut client, uuid.0, &mut next_state, &state),
_ => client.send_message(format!("Command {} not found!", cmd.name)),
}
}
}

@ -0,0 +1,25 @@
use std::fs;
use once_cell::sync::Lazy;
use serde::Deserialize;
use valence::uuid::Uuid;
#[derive(Deserialize)]
pub struct Config {
// USE ONLY FOR DEVELOPMENT PURPOSES
pub no_auto_stop: Option<bool>,
pub spawn_y: i32,
pub platforms: i32,
pub platform_gap: i32,
pub platform_size: i32,
pub operators: Vec<Uuid>,
}
pub static CONFIG: Lazy<Config> = Lazy::new(|| {
let config = fs::read_to_string("./config.toml").expect("Failed to read config.toml!");
toml::from_str(&config).unwrap()
});

@ -0,0 +1,94 @@
use rand::prelude::*;
use valence::prelude::*;
use crate::{player::Alive, GameState, CONFIG};
pub fn start_game(
mut clients: Query<(&mut Client, &mut Position, &mut GameMode, &mut Alive)>,
mut instances: Query<&mut Instance>,
) {
tracing::debug!("Starting game!");
let mut instance = instances.single_mut();
for (mut client, mut pos, mut gamemode, mut alive) in &mut clients {
*gamemode = GameMode::Survival;
let mut rng = rand::thread_rng();
let x = rng.gen_range(0..=CONFIG.platform_size);
let z = rng.gen_range(0..=CONFIG.platform_size);
pos.set([x.into(), CONFIG.spawn_y as f64 + 1.0, z.into()]);
alive.0 = true;
// TODO: add colors
client.set_title("Game started!", "Have fun!", None);
}
for platform in -CONFIG.platforms..CONFIG.platforms * CONFIG.platform_gap {
if platform % CONFIG.platform_gap == 0 {
for z in -CONFIG.platform_size..CONFIG.platform_size {
for x in -CONFIG.platform_size..CONFIG.platform_size {
instance.set_block([x, CONFIG.spawn_y - platform, z], BlockState::SNOW_BLOCK);
}
}
}
}
}
pub fn stop_game(
mut clients: Query<(
&mut Client,
&mut Position,
&mut GameMode,
&mut Alive,
&Username,
)>,
) {
tracing::debug!("Stopping game!");
let mut alive_players = vec![];
for (mut client, mut pos, mut gamemode, mut alive, username) in &mut clients {
if alive.0 {
alive_players.push(username);
}
*gamemode = GameMode::Spectator;
pos.set([0.0, CONFIG.spawn_y as f64 + 10.0, 0.0]);
alive.0 = false;
// TODO: add colors
client.set_title(
"Game has ended!",
format!(
"{} wins!",
// if statement to not panic if somehow no one was alive when the game ended
if alive_players.len() >= 1 {
// let's assume there's only one alive player
alive_players[0].0.clone()
} else {
"Somehow, no one".to_string()
}
),
None,
);
}
}
pub fn auto_stop(
clients: Query<&Alive, With<Client>>,
mut next_state: ResMut<NextState<GameState>>,
state: Res<State<GameState>>,
) {
if state.0 == GameState::Started {
let alive_clients = clients
.iter()
.filter(|alive| alive.0)
.collect::<Vec<&Alive>>();
if alive_clients.len() <= 1 {
next_state.set(GameState::Stopped);
}
}
}

@ -0,0 +1,48 @@
mod chat;
mod commands;
mod config;
mod game;
mod player;
mod world;
use config::CONFIG;
use once_cell::sync::Lazy;
use valence::bevy_ecs;
use valence::prelude::*;
#[derive(States, Default, Debug, Hash, PartialEq, Eq, Clone)]
pub enum GameState {
Started,
#[default]
Stopped,
}
fn main() {
Lazy::force(&CONFIG);
tracing_subscriber::fmt::init();
App::new()
.add_state::<GameState>()
.add_plugin(ServerPlugin::new(()))
.add_startup_system(world::setup)
.add_system(player::init_clients)
.add_systems(
(
default_event_handler,
player::break_block,
commands::command_executor,
chat::handle_messages,
)
.in_schedule(EventLoopSchedule),
)
.add_systems((
despawn_disconnected_clients,
player::death,
game::auto_stop.run_if(|| !CONFIG.no_auto_stop.unwrap_or(false)),
))
.add_system(game::start_game.in_schedule(OnEnter(GameState::Started)))
.add_system(game::stop_game.in_schedule(OnExit(GameState::Started)))
.add_systems(PlayerList::default_systems())
.run();
}

@ -0,0 +1,52 @@
use valence::bevy_ecs;
use valence::{
entity::player::PlayerEntityBundle,
prelude::{event::StartDigging, *},
};
use crate::{GameState, CONFIG};
#[derive(Component, Debug)]
pub struct Alive(pub bool);
pub fn init_clients(
mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added<Client>>,
instances: Query<Entity, With<Instance>>,
mut commands: Commands,
) {
for (entity, uuid, mut game_mode) in &mut clients {
*game_mode = GameMode::Spectator;
commands.entity(entity).insert((
Alive(false),
PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([0.0, CONFIG.spawn_y as f64 + 10.0, 0.0]),
uuid: *uuid,
..Default::default()
},
));
}
}
pub fn break_block(mut instances: Query<&mut Instance>, mut events: EventReader<StartDigging>) {
let mut instance = instances.single_mut();
for event in events.iter() {
instance.set_block(event.position, BlockState::AIR);
}
}
pub fn death(
mut clients: Query<(&mut Client, &mut Position, &mut GameMode, &mut Alive)>,
state: Res<State<GameState>>,
) {
for (mut client, mut pos, mut gamemode, mut alive) in &mut clients {
if pos.0.y <= 0.0 && state.0 == GameState::Started {
*gamemode = GameMode::Spectator;
pos.set([0.0, (CONFIG.spawn_y + 10).into(), 0.0]);
alive.0 = false;
client.send_message("You died!".italic().bold().color(Color::GRAY));
}
}
}

@ -0,0 +1,20 @@
use valence::prelude::*;
pub fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
instance.insert_chunk([x, z], Chunk::default());
}
}
commands.spawn(instance);
tracing::info!("Finished setting up world!");
}
Loading…
Cancel
Save