mirror of https://github.com/zyllian/classics.git synced 2025-05-12 06:20:55 -07:00

implement level rules feature, resolves #8

This commit is contained in:
zyl 2025-01-18 00:56:50 -08:00
parent ff09a06b16
commit 728fc156c8
Signed by: zyl
SSH key fingerprint: SHA256:uxxbSXbdroP/OnKBGnEDk5q7EKB2razvstC/KmzdXXs
5 changed files with 512 additions and 2 deletions

View file

@ -26,6 +26,7 @@ const CMD_SETLEVELSPAWN: &str = "setlevelspawn";
const CMD_WEATHER: &str = "weather";
const CMD_SAVE: &str = "save";
const CMD_TELEPORT: &str = "tp";
const CMD_LEVELRULE: &str = "levelrule";
const USERNAME_SELF: &str = "@s";
@ -44,6 +45,7 @@ pub const COMMANDS_LIST: &[&str] = &[
CMD_WEATHER,
CMD_SAVE,
CMD_TELEPORT,
CMD_LEVELRULE,
];
/// enum for possible commands
@ -92,6 +94,11 @@ pub enum Command<'m> {
username: &'m str,
mode: TeleportMode<'m>,
},
/// gets or sets a level rule for the current level
LevelRule {
rule: &'m str,
value: Option<&'m str>,
},
}
#[derive(Debug, Clone)]
@ -167,6 +174,12 @@ impl<'m> Command<'m> {
Self::Teleport { username, mode }
}
CMD_LEVELRULE => {
let rule = Self::next_string(&mut arguments)?;
let value = Self::next_string(&mut arguments).ok();
Self::LevelRule { rule, value }
}
_ => return Err(format!("Unknown command: {command_name}")),
})
}
@ -187,6 +200,7 @@ impl<'m> Command<'m> {
Self::Weather { .. } => CMD_WEATHER,
Self::Save => CMD_SAVE,
Self::Teleport { .. } => CMD_TELEPORT,
Self::LevelRule { .. } => CMD_LEVELRULE,
}
}
@ -257,6 +271,11 @@ impl<'m> Command<'m> {
c("(<username> or <x> <y> <z>"),
"&fTeleports to the given username or coordinates.".to_string(),
],
CMD_LEVELRULE => vec![
c("<rule> [value]"),
"&fGets or sets the given level rule. The special rule \"all\" will get all rules."
.to_string(),
],
_ => vec!["&eUnknown command!".to_string()],
}
}
@ -645,7 +664,37 @@ impl<'m> Command<'m> {
player.packets_to_send.push(packet);
}
} else {
messages.push(format!("&fUnknown username: {username}!"));
messages.push(format!("Unknown username: {username}!"));
}
}
Command::LevelRule { rule, value } => {
if rule == "all" {
// get all rules
if let Some(rules) = data.level.level_rules.get_all_rules_info() {
for (name, rule) in rules {
messages.push(format!("&f{name}: {rule}"));
}
} else {
messages.push("Unable to fetch level rules!".to_string());
}
} else if let Some(value) = value {
// set a rule
match data.level.level_rules.set_rule(rule, value) {
Ok(()) => {
messages.push(format!("&fUpdated rule {rule}"));
}
Err(err) => {
messages.push(err.to_string());
}
}
} else {
// get a rule
if let Some(info) = data.level.level_rules.get_rule(rule) {
messages.push(format!("&f{info}"));
} else {
messages.push(format!("Unknown rule: {rule}"));
}
}
}
}

View file

@ -1,9 +1,11 @@
use std::{
any::TypeId,
collections::{BTreeMap, BTreeSet},
io::{Read, Write},
path::Path,
};
use bevy_reflect::{PartialReflect, Struct};
use serde::{Deserialize, Serialize};
use crate::{
@ -33,6 +35,9 @@ pub struct Level {
pub blocks: Vec<u8>,
/// the level's weather
pub weather: WeatherType,
/// the level's level rules
#[serde(default)]
pub level_rules: LevelRules,
/// index of blocks which need to be updated in the next tick
pub awaiting_update: BTreeSet<usize>,
@ -55,6 +60,7 @@ impl Level {
z_size,
blocks: vec![0; x_size * y_size * z_size],
weather: WeatherType::Sunny,
level_rules: Default::default(),
awaiting_update: Default::default(),
updates: Default::default(),
save_now: false,
@ -202,3 +208,78 @@ impl From<u8> for WeatherType {
}
}
}
/// Struct for rules in the level.
#[derive(Debug, Clone, Serialize, Deserialize, bevy_reflect::Reflect)]
pub struct LevelRules {
/// whether fluids should spread in the level
pub fluid_spread: bool,
}
impl LevelRules {
/// Gets information about all level rules.
pub fn get_all_rules_info(&self) -> Option<BTreeMap<String, String>> {
let info = self.get_represented_struct_info()?;
let mut rules = BTreeMap::new();
for name in info.field_names() {
rules.insert(name.to_string(), self.get_rule(name)?);
}
Some(rules)
}
/// Gets information about a single level rule.
pub fn get_rule(&self, name: &str) -> Option<String> {
let info = self.get_represented_struct_info()?;
Some(format!(
"{:?} ({})",
self.field(name)?,
info.field(name)?.type_path_table().ident()?
))
}
/// Sets a rule to the given value if possible.
pub fn set_rule(&mut self, name: &str, value: &str) -> Result<(), String> {
let bool_type_id = TypeId::of::<bool>();
let f64_type_id = TypeId::of::<f64>();
let string_type_id = TypeId::of::<String>();
fn parse_and_apply<T>(value: &str, field_mut: &mut dyn PartialReflect) -> Result<(), String>
where
T: std::str::FromStr + PartialReflect,
{
let value = value
.parse::<T>()
.map_err(|_| "Failed to parse value".to_string())?;
field_mut.apply(value.as_partial_reflect());
Ok(())
}
let info = self
.get_represented_struct_info()
.ok_or_else(|| "Failed to get field info".to_string())?;
let field = info
.field(name)
.ok_or_else(|| format!("Unknown field: {name}"))?;
let field_mut = self
.field_mut(name)
.ok_or_else(|| format!("Unknown field: {name}"))?;
let id = field.type_id();
if id == bool_type_id {
parse_and_apply::<bool>(value, field_mut)?;
} else if id == f64_type_id {
parse_and_apply::<f64>(value, field_mut)?;
} else if id == string_type_id {
parse_and_apply::<String>(value, field_mut)?;
} else {
return Err(format!("Field has unknown type: {}", field.type_path()));
};
Ok(())
}
}
impl Default for LevelRules {
fn default() -> Self {
Self { fluid_spread: true }
}
}

View file

@ -219,6 +219,9 @@ fn tick(data: &mut ServerData, tick: usize) {
stationary,
ticks_to_spread,
} => {
if !level.level_rules.fluid_spread {
continue;
}
if tick % ticks_to_spread == 0 {
let update = BlockUpdate {
index,
@ -266,6 +269,9 @@ fn tick(data: &mut ServerData, tick: usize) {
}
}
BlockType::FluidStationary { moving } => {
if !level.level_rules.fluid_spread {
continue;
}
let mut needs_update = false;
for (nx, ny, nz) in neighbors_minus_up(level, x, y, z) {
if matches!(