diff --git a/common/src/cmd.rs b/common/src/cmd.rs index a80ba24f6c..06621bc5d7 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -312,6 +312,7 @@ pub enum ServerChatCommand { Tp, TpNpc, NpcInfo, + RtsimChunk, Unban, Version, Waypoint, @@ -691,6 +692,11 @@ impl ServerChatCommand { "Display information about an rtsim NPC", Some(Moderator), ), + ServerChatCommand::RtsimChunk => cmd( + vec![], + "Display information about the current chunk from rtsim", + Some(Moderator), + ), ServerChatCommand::Unban => cmd( vec![PlayerName(Required)], "Remove the ban for the given username", @@ -815,6 +821,7 @@ impl ServerChatCommand { ServerChatCommand::Tp => "tp", ServerChatCommand::TpNpc => "tp_npc", ServerChatCommand::NpcInfo => "npc_info", + ServerChatCommand::RtsimChunk => "rtsim_chunk", ServerChatCommand::Unban => "unban", ServerChatCommand::Version => "version", ServerChatCommand::Waypoint => "waypoint", diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index 8e34c97726..ce29f9630b 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -94,9 +94,25 @@ pub enum ChunkResource { #[serde(rename = "0")] Grass, #[serde(rename = "1")] - Flax, + Flower, #[serde(rename = "2")] - Cotton, + Fruit, + #[serde(rename = "3")] + Vegetable, + #[serde(rename = "4")] + Mushroom, + #[serde(rename = "5")] + Loot, // Chests, boxes, potions, etc. + #[serde(rename = "6")] + Plant, // Flax, cotton, wheat, corn, etc. + #[serde(rename = "7")] + Stone, + #[serde(rename = "8")] + Wood, // Twigs, logs, bamboo, etc. + #[serde(rename = "9")] + Gem, // Amethyst, diamond, etc. + #[serde(rename = "a")] + Ore, // Iron, copper, etc. } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 614b424bb6..ba8a8379d1 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -200,6 +200,37 @@ impl Block { #[inline] pub fn get_rtsim_resource(&self) -> Option { match self.get_sprite()? { + SpriteKind::Stones => Some(rtsim::ChunkResource::Stone), + SpriteKind::Twigs + | SpriteKind::Wood + | SpriteKind::Bamboo + | SpriteKind::Hardwood + | SpriteKind::Ironwood + | SpriteKind::Frostwood + | SpriteKind::Eldwood => Some(rtsim::ChunkResource::Wood), + SpriteKind::Amethyst + | SpriteKind::Ruby + | SpriteKind::Sapphire + | SpriteKind::Emerald + | SpriteKind::Topaz + | SpriteKind::Diamond + | SpriteKind::AmethystSmall + | SpriteKind::TopazSmall + | SpriteKind::DiamondSmall + | SpriteKind::RubySmall + | SpriteKind::EmeraldSmall + | SpriteKind::SapphireSmall + | SpriteKind::CrystalHigh + | SpriteKind::CrystalLow => Some(rtsim::ChunkResource::Gem), + SpriteKind::Bloodstone + | SpriteKind::Coal + | SpriteKind::Cobalt + | SpriteKind::Copper + | SpriteKind::Iron + | SpriteKind::Tin + | SpriteKind::Silver + | SpriteKind::Gold => Some(rtsim::ChunkResource::Ore), + SpriteKind::LongGrass | SpriteKind::MediumGrass | SpriteKind::ShortGrass @@ -209,9 +240,48 @@ impl Block { | SpriteKind::SavannaGrass | SpriteKind::TallSavannaGrass | SpriteKind::RedSavannaGrass - | SpriteKind::JungleRedGrass => Some(rtsim::ChunkResource::Grass), - SpriteKind::WildFlax => Some(rtsim::ChunkResource::Flax), - SpriteKind::Cotton => Some(rtsim::ChunkResource::Cotton), + | SpriteKind::JungleRedGrass + | SpriteKind::Fern => Some(rtsim::ChunkResource::Grass), + SpriteKind::BlueFlower + | SpriteKind::PinkFlower + | SpriteKind::PurpleFlower + | SpriteKind::RedFlower + | SpriteKind::WhiteFlower + | SpriteKind::YellowFlower + | SpriteKind::Sunflower + | SpriteKind::Moonbell + | SpriteKind::Pyrebloom => Some(rtsim::ChunkResource::Flower), + SpriteKind::Reed + | SpriteKind::Flax + | SpriteKind::WildFlax + | SpriteKind::Cotton + | SpriteKind::Corn + | SpriteKind::WheatYellow + | SpriteKind::WheatGreen => Some(rtsim::ChunkResource::Plant), + SpriteKind::Apple + | SpriteKind::Pumpkin + | SpriteKind::Beehive // TODO: Not a fruit, but kind of acts like one + | SpriteKind::Coconut => Some(rtsim::ChunkResource::Fruit), + SpriteKind::Cabbage + | SpriteKind::Carrot + | SpriteKind::Tomato + | SpriteKind::Radish + | SpriteKind::Turnip => Some(rtsim::ChunkResource::Vegetable), + SpriteKind::Mushroom + | SpriteKind::CaveMushroom + | SpriteKind::CeilingMushroom => Some(rtsim::ChunkResource::Mushroom), + + SpriteKind::Chest + | SpriteKind::ChestBuried + | SpriteKind::PotionMinor + | SpriteKind::DungeonChest0 + | SpriteKind::DungeonChest1 + | SpriteKind::DungeonChest2 + | SpriteKind::DungeonChest3 + | SpriteKind::DungeonChest4 + | SpriteKind::DungeonChest5 + | SpriteKind::CoralChest + | SpriteKind::Crate => Some(rtsim::ChunkResource::Loot), _ => None, } } diff --git a/rtsim/src/lib.rs b/rtsim/src/lib.rs index cc73f3ec92..1ae1622b8d 100644 --- a/rtsim/src/lib.rs +++ b/rtsim/src/lib.rs @@ -62,6 +62,7 @@ impl RtState { fn start_default_rules(&mut self) { info!("Starting default rtsim rules..."); self.start_rule::(); + self.start_rule::(); self.start_rule::(); self.start_rule::(); } diff --git a/rtsim/src/rule.rs b/rtsim/src/rule.rs index 5444d0a2c7..a58dd20493 100644 --- a/rtsim/src/rule.rs +++ b/rtsim/src/rule.rs @@ -1,4 +1,5 @@ pub mod npc_ai; +pub mod replenish_resources; pub mod setup; pub mod simulate_npcs; diff --git a/rtsim/src/rule/replenish_resources.rs b/rtsim/src/rule/replenish_resources.rs new file mode 100644 index 0000000000..e1838f8883 --- /dev/null +++ b/rtsim/src/rule/replenish_resources.rs @@ -0,0 +1,41 @@ +use crate::{event::OnTick, RtState, Rule, RuleError}; +use common::{terrain::TerrainChunkSize, vol::RectVolSize}; +use rand::prelude::*; +use tracing::info; +use vek::*; + +pub struct ReplenishResources; + +/// Take 1 hour to replenish resources entirely. Makes farming unviable, but +/// probably still poorly balanced. +// TODO: Different rates for different resources? +// TODO: Non-renewable resources? +pub const REPLENISH_TIME: f32 = 60.0 * 60.0; +/// How many chunks should be replenished per tick? +pub const REPLENISH_PER_TICK: usize = 100000; + +impl Rule for ReplenishResources { + fn start(rtstate: &mut RtState) -> Result { + rtstate.bind::(|ctx| { + let world_size = ctx.world.sim().get_size(); + let mut data = ctx.state.data_mut(); + + // How much should be replenished for each chosen chunk to hit our target + // replenishment rate? + let replenish_amount = world_size.product() as f32 * ctx.event.dt + / REPLENISH_TIME + / REPLENISH_PER_TICK as f32; + for _ in 0..REPLENISH_PER_TICK { + let key = world_size.map(|e| thread_rng().gen_range(0..e as i32)); + + let mut res = data.nature.get_chunk_resources(key); + for (_, res) in &mut res { + *res = (*res + replenish_amount).clamp(0.0, 1.0); + } + data.nature.set_chunk_resources(key, res); + } + }); + + Ok(Self) + } +} diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 6f7cf7b884..e724894f88 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -186,6 +186,7 @@ fn do_command( ServerChatCommand::Tp => handle_tp, ServerChatCommand::TpNpc => handle_tp_npc, ServerChatCommand::NpcInfo => handle_npc_info, + ServerChatCommand::RtsimChunk => handle_rtsim_chunk, ServerChatCommand::Unban => handle_unban, ServerChatCommand::Version => handle_version, ServerChatCommand::Waypoint => handle_waypoint, @@ -1262,6 +1263,61 @@ fn handle_npc_info( } } +fn handle_rtsim_chunk( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: Vec, + action: &ServerChatCommand, +) -> CmdResult<()> { + use crate::rtsim2::{ChunkStates, RtSim}; + let pos = position(server, target, "target")?; + + let chunk_key = pos.0.xy().map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { + e as i32 / sz as i32 + }); + + let rtsim = server.state.ecs().read_resource::(); + let data = rtsim.state().data(); + + let chunk_states = rtsim.state().resource::(); + let chunk_state = match chunk_states.0.get(chunk_key) { + Some(Some(chunk_state)) => chunk_state, + Some(None) => return Err(format!("Chunk {}, {} not loaded", chunk_key.x, chunk_key.y)), + None => { + return Err(format!( + "Chunk {}, {} not within map bounds", + chunk_key.x, chunk_key.y + )); + }, + }; + + let mut info = String::new(); + let _ = writeln!( + &mut info, + "-- Chunk {}, {} Resources --", + chunk_key.x, chunk_key.y + ); + for (res, frac) in data.nature.get_chunk_resources(chunk_key) { + let total = chunk_state.max_res[res]; + let _ = writeln!( + &mut info, + "{:?}: {} / {} ({}%)", + res, + frac * total as f32, + total, + frac * 100.0 + ); + } + + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, info), + ); + + Ok(()) +} + fn handle_spawn( server: &mut Server, client: EcsEntity, diff --git a/server/src/rtsim2/mod.rs b/server/src/rtsim2/mod.rs index 8ee595bb8f..7d8ee64ce7 100644 --- a/server/src/rtsim2/mod.rs +++ b/server/src/rtsim2/mod.rs @@ -208,11 +208,11 @@ impl RtSim { pub fn state(&self) -> &RtState { &self.state } } -struct ChunkStates(pub Grid>); +pub struct ChunkStates(pub Grid>); -struct LoadedChunkState { +pub struct LoadedChunkState { // The maximum possible number of each resource in this chunk - max_res: EnumMap, + pub max_res: EnumMap, } pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { diff --git a/server/src/rtsim2/rule/deplete_resources.rs b/server/src/rtsim2/rule/deplete_resources.rs index 05bfe43789..810af3aaed 100644 --- a/server/src/rtsim2/rule/deplete_resources.rs +++ b/server/src/rtsim2/rule/deplete_resources.rs @@ -1,14 +1,11 @@ use crate::rtsim2::{event::OnBlockChange, ChunkStates}; use common::{terrain::TerrainChunk, vol::RectRasterableVol}; use rtsim2::{RtState, Rule, RuleError}; -use tracing::info; pub struct DepleteResources; impl Rule for DepleteResources { fn start(rtstate: &mut RtState) -> Result { - info!("Hello from the resource depletion rule!"); - rtstate.bind::(|ctx| { let key = ctx .event @@ -35,7 +32,7 @@ impl Rule for DepleteResources { / chunk_state.max_res[res] as f32; } } - println!("Chunk resources = {:?}", chunk_res); + //println!("Chunk resources = {:?}", chunk_res); ctx.state .data_mut() .nature diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index e59de70cc3..936c204f3f 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -1,4 +1,4 @@ -use crate::{column::ColumnSample, sim::SimChunk, Canvas, CONFIG}; +use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, Canvas, CONFIG}; use common::{ calendar::{Calendar, CalendarEvent}, terrain::{Block, BlockKind, SpriteKind}, @@ -13,7 +13,7 @@ pub fn close(x: f32, tgt: f32, falloff: f32) -> f32 { (1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.125) } -/// Returns a decimal value between 0 and 1. +/// Returns a decimal value between 0 and 1. /// The density is maximum at the middle of the highest and the lowest allowed /// altitudes, and zero otherwise. Quadratic curve. /// @@ -1071,7 +1071,8 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng, calendar: Optio }) .unwrap_or(density); if density > 0.0 - && rng.gen::() < density //RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) + // Now deterministic, chunk resources are tracked by rtsim + && /*rng.gen::() < density*/ RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) && matches!(&water_mode, Underwater | Floating) == underwater { Some((*kind, water_mode))