Skip to content

Commit

Permalink
Buildings can start burning
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanNano committed Jul 5, 2024
1 parent ee4f94e commit f532733
Show file tree
Hide file tree
Showing 28 changed files with 655 additions and 29 deletions.
2 changes: 1 addition & 1 deletion export_presets.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ custom_features=""
export_filter="all_resources"
include_filter="resources/Maps/*.sc2.mpz"
exclude_filter="build/*, snapshots/*, resources/TestScenes/*, resources/HDR/*, addons/customization/*, addons/visual_shader_node_library/examples/*"
export_path="build/sc_test_7_mac.dmg"
export_path="build/sc_test_8_mac.dmg"
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
Expand Down
4 changes: 2 additions & 2 deletions native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ thiserror = "1.0.56"
anyhow = { version = "1.0.79" }
rand = "0.8.5"

godot-rust-script = { git = "https://github.com/titannano/godot-rust-script", rev = "da275fb0cb61e24571295c8d5a3cce4123fdce24" }
godot-rust-script = { git = "https://github.com/titannano/godot-rust-script", rev = "7549d6fc208c545d3f06f02a68a7ce163073ce5d" }
1 change: 1 addition & 0 deletions native/src/scripts/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod objects;
mod particles;
mod spawner;
mod world;
Expand Down
1 change: 1 addition & 0 deletions native/src/scripts/objects.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod building;
190 changes: 190 additions & 0 deletions native/src/scripts/objects/building.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use godot::{
builtin::{Array, NodePath, Transform3D, Vector3},
engine::{MeshInstance3D, Node, Node3D, PackedScene, ResourceLoader, Time},
meta::ToGodot,
obj::{Gd, Inherits},
};
use godot_rust_script::{godot_script_impl, Context, GodotScript};
use rand::Rng;
use std::fmt::Debug;

use crate::{util::logger, world::city_data::TileCoords};

trait BuildingFeature<N: Inherits<Node>>: Debug {
fn process(&mut self, _delta: f64, _node: &mut Gd<N>) {}
fn physics_process(&mut self, _delta: f64, _node: &mut Gd<N>) {}
}

struct BuildingEventFlags(u8);

impl BuildingEventFlags {
fn fire(&self) -> bool {
self.0 & 0b00000001 == 1
}
}

#[derive(Debug)]
struct Features<F: Debug + ?Sized>(Vec<Box<F>>);

impl<F: Debug + ?Sized> Features<F> {
pub fn push(&mut self, feature: Box<F>) {
self.0.push(feature);
}
}

impl<F: Debug + ?Sized> Default for Features<F> {
fn default() -> Self {
Self(Vec::default())
}
}

impl<N: Inherits<Node>> BuildingFeature<N> for Features<dyn BuildingFeature<N>> {
fn process(&mut self, delta: f64, node: &mut Gd<N>) {
for item in self.0.iter_mut() {
item.process(delta, node);
}
}

fn physics_process(&mut self, delta: f64, node: &mut Gd<N>) {
for item in self.0.iter_mut() {
item.physics_process(delta, node);
}
}
}

const FIRE_SPAWNER_SCENE: &str = "res://resources/Objects/Spawner/fire_spawner.tscn";

#[derive(Debug)]
struct FireFeature {
fire_scene: Option<Gd<Node3D>>,
last_fire: u64,
building_mesh: Gd<MeshInstance3D>,
tile_coords: TileCoords,
}

impl FireFeature {
fn new(tile_coords: TileCoords, mesh: &Gd<MeshInstance3D>) -> Self {
Self {
fire_scene: None,
last_fire: 0,
building_mesh: mesh.to_owned(),
tile_coords,
}
}
}

impl<N: Inherits<Node>> BuildingFeature<N> for FireFeature {
fn process(&mut self, _delta: f64, node: &mut Gd<N>) {
let current_ticks = Time::singleton().get_ticks_msec();

if let Some(ref mut scene) = self.fire_scene {
if current_ticks - self.last_fire < 60_000 {
return;
}

scene.queue_free();
self.fire_scene = None;
return;
}

let tick_delta = current_ticks - self.last_fire;
let tick_boost = (tick_delta + 10_000) as f64 / 10_000.0;
let rng = rand::thread_rng().gen_range(0.0..1.0);

let chance = rng * tick_boost;

if chance < 0.5 {
return;
}

let Some(scene) = ResourceLoader::singleton()
.load_ex(FIRE_SPAWNER_SCENE.into())
.type_hint("PackedScene".into())
.done()
else {
logger::error!("Failed to load fire_spawner scene: {}", FIRE_SPAWNER_SCENE);
return;
};

let Some(mut scene_instance) = scene.cast::<PackedScene>().try_instantiate_as::<Node3D>()
else {
logger::error!(
"Failed to instantiate fire_spawner scene as decendant of Node3D: {}",
FIRE_SPAWNER_SCENE
);
return;
};

let aabb = self.building_mesh.get_aabb();
let aabb_size = (Transform3D::new(self.building_mesh.get_basis(), Vector3::default())
* aabb.size)
.abs();

scene_instance.call_deferred("resize".into(), &[aabb_size.to_variant()]);

node.upcast_mut()
.add_child_ex(scene_instance.clone().upcast())
.force_readable_name(true)
.done();

self.fire_scene = Some(scene_instance);
self.last_fire = current_ticks;

logger::info!("Building started burning: {:?}", self.tile_coords);
}
}

#[derive(GodotScript, Debug)]
#[script(base = Node)]
struct Building {
#[export(flags = ["Fire:1"])]
pub events: u8,

#[export(node_path = ["MeshInstance3D"])]
pub mesh_path: NodePath,

pub tile_coords_array: Array<u32>,

tile_coords: TileCoords,

mesh: Option<Gd<MeshInstance3D>>,
features: Features<dyn BuildingFeature<Node>>,

base: Gd<Node>,
}

#[godot_script_impl]
impl Building {
pub fn _ready(&mut self, mut context: Context) {
let events = BuildingEventFlags(self.events);

self.mesh = {
let mesh_path = self.mesh_path.clone();
let base = self.base.clone();

context.reentrant_scope(self, || base.try_get_node_as(mesh_path.to_owned()))
};

self.tile_coords = (
self.tile_coords_array.get(0).unwrap_or(0),
self.tile_coords_array.get(1).unwrap_or(0),
);

if events.fire() {
if let Some(ref mesh) = self.mesh {
self.features
.push(Box::new(FireFeature::new(self.tile_coords, mesh)));
} else {
logger::warn!("Unable to instantiate FireFeature because no mesh has been set.");
}
}
}

pub fn _process(&mut self, delta: f64) {
self.features.process(delta, &mut self.base);
}

pub fn _physics_process(&mut self, delta: f64) {
self.features.physics_process(delta, &mut self.base);
}
}
98 changes: 98 additions & 0 deletions native/src/scripts/spawner/fire_spawner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use godot::{
builtin::{NodePath, Transform3D, Vector3, Vector3Axis},
engine::{light_3d, AnimationPlayer, FogVolume, Node, OmniLight3D},
obj::Gd,
};
use godot_rust_script::{godot_script_impl, GodotScript};

use crate::util::logger;

#[derive(GodotScript, Debug)]
#[script(base = Node)]
struct FireSpawner {
#[export(node_path = ["FogVolume"])]
pub fire_path: NodePath,
fire: Option<Gd<FogVolume>>,

#[export(node_path = ["FogVolume"])]
pub smoke_path: NodePath,
smoke: Option<Gd<FogVolume>>,

#[export(node_path = ["OmniLight3D"])]
pub light_source_path: NodePath,
light_source: Option<Gd<OmniLight3D>>,

#[export(node_path = ["AnimationPlayer"])]
pub audio_player_path: NodePath,
audio_player: Option<Gd<AnimationPlayer>>,

base: Gd<Node>,
}

#[godot_script_impl]
impl FireSpawner {
pub fn _ready(&mut self) {
logger::debug!("Init Fire spawner...");
self.fire = self.base.try_get_node_as(self.fire_path.clone());
self.smoke = self.base.try_get_node_as(self.smoke_path.clone());
self.audio_player = self.base.try_get_node_as(self.audio_player_path.clone());
self.light_source = self.base.try_get_node_as(self.light_source_path.clone());

if let Some(ref mut audio_player) = self.audio_player {
audio_player.set_current_animation("burning".into());
} else {
logger::warn!("No audio player has been setup!");
}
}

pub fn resize(&mut self, size: Vector3) {
logger::debug!("Resizing fire spawner...");
let Some(ref mut fire) = self.fire else {
logger::error!("Failed to resize fire spawner! No fire setup!");
return;
};

let Some(ref mut smoke) = self.smoke else {
logger::error!("Failed to resize fire spawner! No smoke setup!");
return;
};

let Some(ref mut light_source) = self.light_source else {
logger::error!("Failed to resize fire spawner! No light source setup!");
return;
};

let smoke_ratio = smoke.get_size() / fire.get_size();
let fire_size = size * Vector3::new(1.0, 1.5, 1.0);
let smoke_size = fire_size * smoke_ratio;
let light_size = size;

fire.set_size(fire_size);
fire.set_transform(Transform3D::default().translated(Vector3::new(
0.0,
fire_size.y / 2.0 * 0.9,
0.0,
)));

smoke.set_size(smoke_size);
smoke.set_transform(Transform3D::default().translated(Vector3::new(
0.0,
smoke_size.y / 2.0 * 1.2,
0.0,
)));

let light_max_size = match light_size.max_axis().unwrap_or(Vector3Axis::X) {
Vector3Axis::X => light_size.x,
Vector3Axis::Y => light_size.y,
Vector3Axis::Z => light_size.z,
};

light_source.set_param(light_3d::Param::RANGE, light_size.length_squared());
light_source.set_param(light_3d::Param::SIZE, light_max_size);
light_source.set_transform(Transform3D::default().translated(Vector3::new(
0.0,
light_size.y / 2.0,
0.0,
)))
}
}
1 change: 1 addition & 0 deletions native/src/scripts/spawner/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod car_spawner;
mod fire_spawner;
15 changes: 13 additions & 2 deletions native/src/scripts/world/buildings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,22 @@ impl Buildings {
));
}

let (Some(mut instance), instance_time) = with_timing(|| object.instantiate()) else {
let (Some(mut instance), instance_time) =
with_timing(|| object.try_instantiate_as::<Node3D>())
else {
logger::error!("failed to instantiate building {}", name);
return;
};

if !instance.get("tile_coords_array".into()).is_nil() {
let mut array = Array::new();

array.push(tile_coords.0);
array.push(tile_coords.1);

instance.set("tile_coords_array".into(), array.to_variant());
}

let mut location = self.city_coords_feature.get_building_coords(
tile_coords.0,
tile_coords.1,
Expand All @@ -164,7 +175,7 @@ impl Buildings {

let (_, insert_time) = with_timing(|| {
self.get_sector(tile_coords)
.add_child_ex(instance.clone())
.add_child_ex(instance.clone().upcast())
.force_readable_name(true)
.done();

Expand Down
5 changes: 3 additions & 2 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,17 @@ limits/debugger_stdout/max_chars_per_second=1000000
[physics]

3d/default_gravity=9.81
common/physics_interpolation=true
common/physics_fps=120
common/enable_pause_aware_picking=true
common/physics_interpolation=true

[rendering]

textures/vram_compression/import_s3tc_bptc=true
lights_and_shadows/directional_shadow/size=8192
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=4
lights_and_shadows/directional_shadow/16_bits=false
environment/volumetric_fog/volume_size=200
environment/volumetric_fog/volume_depth=200
lights_and_shadows/positional_shadow/atlas_16_bits=false
threads/thread_model=2
environment/default_environment="res://resources/Environments/default_env.tres"
3 changes: 3 additions & 0 deletions resources/Environments/WorldEnv.tres
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ ssao_enabled = true
sdfgi_enabled = true
fog_light_color = Color(0.65, 0.84, 0.95, 1)
fog_density = 0.001
volumetric_fog_enabled = true
volumetric_fog_density = 0.0
volumetric_fog_length = 2000.0
Loading

0 comments on commit f532733

Please sign in to comment.