diff options
-rw-r--r-- | client/game.gd | 2 | ||||
-rw-r--r-- | client/game.tscn | 5 | ||||
-rw-r--r-- | client/map/auto_setup/environment_setup.gd | 3 | ||||
-rw-r--r-- | client/map/environment.gd | 10 | ||||
-rw-r--r-- | client/map/environment.tscn | 73 | ||||
-rw-r--r-- | client/multiplayer.gd | 3 | ||||
-rw-r--r-- | data/maps/debug.yaml | 1 | ||||
-rw-r--r-- | server/protocol/src/lib.rs | 6 | ||||
-rw-r--r-- | server/src/entity/mod.rs | 12 | ||||
-rw-r--r-- | server/src/entity/weather.rs | 51 | ||||
-rw-r--r-- | server/src/game.rs | 23 | ||||
-rw-r--r-- | server/src/lib.rs | 19 | ||||
-rw-r--r-- | test-client/protocol.ts | 7 |
13 files changed, 204 insertions, 11 deletions
diff --git a/client/game.gd b/client/game.gd index e0b06772..b1f209a1 100644 --- a/client/game.gd +++ b/client/game.gd @@ -257,6 +257,8 @@ func _ready(): if lobby_state and not join_sent: join() ) + + mp.update_environment.connect($Environment.update) func join(): join_sent = true diff --git a/client/game.tscn b/client/game.tscn index 345a35b8..a30e5fe2 100644 --- a/client/game.tscn +++ b/client/game.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=10 format=3 uid="uid://c6krh36hoqfg8"] +[gd_scene load_steps=11 format=3 uid="uid://c6krh36hoqfg8"] [ext_resource type="Script" path="res://game.gd" id="1_sftfn"] [ext_resource type="PackedScene" uid="uid://b31mlnao6ybt8" path="res://player/follow_camera.tscn" id="2_s8y6o"] @@ -6,6 +6,7 @@ [ext_resource type="Script" path="res://map/auto_setup/light_setup.gd" id="5_6fpff"] [ext_resource type="Script" path="res://multiplayer.gd" id="6_fbxu8"] [ext_resource type="PackedScene" uid="uid://b4gone8fu53r7" path="res://map/map.tscn" id="6_prg6t"] +[ext_resource type="PackedScene" uid="uid://nroo08m5og0" path="res://map/environment.tscn" id="7_v2apt"] [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_py7px"] ground_bottom_color = Color(0.0826605, 0.065772, 0.0461518, 1) @@ -45,3 +46,5 @@ script = ExtResource("5_6fpff") [node name="Map" parent="." instance=ExtResource("6_prg6t")] [node name="Center" type="Node3D" parent="."] + +[node name="Environment" parent="." instance=ExtResource("7_v2apt")] diff --git a/client/map/auto_setup/environment_setup.gd b/client/map/auto_setup/environment_setup.gd index 96feb86b..75dc900f 100644 --- a/client/map/auto_setup/environment_setup.gd +++ b/client/map/auto_setup/environment_setup.gd @@ -14,7 +14,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. # extends WorldEnvironment -class_name EnvironmentSetup +class_name EnvironmentSetup @export var allow_sdfgi := true @@ -26,3 +26,4 @@ func apply_settings(): environment.ssao_enabled = Global.get_setting("ssao") environment.sdfgi_enabled = Global.get_setting("sdfgi") and allow_sdfgi environment.glow_enabled = Global.get_setting("glow") + diff --git a/client/map/environment.gd b/client/map/environment.gd new file mode 100644 index 00000000..f36b51b6 --- /dev/null +++ b/client/map/environment.gd @@ -0,0 +1,10 @@ +extends Node3D + +func update(params: Dictionary): + $Wind.emitting = params["wind"] > 0.1 + var a: ParticleProcessMaterial = $Wind.process_material + a.initial_velocity_max = params["wind"] * 15. + a.initial_velocity_min = params["wind"] * 15. + + $Rain.emitting = params["rain"] > 0.1 + $Rain.amount = params["rain"] * 500 diff --git a/client/map/environment.tscn b/client/map/environment.tscn new file mode 100644 index 00000000..ee51b490 --- /dev/null +++ b/client/map/environment.tscn @@ -0,0 +1,73 @@ +[gd_scene load_steps=10 format=3 uid="uid://nroo08m5og0"] + +[ext_resource type="Script" path="res://map/environment.gd" id="1_qy481"] + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_fyrr8"] +emission_shape_offset = Vector3(0, 5, 0) +emission_shape = 3 +emission_box_extents = Vector3(5, 5, 5) +direction = Vector3(0, -1, 0) +spread = 5.0 +initial_velocity_min = 10.0 +initial_velocity_max = 10.0 +scale_over_velocity_max = 1000.0 +turbulence_influence_min = 0.01 +turbulence_influence_max = 0.01 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6mo3f"] +depth_draw_mode = 2 +albedo_color = Color(0.792157, 0.74902, 1, 1) +roughness = 0.07 + +[sub_resource type="CapsuleMesh" id="CapsuleMesh_6kss1"] +material = SubResource("StandardMaterial3D_6mo3f") +radius = 0.01 +height = 0.5 +radial_segments = 4 +rings = 4 + +[sub_resource type="Curve" id="Curve_frsnm"] +_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.111524, 0.771057), 1.26444, 1.26444, 0, 0, Vector2(0.866171, 0.820827), -1.03348, -1.03348, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 4 + +[sub_resource type="CurveTexture" id="CurveTexture_3gn1x"] +curve = SubResource("Curve_frsnm") + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_pyn6b"] +emission_shape_offset = Vector3(-2.5, 0, 0) +emission_shape = 3 +emission_box_extents = Vector3(5, 5, 5) +spread = 5.0 +initial_velocity_min = 2.0 +initial_velocity_max = 2.0 +gravity = Vector3(0, 0, 0) +scale_curve = SubResource("CurveTexture_3gn1x") +scale_over_velocity_max = 1000.0 +turbulence_enabled = true +turbulence_influence_min = 0.01 +turbulence_influence_max = 0.01 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ig8qu"] +depth_draw_mode = 2 +albedo_color = Color(0.412412, 0.527344, 0.250208, 1) + +[sub_resource type="SphereMesh" id="SphereMesh_4trvf"] +material = SubResource("StandardMaterial3D_ig8qu") +radius = 0.03 +height = 0.06 + +[node name="Environment" type="Node3D"] +script = ExtResource("1_qy481") + +[node name="Rain" type="GPUParticles3D" parent="."] +emitting = false +amount = 500 +process_material = SubResource("ParticleProcessMaterial_fyrr8") +draw_pass_1 = SubResource("CapsuleMesh_6kss1") + +[node name="Wind" type="GPUParticles3D" parent="."] +emitting = false +amount = 150 +lifetime = 5.0 +process_material = SubResource("ParticleProcessMaterial_pyn6b") +draw_pass_1 = SubResource("SphereMesh_4trvf") diff --git a/client/multiplayer.gd b/client/multiplayer.gd index cd697672..0e3b450a 100644 --- a/client/multiplayer.gd +++ b/client/multiplayer.gd @@ -50,6 +50,7 @@ signal set_tile_finished(tile: Vector2i, warn: bool) signal set_player_finished(player: int, warn: bool) signal set_ingame(state: bool, lobby: bool) signal score(demands_failed: int, demands_completed: int, points: int, time_remaining: float) +signal update_environment(params: Dictionary) signal server_message(text: String) signal replay_start() signal connection_closed(reason: String) @@ -265,6 +266,8 @@ func handle_packet(bytes: PackedByteArray): "server_message": var text = decoded["text"] server_message.emit(text) + "update_environment": + update_environment.emit(decoded) "replay_start": replay_start.emit() _: push_error("Unrecognized packet type: %s" % packet_type) diff --git a/data/maps/debug.yaml b/data/maps/debug.yaml index d571dc13..d31e2f5b 100644 --- a/data/maps/debug.yaml +++ b/data/maps/debug.yaml @@ -92,6 +92,7 @@ entities: - !customers - !player_portal { from: [12.5, 8.5], to: [12.5, 2.5] } - !item_portal { from: [14, 4], to: [14, 6] } + - !weather tile_entities: "}": !conveyor { dir: [1, 0], filter: dough-foodprocessor } diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index f4513641..2fcc5598 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -231,9 +231,9 @@ pub struct Score { #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] pub struct Environment { - rain: f64, // 0-1; clear..raining - wind: f64, // 0-1; still..stormy - time: f64, // 0-1; night..morning..noon..evening + pub rain: f32, // 0-1; clear..raining + pub wind: f32, // 0-1; still..stormy + pub time: f32, // 0-1; night..morning..noon..evening } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Copy, PartialEq, Eq, Hash)] diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index ad0bde6f..d042d458 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -19,6 +19,7 @@ pub mod conveyor; pub mod customers; pub mod item_portal; pub mod player_portal; +pub mod weather; use crate::{data::ItemTileRegistry, game::Game, interaction::Recipe}; use anyhow::{anyhow, Result}; @@ -32,6 +33,7 @@ use item_portal::ItemPortal; use player_portal::PlayerPortal; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet, VecDeque}; +use weather::WeatherController; pub trait EntityT: Clone { fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()>; @@ -49,7 +51,13 @@ macro_rules! entities { }; } -entities!(Conveyor, ItemPortal, PlayerPortal, Customers); +entities!( + Conveyor, + ItemPortal, + PlayerPortal, + Customers, + WeatherController +); #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] @@ -71,6 +79,7 @@ pub enum EntityDecl { to: Vec2, }, Customers {}, + Weather {}, } pub fn construct_entity( @@ -125,5 +134,6 @@ pub fn construct_entity( .collect(); Entity::Customers(Customers::new(chairs, demands)?) } + EntityDecl::Weather {} => Entity::WeatherController(WeatherController::default()), }) } diff --git a/server/src/entity/weather.rs b/server/src/entity/weather.rs new file mode 100644 index 00000000..79ac364f --- /dev/null +++ b/server/src/entity/weather.rs @@ -0,0 +1,51 @@ +use super::EntityT; +use crate::{game::Game, InterpolateExt}; +use hurrycurry_protocol::PacketC; +use rand::{random, seq::IndexedRandom, thread_rng}; +use std::{ + collections::VecDeque, + time::{Duration, Instant}, +}; + +#[derive(Clone, Debug)] +pub struct WeatherController { + next_event: Instant, + event: usize, +} + +impl Default for WeatherController { + fn default() -> Self { + Self { + next_event: Instant::now() + Duration::from_secs(10), + event: Default::default(), + } + } +} +impl EntityT for WeatherController { + fn tick( + &mut self, + game: &mut Game, + _packet_out: &mut VecDeque<PacketC>, + dt: f32, + ) -> anyhow::Result<()> { + if self.next_event < Instant::now() { + self.next_event += Duration::from_secs_f32(30. + random::<f32>() * 40.); + if self.event > 0 { + self.event = 0; + } else { + self.event = *[0, 0, 1, 2].choose(&mut thread_rng()).unwrap(); + } + } + + let (rain, wind) = match self.event { + 0 => (0., 0.), + 1 => (0.7, 0.), + 2 => (0., 0.7), + _ => (1., 1.), + }; + game.environment.wind.exp_to(wind, dt * 0.05); + game.environment.rain.exp_to(rain, dt * 0.15); + eprintln!("{:#?}", game.environment); + Ok(()) + } +} diff --git a/server/src/game.rs b/server/src/game.rs index d649d87e..69272159 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -25,8 +25,8 @@ use anyhow::{anyhow, bail, Result}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, movement::MovementBase, - ClientGamedata, ItemIndex, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID, - RecipeIndex, Score, TileIndex, + ClientGamedata, Environment, ItemIndex, ItemLocation, Menu, Message, PacketC, PacketS, + PlayerID, RecipeIndex, Score, TileIndex, }; use log::{info, warn}; use std::{ @@ -76,6 +76,8 @@ pub struct Game { end: Option<Instant>, pub lobby: bool, + pub environment_next_update: Instant, + pub environment: Environment, pub score_changed: bool, pub score: Score, } @@ -93,11 +95,13 @@ impl Game { data: Gamedata::default().into(), players: HashMap::new(), tiles: HashMap::new(), + environment_next_update: Instant::now(), walkable: HashSet::new(), end: None, entities: Arc::new(RwLock::new(vec![])), players_spatial_index: SpatialIndex::default(), score: Score::default(), + environment: Environment::default(), score_changed: false, } } @@ -117,6 +121,9 @@ impl Game { neighbors: [None, None, None, None], }) } + self.score = Score::default(); + self.end = None; + self.environment = Environment::default(); self.walkable.clear(); } pub fn load( @@ -215,6 +222,7 @@ impl Game { .collect(), }, }); + out.push(PacketC::UpdateEnvironment(self.environment.clone())); for (&id, player) in &self.players { out.push(PacketC::AddPlayer { id, @@ -589,9 +597,16 @@ impl Game { } } + let now = Instant::now(); + + if self.environment_next_update < now { + packet_out.push_back(PacketC::UpdateEnvironment(self.environment.clone())); + self.environment_next_update += Duration::from_secs(5); + } + if let Some(end) = self.end { - self.score.time_remaining = (end - Instant::now()).as_secs_f64(); - if end < Instant::now() { + self.score.time_remaining = (end - now).as_secs_f64(); + if end < now { let relative_score = (self.score.points * 100) / self.data.score_baseline.max(1); self.score.stars = match relative_score { 100.. => 3, diff --git a/server/src/lib.rs b/server/src/lib.rs index a59aad11..2cbcc10b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -20,5 +20,22 @@ pub mod data; pub mod entity; pub mod game; pub mod interaction; -pub mod state; pub mod spatial_index; +pub mod state; + +use hurrycurry_protocol::glam::Vec2; + +pub trait InterpolateExt { + fn exp_to(&mut self, target: Self, dt: f32); +} +impl InterpolateExt for Vec2 { + fn exp_to(&mut self, target: Self, dt: f32) { + self.x = target.x + (self.x - target.x) * (-dt).exp(); + self.y = target.y + (self.y - target.y) * (-dt).exp(); + } +} +impl InterpolateExt for f32 { + fn exp_to(&mut self, target: Self, dt: f32) { + *self = target + (*self - target) * (-dt).exp(); + } +} diff --git a/test-client/protocol.ts b/test-client/protocol.ts index 432f30e2..ef216ffd 100644 --- a/test-client/protocol.ts +++ b/test-client/protocol.ts @@ -59,6 +59,7 @@ export type PacketC = | { type: "score" } & Score // Supplies information for score OSD | { type: "menu" } & Menu // Open a menu on the client-side | { type: "movement_sync" } // Your movement is difference on the server, you should update your position from a `position` packet + | { type: "update_environment" } & Environment | { type: "set_ingame", state: boolean, lobby: boolean } // Set to false when entering the game or switching maps | { type: "error", message: string } // Your client did something wrong. @@ -86,3 +87,9 @@ export type Message = export type ItemLocation = { player: PlayerID } | { tile: Vec2 } + +export interface Environment { + rain: number, // 0-1; clear..raining + wind: number, // 0-1; still..stormy + time: number, // 0-1; night..morning..noon..evening +}
\ No newline at end of file |