feat(ambience): underwater shaders + audio manager + main menu + CC0 assets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,3 +9,10 @@ _À compléter au fur et à mesure des phases_
|
||||
| Asset | Source | Licence |
|
||||
|-------|--------|---------|
|
||||
| icon.svg | Créé manuellement | CC0 |
|
||||
| audio/music/underwater_theme.mp3 | https://opengameart.org/content/underwater-theme — Cleyton RX | CC-BY 3.0 |
|
||||
| audio/sfx/underwater_ambient.ogg | https://freesound.org/people/Zozzy/sounds/56678/ (nemoscape2.mp3) — Zozzy | CC0 |
|
||||
| audio/sfx/bubbles.mp3 | https://freesound.org/people/ristooooo1/sounds/539823/ — ristooooo1 | CC0 |
|
||||
| audio/sfx/whale_call.mp3 | https://freesound.org/people/taure/sounds/361423/ (Whalesong.wav) — taure | CC0 |
|
||||
|
||||
## Attribution requise
|
||||
- **Cleyton RX - Underwater Theme** : musique sous Creative Commons Attribution 3.0. Crédit : "Underwater Theme by Cleyton RX, https://opengameart.org/content/underwater-theme, CC-BY 3.0"
|
||||
|
||||
BIN
audio/music/underwater_theme.mp3
Normal file
BIN
audio/music/underwater_theme.mp3
Normal file
Binary file not shown.
BIN
audio/sfx/bubbles.mp3
Normal file
BIN
audio/sfx/bubbles.mp3
Normal file
Binary file not shown.
BIN
audio/sfx/underwater_ambient.ogg
Normal file
BIN
audio/sfx/underwater_ambient.ogg
Normal file
Binary file not shown.
BIN
audio/sfx/whale_call.mp3
Normal file
BIN
audio/sfx/whale_call.mp3
Normal file
Binary file not shown.
@@ -89,6 +89,7 @@ escape={
|
||||
|
||||
[autoload]
|
||||
BlockDatabase="*res://scripts/world/BlockDatabase.gd"
|
||||
AudioManager="*res://scripts/ambience/AudioManager.gd"
|
||||
|
||||
[rendering]
|
||||
environment/defaults/default_clear_color=Color(0.05, 0.15, 0.25, 1)
|
||||
|
||||
141
scenes/MainMenu.tscn
Normal file
141
scenes/MainMenu.tscn
Normal file
@@ -0,0 +1,141 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dauphincraft_mainmenu"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/ambience/MainMenu.gd" id="1_mainmenu"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_btn"]
|
||||
bg_color = Color(0.05, 0.15, 0.3, 0.75)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.3, 0.8, 1.0, 1.0)
|
||||
corner_radius_top_left = 6
|
||||
corner_radius_top_right = 6
|
||||
corner_radius_bottom_right = 6
|
||||
corner_radius_bottom_left = 6
|
||||
|
||||
[node name="MainMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1_mainmenu")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.02, 0.08, 0.18, 1.0)
|
||||
|
||||
[node name="CenterContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = -160.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 160.0
|
||||
alignment = 1
|
||||
|
||||
[node name="Title" type="Label" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
text = "DAUPHINCRAFT"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 80
|
||||
theme_override_colors/font_color = Color(0.7, 0.95, 1.0, 1.0)
|
||||
theme_override_colors/font_shadow_color = Color(0.0, 0.3, 0.6, 0.8)
|
||||
theme_override_constants/shadow_offset_x = 3
|
||||
theme_override_constants/shadow_offset_y = 3
|
||||
|
||||
[node name="Subtitle" type="Label" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
text = "Un monde voxel sous l'océan"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 22
|
||||
theme_override_colors/font_color = Color(0.5, 0.75, 0.9, 0.85)
|
||||
|
||||
[node name="Spacer" type="Control" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(0, 30)
|
||||
|
||||
[node name="DiveButton" type="Button" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(300, 56)
|
||||
text = "Plonger"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_colors/font_color = Color(0.8, 0.97, 1.0, 1.0)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_btn")
|
||||
|
||||
[node name="OptionsButton" type="Button" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(300, 56)
|
||||
text = "Options"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_colors/font_color = Color(0.8, 0.97, 1.0, 1.0)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_btn")
|
||||
|
||||
[node name="QuitButton" type="Button" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(300, 56)
|
||||
text = "Quitter"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_colors/font_color = Color(0.8, 0.97, 1.0, 1.0)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_btn")
|
||||
|
||||
[node name="Credits" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_left = 0.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -30.0
|
||||
text = "Assets: Kenney, OpenGameArt, Freesound (voir CREDITS.md)"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 13
|
||||
theme_override_colors/font_color = Color(0.4, 0.6, 0.7, 0.6)
|
||||
|
||||
[node name="OptionsPopup" type="Window" parent="."]
|
||||
title = "Options"
|
||||
size = Vector2i(360, 220)
|
||||
visible = false
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="OptionsPopup"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 20.0
|
||||
offset_top = 20.0
|
||||
offset_right = -20.0
|
||||
offset_bottom = -20.0
|
||||
|
||||
[node name="MusicLabel" type="Label" parent="OptionsPopup/VBox"]
|
||||
text = "Volume Musique"
|
||||
|
||||
[node name="MusicSlider" type="HSlider" parent="OptionsPopup/VBox"]
|
||||
layout_mode = 2
|
||||
min_value = 0.0
|
||||
max_value = 1.0
|
||||
step = 0.01
|
||||
value = 0.7
|
||||
|
||||
[node name="SFXLabel" type="Label" parent="OptionsPopup/VBox"]
|
||||
text = "Volume SFX"
|
||||
|
||||
[node name="SFXSlider" type="HSlider" parent="OptionsPopup/VBox"]
|
||||
layout_mode = 2
|
||||
min_value = 0.0
|
||||
max_value = 1.0
|
||||
step = 0.01
|
||||
value = 0.8
|
||||
|
||||
[connection signal="pressed" from="CenterContainer/DiveButton" to="." method="_on_dive_pressed"]
|
||||
[connection signal="pressed" from="CenterContainer/OptionsButton" to="." method="_on_options_pressed"]
|
||||
[connection signal="pressed" from="CenterContainer/QuitButton" to="." method="_on_quit_pressed"]
|
||||
[connection signal="value_changed" from="OptionsPopup/VBox/MusicSlider" to="." method="_on_music_slider_value_changed"]
|
||||
[connection signal="value_changed" from="OptionsPopup/VBox/SFXSlider" to="." method="_on_sfx_slider_value_changed"]
|
||||
124
scripts/ambience/AudioManager.gd
Normal file
124
scripts/ambience/AudioManager.gd
Normal file
@@ -0,0 +1,124 @@
|
||||
extends Node
|
||||
|
||||
var music_player: AudioStreamPlayer
|
||||
var ambient_loop: AudioStreamPlayer
|
||||
var whale_player: AudioStreamPlayer
|
||||
|
||||
var _whale_timer: Timer
|
||||
var _music_tween: Tween
|
||||
|
||||
const MUSIC_PATH := "res://audio/music/"
|
||||
const SFX_PATH := "res://audio/sfx/"
|
||||
|
||||
func _ready() -> void:
|
||||
music_player = AudioStreamPlayer.new()
|
||||
music_player.bus = "Music"
|
||||
music_player.volume_db = -10.0
|
||||
add_child(music_player)
|
||||
|
||||
ambient_loop = AudioStreamPlayer.new()
|
||||
ambient_loop.bus = "SFX"
|
||||
ambient_loop.volume_db = -12.0
|
||||
add_child(ambient_loop)
|
||||
|
||||
whale_player = AudioStreamPlayer.new()
|
||||
whale_player.bus = "SFX"
|
||||
whale_player.volume_db = -15.0
|
||||
add_child(whale_player)
|
||||
|
||||
_whale_timer = Timer.new()
|
||||
_whale_timer.one_shot = true
|
||||
_whale_timer.timeout.connect(_on_whale_timer)
|
||||
add_child(_whale_timer)
|
||||
_schedule_next_whale()
|
||||
|
||||
|
||||
func play_music(track_name: String) -> void:
|
||||
var path := MUSIC_PATH + track_name
|
||||
if not ResourceLoader.exists(path):
|
||||
return
|
||||
|
||||
var stream: AudioStream = load(path)
|
||||
if stream == null:
|
||||
return
|
||||
|
||||
music_player.stream = stream
|
||||
music_player.stream.loop = true
|
||||
music_player.volume_db = -40.0
|
||||
music_player.play()
|
||||
|
||||
if _music_tween:
|
||||
_music_tween.kill()
|
||||
_music_tween = create_tween()
|
||||
_music_tween.tween_property(music_player, "volume_db", -10.0, 3.0).set_trans(Tween.TRANS_SINE)
|
||||
|
||||
|
||||
func play_ambient_loop(loop_name: String) -> void:
|
||||
var path := SFX_PATH + loop_name
|
||||
if not ResourceLoader.exists(path):
|
||||
return
|
||||
|
||||
var stream: AudioStream = load(path)
|
||||
if stream == null:
|
||||
return
|
||||
|
||||
ambient_loop.stream = stream
|
||||
ambient_loop.stream.loop = true
|
||||
ambient_loop.play()
|
||||
|
||||
|
||||
func play_whale_call_random() -> void:
|
||||
var path := SFX_PATH + "whale_call.ogg"
|
||||
if not ResourceLoader.exists(path):
|
||||
return
|
||||
if whale_player.playing:
|
||||
return
|
||||
|
||||
var stream: AudioStream = load(path)
|
||||
if stream == null:
|
||||
return
|
||||
|
||||
whale_player.stream = stream
|
||||
whale_player.play()
|
||||
|
||||
|
||||
func play_bubble_sfx(position: Vector3) -> void:
|
||||
var path := SFX_PATH + "bubbles.ogg"
|
||||
if not ResourceLoader.exists(path):
|
||||
return
|
||||
|
||||
var player := AudioStreamPlayer3D.new()
|
||||
player.bus = "SFX"
|
||||
player.volume_db = -8.0
|
||||
player.max_distance = 20.0
|
||||
player.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_SQUARE_DISTANCE
|
||||
add_child(player)
|
||||
player.global_position = position
|
||||
|
||||
var stream: AudioStream = load(path)
|
||||
if stream == null:
|
||||
player.queue_free()
|
||||
return
|
||||
|
||||
player.stream = stream
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
|
||||
|
||||
func set_music_volume(db: float) -> void:
|
||||
music_player.volume_db = db
|
||||
|
||||
|
||||
func set_sfx_volume(db: float) -> void:
|
||||
ambient_loop.volume_db = db
|
||||
whale_player.volume_db = db - 3.0
|
||||
|
||||
|
||||
func _on_whale_timer() -> void:
|
||||
play_whale_call_random()
|
||||
_schedule_next_whale()
|
||||
|
||||
|
||||
func _schedule_next_whale() -> void:
|
||||
_whale_timer.wait_time = randf_range(30.0, 90.0)
|
||||
_whale_timer.start()
|
||||
29
scripts/ambience/MainMenu.gd
Normal file
29
scripts/ambience/MainMenu.gd
Normal file
@@ -0,0 +1,29 @@
|
||||
extends Control
|
||||
|
||||
@onready var _options_popup: Window = $OptionsPopup
|
||||
@onready var _music_slider: HSlider = $OptionsPopup/VBox/MusicSlider
|
||||
@onready var _sfx_slider: HSlider = $OptionsPopup/VBox/SFXSlider
|
||||
|
||||
func _ready() -> void:
|
||||
AudioManager.play_music("underwater_theme.mp3")
|
||||
AudioManager.play_ambient_loop("underwater_ambient.ogg")
|
||||
|
||||
|
||||
func _on_dive_pressed() -> void:
|
||||
get_tree().change_scene_to_file("res://scenes/Main.tscn")
|
||||
|
||||
|
||||
func _on_options_pressed() -> void:
|
||||
_options_popup.popup_centered()
|
||||
|
||||
|
||||
func _on_quit_pressed() -> void:
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func _on_music_slider_value_changed(value: float) -> void:
|
||||
AudioManager.set_music_volume(linear_to_db(value))
|
||||
|
||||
|
||||
func _on_sfx_slider_value_changed(value: float) -> void:
|
||||
AudioManager.set_sfx_volume(linear_to_db(value))
|
||||
57
scripts/ambience/PlanktonParticles.gd
Normal file
57
scripts/ambience/PlanktonParticles.gd
Normal file
@@ -0,0 +1,57 @@
|
||||
extends GPUParticles3D
|
||||
|
||||
func _ready() -> void:
|
||||
amount = 500
|
||||
lifetime = 10.0
|
||||
one_shot = false
|
||||
explosiveness = 0.0
|
||||
randomness = 1.0
|
||||
visibility_aabb = AABB(Vector3(-50, -30, -50), Vector3(100, 60, 100))
|
||||
|
||||
var process_mat := ParticleProcessMaterial.new()
|
||||
process_mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
|
||||
process_mat.emission_box_extents = Vector3(50.0, 30.0, 50.0)
|
||||
|
||||
process_mat.direction = Vector3(0.0, 0.0, 0.0)
|
||||
process_mat.spread = 180.0
|
||||
process_mat.initial_velocity_min = 0.0
|
||||
process_mat.initial_velocity_max = 0.1
|
||||
|
||||
process_mat.linear_accel_min = -0.05
|
||||
process_mat.linear_accel_max = 0.05
|
||||
process_mat.radial_accel_min = -0.02
|
||||
process_mat.radial_accel_max = 0.02
|
||||
|
||||
process_mat.damping_min = 0.5
|
||||
process_mat.damping_max = 1.0
|
||||
|
||||
process_mat.scale_min = 0.02
|
||||
process_mat.scale_max = 0.08
|
||||
|
||||
process_mat.color = Color(0.85, 0.92, 1.0, 0.8)
|
||||
|
||||
var grad := Gradient.new()
|
||||
grad.set_color(0, Color(0.85, 0.92, 1.0, 0.0))
|
||||
grad.set_color(1, Color(0.85, 0.92, 1.0, 0.0))
|
||||
grad.add_point(0.1, Color(0.85, 0.92, 1.0, 0.8))
|
||||
grad.add_point(0.9, Color(0.85, 0.92, 1.0, 0.8))
|
||||
var grad_tex := GradientTexture1D.new()
|
||||
grad_tex.gradient = grad
|
||||
process_mat.color_ramp = grad_tex
|
||||
|
||||
process_material = process_mat
|
||||
|
||||
var quad := QuadMesh.new()
|
||||
quad.size = Vector2(0.05, 0.05)
|
||||
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(0.8, 0.9, 1.0)
|
||||
mat.emission_energy_multiplier = 1.5
|
||||
mat.albedo_color = Color(0.85, 0.92, 1.0, 0.8)
|
||||
|
||||
quad.surface_set_material(0, mat)
|
||||
draw_pass_1 = quad
|
||||
44
scripts/ambience/UnderwaterEnvironment.gd
Normal file
44
scripts/ambience/UnderwaterEnvironment.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
extends WorldEnvironment
|
||||
|
||||
func _ready() -> void:
|
||||
var env := Environment.new()
|
||||
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.03, 0.12, 0.22)
|
||||
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.2, 0.4, 0.6)
|
||||
env.ambient_light_energy = 0.4
|
||||
|
||||
env.fog_enabled = true
|
||||
env.fog_light_color = Color(0.1, 0.3, 0.5)
|
||||
env.fog_density = 0.02
|
||||
env.fog_aerial_perspective = 0.3
|
||||
|
||||
env.volumetric_fog_enabled = true
|
||||
env.volumetric_fog_density = 0.04
|
||||
env.volumetric_fog_albedo = Color(0.2, 0.5, 0.7)
|
||||
env.volumetric_fog_emission = Color(0.02, 0.06, 0.1)
|
||||
env.volumetric_fog_emission_energy = 0.1
|
||||
env.volumetric_fog_length = 64.0
|
||||
env.volumetric_fog_detail_spread = 2.0
|
||||
|
||||
env.glow_enabled = true
|
||||
env.glow_intensity = 0.5
|
||||
env.glow_bloom = 0.1
|
||||
env.glow_blend_mode = Environment.GLOW_BLEND_MODE_SOFTLIGHT
|
||||
|
||||
env.ssao_enabled = true
|
||||
env.ssao_radius = 1.0
|
||||
env.ssao_intensity = 1.0
|
||||
env.ssao_power = 1.5
|
||||
env.ssao_detail = 0.5
|
||||
|
||||
env.tone_mapper = Environment.TONE_MAPPER_FILMIC
|
||||
env.exposure = 1.0
|
||||
|
||||
env.adjustment_enabled = true
|
||||
env.adjustment_saturation = 1.1
|
||||
env.adjustment_color_correction = null
|
||||
|
||||
environment = env
|
||||
21
shaders/caustics.gdshader
Normal file
21
shaders/caustics.gdshader
Normal file
@@ -0,0 +1,21 @@
|
||||
shader_type spatial;
|
||||
render_mode unshaded, blend_add, cull_disabled, depth_draw_never;
|
||||
|
||||
uniform float speed : hint_range(0.1, 3.0) = 0.8;
|
||||
uniform float scale : hint_range(1.0, 20.0) = 8.0;
|
||||
uniform float intensity : hint_range(0.0, 2.0) = 0.6;
|
||||
uniform vec3 caustic_color : source_color = vec3(0.7, 0.9, 1.0);
|
||||
|
||||
void fragment() {
|
||||
vec2 uv = UV * scale;
|
||||
|
||||
float pattern1 = sin(TIME * speed + uv.x * 10.0 + sin(uv.y * 10.0 + TIME * speed));
|
||||
float pattern2 = sin(TIME * speed * 0.7 + uv.y * 12.0 + sin(uv.x * 8.0 + TIME * speed * 1.3));
|
||||
float pattern3 = sin(TIME * speed * 1.1 + (uv.x + uv.y) * 7.0 + sin(uv.x * 5.0 - TIME * speed * 0.5));
|
||||
|
||||
float caustic = (pattern1 + pattern2 + pattern3) / 3.0;
|
||||
caustic = pow(max(caustic, 0.0), 2.0) * intensity;
|
||||
|
||||
EMISSION = caustic_color * caustic;
|
||||
ALBEDO = vec3(0.0);
|
||||
}
|
||||
20
shaders/godrays.gdshader
Normal file
20
shaders/godrays.gdshader
Normal file
@@ -0,0 +1,20 @@
|
||||
shader_type canvas_item;
|
||||
render_mode blend_add;
|
||||
|
||||
uniform float ray_count : hint_range(5.0, 40.0) = 20.0;
|
||||
uniform float speed : hint_range(0.1, 2.0) = 0.4;
|
||||
uniform float intensity : hint_range(0.0, 2.0) = 0.5;
|
||||
uniform vec3 ray_color : source_color = vec3(0.5, 0.8, 1.0);
|
||||
|
||||
void fragment() {
|
||||
vec2 uv = UV;
|
||||
|
||||
float vertical_fade = pow(1.0 - uv.y, 2.0);
|
||||
|
||||
float ray_pattern = fract(uv.x * ray_count + sin(TIME * speed + uv.y * 5.0) * 0.3);
|
||||
float ray = smoothstep(0.0, 0.15, ray_pattern) * smoothstep(0.4, 0.15, ray_pattern);
|
||||
|
||||
float brightness = ray * vertical_fade * intensity;
|
||||
|
||||
COLOR = vec4(ray_color * brightness, brightness * 0.6);
|
||||
}
|
||||
21
shaders/underwater_fog.gdshader
Normal file
21
shaders/underwater_fog.gdshader
Normal file
@@ -0,0 +1,21 @@
|
||||
shader_type fog;
|
||||
|
||||
uniform float surface_y : hint_range(-100.0, 100.0) = 0.0;
|
||||
uniform float abyss_depth : hint_range(-200.0, 0.0) = -60.0;
|
||||
|
||||
void fog() {
|
||||
float depth_factor = clamp((WORLD_POSITION.y - abyss_depth) / (surface_y - abyss_depth), 0.0, 1.0);
|
||||
|
||||
vec3 surface_color = vec3(0.15, 0.45, 0.6);
|
||||
vec3 abyss_color = vec3(0.02, 0.08, 0.15);
|
||||
vec3 fog_color = mix(abyss_color, surface_color, depth_factor);
|
||||
|
||||
float current_wave = sin(TIME * 0.3 + WORLD_POSITION.x * 0.05) * 0.02
|
||||
+ sin(TIME * 0.17 + WORLD_POSITION.z * 0.04) * 0.01;
|
||||
|
||||
float base_density = mix(0.3, 0.05, depth_factor) + current_wave;
|
||||
base_density = clamp(base_density, 0.0, 0.4);
|
||||
|
||||
ALBEDO = fog_color;
|
||||
DENSITY = base_density;
|
||||
}
|
||||
Reference in New Issue
Block a user