add weather_zone command

This commit is contained in:
IsseW 2022-06-02 22:51:17 +02:00
parent cb969e5b87
commit 27ec6d7469
4 changed files with 160 additions and 21 deletions

View File

@ -125,6 +125,13 @@ lazy_static! {
.map(|s| s.to_string())
.collect();
static ref WEATHERS: Vec<String> = vec![
"clear", "cloudy", "rain", "wind", "storm"
]
.iter()
.map(|s| s.to_string())
.collect();
pub static ref BUFF_PARSER: HashMap<String, BuffKind> = {
let string_from_buff = |kind| match kind {
BuffKind::Burning => "burning",
@ -297,6 +304,7 @@ pub enum ServerChatCommand {
Location,
CreateLocation,
DeleteLocation,
WeatherZone,
}
impl ServerChatCommand {
@ -686,6 +694,15 @@ impl ServerChatCommand {
"Delete a location",
Some(Moderator),
),
ServerChatCommand::WeatherZone => cmd(
vec![
Enum("weather kind", WEATHERS.clone(), Required),
Float("radius", 500.0, Optional),
Float("time", 300.0, Optional),
],
"Create a weather zone",
Some(Admin),
),
}
}
@ -763,6 +780,7 @@ impl ServerChatCommand {
ServerChatCommand::Location => "location",
ServerChatCommand::CreateLocation => "create_location",
ServerChatCommand::DeleteLocation => "delete_location",
ServerChatCommand::WeatherZone => "weather_zone",
}
}

View File

@ -10,6 +10,7 @@ use crate::{
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
},
sys::terrain::NpcData,
weather::WeatherSim,
wiring,
wiring::OutputFormula,
Server, Settings, SpawnPoint, StateExt,
@ -44,7 +45,7 @@ use common::{
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
uid::{Uid, UidAllocator},
vol::{ReadVol, RectVolSize},
Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
};
use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
@ -190,6 +191,7 @@ fn do_command(
ServerChatCommand::Location => handle_location,
ServerChatCommand::CreateLocation => handle_create_location,
ServerChatCommand::DeleteLocation => handle_delete_location,
ServerChatCommand::WeatherZone => handle_weather_zone,
};
handler(server, client, target, args, cmd)
@ -3595,3 +3597,72 @@ fn handle_delete_location(
Err(action.help_string())
}
}
fn handle_weather_zone(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: Vec<String>,
action: &ServerChatCommand,
) -> CmdResult<()> {
if let (Some(name), radius, time) = parse_cmd_args!(args, String, f32, f32) {
let radius = radius.map(|r| r / weather::CELL_SIZE as f32).unwrap_or(1.0);
let time = time.unwrap_or(100.0);
let mut add_zone = |weather: weather::Weather| {
if let Ok(pos) = position(server, client, "player") {
let pos = pos.0.xy() / weather::CELL_SIZE as f32;
server
.state
.ecs_mut()
.write_resource::<WeatherSim>()
.add_zone(weather, pos, radius, time);
}
};
match name.as_str() {
"clear" => {
add_zone(weather::Weather {
cloud: 0.0,
rain: 0.0,
wind: Vec2::zero(),
});
Ok(())
},
"cloudy" => {
add_zone(weather::Weather {
cloud: 0.4,
rain: 0.0,
wind: Vec2::zero(),
});
Ok(())
},
"rain" => {
add_zone(weather::Weather {
cloud: 0.1,
rain: 0.15,
wind: Vec2::new(1.0, -1.0),
});
Ok(())
},
"wind" => {
add_zone(weather::Weather {
cloud: 0.0,
rain: 0.0,
wind: Vec2::new(10.0, 10.0),
});
Ok(())
},
"storm" => {
add_zone(weather::Weather {
cloud: 0.3,
rain: 0.3,
wind: Vec2::new(15.0,20.0),
});
Ok(())
},
_ => Err("Valid values are 'clear', 'rain', 'wind', 'storm'".to_string()),
}
} else {
Err(action.help_string())
}
}

View File

@ -10,6 +10,11 @@ mod sim;
mod sync;
mod tick;
pub use sim::WeatherSim;
/// How often the weather is updated, in seconds
const WEATHER_DT: f32 = 5.0;
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch::<tick::Sys>(dispatch_builder, &[]);
dispatch::<sync::Sys>(dispatch_builder, &[&tick::Sys::sys_name()]);
@ -17,12 +22,9 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
pub fn init(state: &mut State, world: &world::World) {
let weather_size = world.sim().get_size() / CHUNKS_PER_CELL;
let sim = sim::WeatherSim::new(weather_size, world);
let sim = WeatherSim::new(weather_size, world);
state.ecs_mut().insert(sim);
/// How often the weather is updated, in seconds
const WEATHER_DT: f32 = 5.0;
// NOTE: If weather computations get too heavy, this should not block the main
// thread.
state

View File

@ -1,19 +1,58 @@
use common::{
grid::Grid,
resources::TimeOfDay,
weather::{WeatherGrid, CELL_SIZE},
weather::{Weather, WeatherGrid, CELL_SIZE},
};
use noise::{NoiseFn, SuperSimplex, Turbulence};
use vek::*;
use world::World;
use crate::weather::WEATHER_DT;
fn cell_to_wpos(p: Vec2<i32>) -> Vec2<i32> { p * CELL_SIZE as i32 }
#[derive(Clone)]
struct WeatherZone {
weather: Weather,
/// Time, in seconds this zone lives.
time_to_live: f32,
}
pub struct WeatherSim {
size: Vec2<u32>,
zones: Grid<Option<WeatherZone>>,
}
impl WeatherSim {
pub fn new(size: Vec2<u32>, _world: &World) -> Self { Self { size } }
pub fn new(size: Vec2<u32>, _world: &World) -> Self {
Self {
size,
zones: Grid::new(size.as_(), None),
}
}
/// Adds a weather zone as a circle at a position, with a given radius. Both
/// of which should be in weather cell units
pub fn add_zone(&mut self, weather: Weather, pos: Vec2<f32>, radius: f32, time: f32) {
let min: Vec2<i32> = (pos - radius).as_::<i32>().map(|e| e.max(0));
let max: Vec2<i32> = (pos + radius)
.ceil()
.as_::<i32>()
.map2(self.size.as_::<i32>(), |a, b| a.min(b));
for y in min.y..max.y {
for x in min.x..max.x {
let ipos = Vec2::new(x, y);
let p = ipos.as_::<f32>();
if p.distance_squared(pos) < radius.powi(2) {
self.zones[ipos] = Some(WeatherZone {
weather,
time_to_live: time,
});
}
}
}
}
// Time step is cell size / maximum wind speed
pub fn tick(&mut self, time_of_day: &TimeOfDay, out: &mut WeatherGrid) {
@ -30,24 +69,33 @@ impl WeatherSim {
let rain_nz = SuperSimplex::new();
for (point, cell) in out.iter_mut() {
let wpos = cell_to_wpos(point);
if let Some(zone) = &mut self.zones[point] {
*cell = zone.weather;
zone.time_to_live -= WEATHER_DT;
if zone.time_to_live <= 0.0 {
self.zones[point] = None;
}
} else {
let wpos = cell_to_wpos(point);
let pos = wpos.as_::<f64>() + time as f64 * 0.1;
let pos = wpos.as_::<f64>() + time as f64 * 0.1;
let space_scale = 7_500.0;
let time_scale = 100_000.0;
let spos = (pos / space_scale).with_z(time as f64 / time_scale);
let space_scale = 7_500.0;
let time_scale = 100_000.0;
let spos = (pos / space_scale).with_z(time as f64 / time_scale);
let pressure = (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
let pressure =
(base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
const RAIN_CLOUD_THRESHOLD: f32 = 0.26;
cell.cloud = (1.0 - pressure) * 0.5;
cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0).powf(1.0);
cell.wind = Vec2::new(
rain_nz.get(spos.into_array()).powi(3) as f32,
rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
) * 200.0
* (1.0 - pressure);
const RAIN_CLOUD_THRESHOLD: f32 = 0.26;
cell.cloud = (1.0 - pressure) * 0.5;
cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0).powf(1.0);
cell.wind = Vec2::new(
rain_nz.get(spos.into_array()).powi(3) as f32,
rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
) * 200.0
* (1.0 - pressure);
}
}
}