diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 00000000..3a8694ec
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,6 @@
+((c++-mode . ((mode . clang-format-on-save)))
+ (c-mode . ((mode . c++)))
+ (nil . ((projectile-project-compilation-cmd . "just build")
+ (projectile-project-run-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86_64.llvm --path project")
+ (projectile-project-configure-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86_64.llvm --path project -e")
+ (projectile-project-test-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86.llvm --path project"))))
diff --git a/.gitignore b/.gitignore
index 3f82628a..b18b60ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,16 +5,18 @@
# When configure fails, SCons outputs these
config.log
.sconf_temp
-.config
-# build artefacts
-.cache/
+# build artifacts
*.o
compile_commands.json
-
engine/.github
project/.godot
build/authority.pck
build/authority.x86_64
build/authority.exe
build.zip
+
+# general-purpose cache folder (used by e.g clangd)
+.cache
+
+__pycache__
diff --git a/assets/textures/props/grass_a.kra b/assets/textures/props/grass_a.kra
new file mode 100644
index 00000000..5dd88a9d
Binary files /dev/null and b/assets/textures/props/grass_a.kra differ
diff --git a/assets/textures/props/grass_a.kra~ b/assets/textures/props/grass_a.kra~
new file mode 100644
index 00000000..a34efc1c
Binary files /dev/null and b/assets/textures/props/grass_a.kra~ differ
diff --git a/design/monarchist_camp.svg b/design/monarchist_camp.svg
deleted file mode 100644
index 23a1e008..00000000
--- a/design/monarchist_camp.svg
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
diff --git a/engine b/engine
index e531f3eb..6b76a5a8 160000
--- a/engine
+++ b/engine
@@ -1 +1 @@
-Subproject commit e531f3eb7b13a9adaaf5b15ac9b3aebc4c2030cd
+Subproject commit 6b76a5a8dc011723033cc8ad2ba3da345daab039
diff --git a/justfile b/justfile
index 48c61a88..db155243 100644
--- a/justfile
+++ b/justfile
@@ -37,9 +37,8 @@ release-windows: build
initialize-template projectname:
# Initializing Template {{projectname}}
sed -i -e "s/PROJECT/{{projectname}}/g" ./modules/PROJECT/register_types.h ./modules/PROJECT/register_types.cpp ./project/project.godot ./project/export_presets.cfg .gitignore
- sed "s/change_me/{{projectname}}/" ./justfile
+ sed -i -e "s/authority/{{projectname}}/" ./justfile
mv ./modules/PROJECT ./modules/{{projectname}}
- # Done Initializing, you will still have to update BUILD_NAME in your justfile
format:
# Formatting Custom Modules
diff --git a/modules/authority/__pycache__/config.cpython-313.pyc b/modules/authority/__pycache__/config.cpython-313.pyc
deleted file mode 100644
index 8d3597ed..00000000
Binary files a/modules/authority/__pycache__/config.cpython-313.pyc and /dev/null differ
diff --git a/modules/authority/actor_body.cpp b/modules/authority/actor_body.cpp
deleted file mode 100644
index 1f995409..00000000
--- a/modules/authority/actor_body.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include "actor_body.h"
-#include "core/config/engine.h"
-#include "core/object/object.h"
-#include "macros.h"
-
-void ActorBody::_bind_methods() {
- BIND_HPROPERTY(Variant::FLOAT, movement_speed, PROPERTY_HINT_RANGE, "0.0,20.0");
-}
-
-void ActorBody::physics_process(double delta) {
- if (this->teleport_on_process) {
- set_global_position(this->teleport_target);
- this->teleport_on_process = false;
- force_update_transform();
- }
- set_velocity(get_movement_direction() * this->movement_speed);
- move_and_slide();
-}
-
-void ActorBody::_notification(int what) {
- if (Engine::get_singleton()->is_editor_hint()) {
- return;
- }
- switch (what) {
- default:
- return;
- case NOTIFICATION_READY:
- set_physics_process(true);
- set_as_top_level(true);
- case NOTIFICATION_PHYSICS_PROCESS:
- physics_process(get_physics_process_delta_time());
- return;
- }
-}
-
-void ActorBody::set_movement_direction(Vector3 direction) {
- this->mode = Direction;
- this->movement_vector = Vector3(direction.x, 0.f, direction.z).normalized();
-}
-
-Vector3 ActorBody::get_movement_direction() const {
- switch (this->mode) {
- case Position: {
- Vector3 const direction_3d{ get_global_position().direction_to(get_movement_target()) };
- return Vector3{ direction_3d.x, 0.f, direction_3d.z };
- } break;
- case Direction:
- return this->movement_vector;
- }
-}
-
-void ActorBody::set_movement_target(Vector3 location) {
- this->mode = Position;
- this->movement_vector = { location.x, 0.f, location.z };
-}
-
-Vector3 ActorBody::get_movement_target() const {
- switch (this->mode) {
- case Position:
- return this->movement_vector;
- case Direction:
- return get_global_position() + this->movement_vector;
- }
-}
-
-void ActorBody::teleport(Vector3 target) {
- this->teleport_target = target;
- this->teleport_on_process = true;
-}
-
-void ActorBody::set_movement_speed(float speed) {
- this->movement_speed = speed;
-}
-
-float ActorBody::get_movement_speed() const {
- return this->movement_speed;
-}
-
-ActorBody::MovementMode ActorBody::get_movement_mode() const {
- return this->mode;
-}
diff --git a/modules/authority/actor_body.h b/modules/authority/actor_body.h
deleted file mode 100644
index fa7fbaac..00000000
--- a/modules/authority/actor_body.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#ifndef ACTOR_BODY_H
-#define ACTOR_BODY_H
-
-#include "scene/3d/physics/character_body_3d.h"
-
-class ActorBody : public CharacterBody3D {
- GDCLASS(ActorBody, CharacterBody3D);
- static void _bind_methods();
- void physics_process(double delta);
-
-public:
- // support both directional and positional movement modes
- enum MovementMode {
- Direction,
- Position
- };
- void _notification(int what);
-
- void set_movement_direction(Vector3 direction);
- Vector3 get_movement_direction() const;
- void set_movement_target(Vector3 location);
- Vector3 get_movement_target() const;
- void teleport(Vector3 target);
-
- void set_movement_speed(float speed);
- float get_movement_speed() const;
- MovementMode get_movement_mode() const;
-
-private:
- float movement_speed{ 3.f };
- Vector3 movement_vector{ 0.f, 0.f, 0.f };
- MovementMode mode{ Direction };
- bool teleport_on_process{ false };
- Vector3 teleport_target{};
-};
-
-#endif //! ACTOR_BODY_H
diff --git a/modules/authority/character.cpp b/modules/authority/character.cpp
new file mode 100644
index 00000000..96d58776
--- /dev/null
+++ b/modules/authority/character.cpp
@@ -0,0 +1,155 @@
+#include "character.h"
+#include "core/config/engine.h"
+#include "macros.h"
+
+void CharacterData::_bind_methods() {
+ BIND_PROPERTY(Variant::FLOAT, speed);
+}
+
+void Character::_bind_methods() {
+ BIND_HPROPERTY(Variant::OBJECT, data, PROPERTY_HINT_RESOURCE_TYPE, "CharacterData");
+}
+
+void Character::physics_process(double delta) {
+ Vector3 const velocity{ get_velocity() };
+ Vector3 new_velocity{ velocity };
+ new_velocity.x = this->world_movement_direction.x;
+ new_velocity.z = this->world_movement_direction.y;
+ set_velocity(new_velocity);
+ if (!velocity.is_zero_approx()) {
+ move_and_slide();
+ }
+}
+
+void Character::_notification(int what) {
+ if (Engine::get_singleton()->is_editor_hint() || !this->data.is_valid()) {
+ return;
+ }
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_READY:
+ set_physics_process(true);
+ return;
+ case NOTIFICATION_PHYSICS_PROCESS:
+ physics_process(get_physics_process_delta_time());
+ return;
+ }
+}
+
+PackedStringArray Character::get_configuration_warnings() const {
+ PackedStringArray warnings{ super_type::get_configuration_warnings() };
+ if (this->data.is_null()) {
+ warnings.push_back("Character requires 'data' to be initialised. To avoid crashes consider adding a placeholder if you intend to programmatically initialise it.");
+ }
+ return warnings;
+}
+
+void Character::set_movement(Vector2 movement) {
+ this->world_movement_direction = movement;
+}
+
+bool Character::is_moving() const {
+ return !this->world_movement_direction.is_zero_approx();
+}
+
+void CharacterState::_bind_methods() {
+ BIND_PROPERTY(Variant::BOOL, start_active);
+}
+
+void CharacterState::_notification(int what) {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_ENTER_TREE:
+ this->character = cast_to(get_parent());
+ ERR_FAIL_COND_EDMSG(this->character == nullptr, "CharacterState requires parent to be of type Character");
+ return;
+ case NOTIFICATION_READY:
+ if (start_active) {
+ callable_mp(this, &self_type::set_state_active).call_deferred(true);
+ }
+ return;
+ }
+}
+
+PackedStringArray CharacterState::get_configuration_warnings() const {
+ PackedStringArray warnings{ super_type::get_configuration_warnings() };
+ if (cast_to(get_parent()) == nullptr) {
+ warnings.push_back("CharacterState requires direct Character parent");
+ }
+ return warnings;
+}
+
+void CharacterState::switch_to_state(String value) {
+ if (!this->state_active) {
+ print_error(vformat("Attempt to switch from inactive state %s to new state %s", get_path(), value));
+ return;
+ }
+ set_state_active(false);
+ stack_state_independent(value);
+}
+
+void CharacterState::stack_state_dependent(String value) {
+ if (!this->state_active) {
+ print_error(vformat("Attempt to stack dependent state %s from inactive state %s", value, get_path()));
+ return;
+ }
+ Node *node{ get_parent()->get_node(value) };
+ if (CharacterState * state{ cast_to(node) }) {
+ state->depending_state = this;
+ this->dependent_states.insert(state);
+ state->set_state_active(true);
+ }
+}
+
+void CharacterState::notify_dependent_inactive(CharacterState *state) {
+ if (!this->state_active) {
+ print_error(vformat("Received notification that dependent state %s became inactive, while depending state %s was inactive", state->get_path(), get_path()));
+ return;
+ }
+ this->dependent_states.erase(state);
+}
+
+void CharacterState::stack_state_independent(String value) {
+ if (!this->state_active) {
+ print_error(vformat("Attempt to stack state %s from inactive state %s", value, get_path()));
+ return;
+ }
+ Node *node{ get_parent()->get_node(value) };
+ if (CharacterState * state{ cast_to(node) }) {
+ state->set_state_active(true);
+ } else {
+ print_error(vformat("Attempt to stack nonexistent state %s from %s", value, get_path()));
+ }
+}
+
+void CharacterState::set_state_active(bool active) {
+ if (this->state_active != active) {
+ this->state_active = active;
+ if (active) {
+ state_entered();
+ } else {
+ state_exited();
+ if (this->depending_state && this->depending_state->state_active) {
+ this->depending_state->notify_dependent_inactive(this);
+ }
+ this->depending_state = nullptr;
+ for (CharacterState *state : this->dependent_states) {
+ state->set_state_active(false);
+ }
+ this->dependent_states.clear();
+ }
+ }
+}
+
+bool CharacterState::get_state_active() const {
+ return this->state_active;
+}
+
+Character *CharacterState::get_character() const {
+ return this->character;
+}
diff --git a/modules/authority/character.h b/modules/authority/character.h
new file mode 100644
index 00000000..5112665e
--- /dev/null
+++ b/modules/authority/character.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "core/io/resource.h"
+#include "core/templates/hash_set.h"
+#include "macros.h"
+#include "scene/3d/physics/character_body_3d.h"
+
+class CharacterData : public Resource {
+ GDCLASS(CharacterData, Resource);
+ static void _bind_methods();
+
+private:
+ float speed{};
+
+public:
+ GET_SET_FNS(float, speed);
+};
+
+class Character : public CharacterBody3D {
+ GDCLASS(Character, CharacterBody3D);
+
+protected:
+ static void _bind_methods();
+ void physics_process(double delta);
+ void _notification(int what);
+ PackedStringArray get_configuration_warnings() const override;
+
+public:
+ void set_movement(Vector2 movement);
+ bool is_moving() const;
+
+private:
+ Ref data{};
+ Vector2 world_movement_direction{};
+
+public:
+ GET_SET_FNS(Ref, data);
+};
+
+class CharacterState : public Node {
+ GDCLASS(CharacterState, Node);
+ static void _bind_methods();
+
+protected:
+ void _notification(int what);
+ PackedStringArray get_configuration_warnings() const override;
+ void switch_to_state(String state);
+ void stack_state_dependent(String state);
+ void notify_dependent_inactive(CharacterState *dependent);
+ void stack_state_independent(String state);
+ virtual void state_entered() {}
+ virtual void state_exited() {}
+
+public:
+ void set_state_active(bool active);
+ bool get_state_active() const;
+ Character *get_character() const;
+
+private:
+ bool start_active{ false };
+ bool state_active{ false };
+
+ Character *character{ nullptr };
+ HashSet dependent_states{};
+ CharacterState *depending_state{ nullptr };
+
+public:
+ GET_SET_FNS(bool, start_active);
+};
diff --git a/modules/authority/game_state.cpp b/modules/authority/game_state.cpp
deleted file mode 100644
index d10da4fd..00000000
--- a/modules/authority/game_state.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "game_state.h"
-#include "macros.h"
-
-GameState *GameState::singleton_instance{ nullptr };
-
-void GameState::_bind_methods() {}
-
-GameState::GameState() {
- self_type::singleton_instance = this;
-}
-
-GameState::~GameState() {
- self_type::singleton_instance = nullptr;
-}
-
-GameState *GameState::get_singleton() {
- return self_type::singleton_instance;
-}
diff --git a/modules/authority/game_state.h b/modules/authority/game_state.h
deleted file mode 100644
index 0170bd94..00000000
--- a/modules/authority/game_state.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef GAME_STATE_H
-#define GAME_STATE_H
-
-#include "core/object/class_db.h"
-#include "core/object/object.h"
-#include "core/templates/vector.h"
-
-class GameState : public Object {
- GDCLASS(GameState, Object);
- static void _bind_methods();
- static GameState *singleton_instance;
-
-public:
- GameState();
- virtual ~GameState();
- static GameState *get_singleton();
-};
-
-#endif // !GAME_STATE_H
diff --git a/modules/authority/nav_marker.cpp b/modules/authority/nav_marker.cpp
new file mode 100644
index 00000000..12690b04
--- /dev/null
+++ b/modules/authority/nav_marker.cpp
@@ -0,0 +1,13 @@
+#include "nav_marker.h"
+
+void NavMarker::_bind_methods() {}
+
+void NavMarker::_notification(int what) {
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_ENTER_TREE:
+ this->set_gizmo_extents(3);
+ return;
+ }
+}
diff --git a/modules/authority/nav_marker.h b/modules/authority/nav_marker.h
new file mode 100644
index 00000000..f8b26120
--- /dev/null
+++ b/modules/authority/nav_marker.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "macros.h"
+#include "scene/3d/marker_3d.h"
+class Character;
+
+class NavMarker : public Marker3D {
+ GDCLASS(NavMarker, Marker3D);
+ static void _bind_methods();
+
+protected:
+ void _notification(int what);
+
+private:
+ Character *claimed{ nullptr };
+
+public:
+ GET_SET_FNS(Character *, claimed);
+};
diff --git a/modules/authority/party_member_states.cpp b/modules/authority/party_member_states.cpp
new file mode 100644
index 00000000..99774610
--- /dev/null
+++ b/modules/authority/party_member_states.cpp
@@ -0,0 +1,84 @@
+#include "party_member_states.h"
+#include "authority/nav_marker.h"
+#include "authority/player_character.h"
+#include "core/config/engine.h"
+#include "core/error/error_macros.h"
+#include "core/templates/vector.h"
+
+void PartyMemberFollow::_bind_methods() {}
+
+void PartyMemberFollow::process_position_target() {
+ Vector3 const marker_position{ this->claimed_marker->get_global_position() };
+ Vector3 const nav_target{ this->nav->get_target_position() };
+ Vector3 const global_position{ get_character()->get_global_position() };
+ if (global_position.distance_squared_to(marker_position) < 0.5) {
+ return;
+ }
+ if (nav_target.distance_squared_to(marker_position) > 0.25) {
+ this->nav->set_target_position(marker_position);
+ }
+ if (this->nav->is_navigation_finished()) {
+ return;
+ }
+ Vector3 velocity{ global_position.direction_to(this->nav->get_next_path_position()) };
+ velocity.y = 0;
+ if (this->nav->get_avoidance_enabled()) {
+ this->nav->set_velocity(velocity * get_character()->get_data()->get_speed());
+ } else {
+ push_movement_direction(velocity * get_character()->get_data()->get_speed());
+ }
+}
+
+void PartyMemberFollow::push_movement_direction(Vector3 velocity) {
+ get_character()->set_movement(Vector2{ velocity.x, velocity.z });
+}
+
+void PartyMemberFollow::_notification(int what) {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_READY:
+ this->nav = cast_to(get_parent()->get_node(NodePath("%NavigationAgent3D")));
+ ERR_FAIL_COND_EDMSG(this->nav == nullptr, "PartyMemberFollow cannot initialise without a navigation agent");
+ return;
+ case NOTIFICATION_PROCESS:
+ process_position_target();
+ return;
+ }
+}
+
+PackedStringArray PartyMemberFollow::get_configuration_warnings() const {
+ PackedStringArray warnings{ super_type::get_configuration_warnings() };
+ if (!get_parent()->has_node(NodePath("%NavigationAgent3D")) || !cast_to(get_parent()->get_node(NodePath("%NavigationAgent3D")))) {
+ warnings.push_back("PartyMemberFollow expects a scene sibling of type NavigationAgent3D named with unique name '%NavigationAgent3D'");
+ }
+ return warnings;
+}
+
+void PartyMemberFollow::state_entered() {
+ Vector const &markers{ PlayerCharacter::get_singleton()->get_party_follow_markers() };
+ for (NavMarker *marker : markers) {
+ if (marker->get_claimed() == nullptr) {
+ marker->set_claimed(get_character());
+ this->claimed_marker = marker;
+ if (this->nav->get_avoidance_enabled()) {
+ this->nav->connect("velocity_computed", callable_mp(this, &self_type::push_movement_direction));
+ }
+ set_process(true);
+ return;
+ }
+ }
+ ERR_FAIL_EDMSG("PartyMemberFollow could not find an unclaimed player follow marker");
+ set_state_active(false);
+}
+
+void PartyMemberFollow::state_exited() {
+ if (this->claimed_marker) {
+ this->claimed_marker->set_claimed(nullptr);
+ this->nav->disconnect("velocity_computed", callable_mp(this, &self_type::push_movement_direction));
+ set_process(false);
+ }
+}
diff --git a/modules/authority/party_member_states.h b/modules/authority/party_member_states.h
new file mode 100644
index 00000000..85faa988
--- /dev/null
+++ b/modules/authority/party_member_states.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "authority/character.h"
+#include "authority/nav_marker.h"
+#include "scene/3d/navigation/navigation_agent_3d.h"
+
+class PartyMemberFollow : public CharacterState {
+ GDCLASS(PartyMemberFollow, CharacterState);
+ static void _bind_methods();
+ void process_position_target();
+ void push_movement_direction(Vector3 velocity);
+
+protected:
+ void _notification(int what);
+ PackedStringArray get_configuration_warnings() const override;
+ void state_entered() override;
+ void state_exited() override;
+
+private:
+ NavigationAgent3D *nav{ nullptr };
+ NavMarker *claimed_marker{ nullptr };
+};
diff --git a/modules/authority/player_actor.cpp b/modules/authority/player_actor.cpp
deleted file mode 100644
index 0401ec04..00000000
--- a/modules/authority/player_actor.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-#include "player_actor.h"
-#include "authority/game_state.h"
-#include "authority/player_input.h"
-#include "scene/main/viewport.h"
-
-void PlayerActor::_bind_methods() {
-}
-
-void PlayerActor::ready() {
- PlayerInput *input{ cast_to(get_node(NodePath("%PlayerInput"))) };
- input->connect(PlayerInput::signal_movement, callable_mp(this, &self_type::input_move));
- set_process(true);
-}
-
-void PlayerActor::process(double delta) {
- Basis const basis{ this->get_viewport()->get_camera_3d()->get_global_basis() };
- Vector3 x{ basis.get_column(0) };
- x = Vector3{ x.x, 0.f, x.z }.normalized();
- Vector3 z{ basis.get_column(2) };
- z = Vector3{ z.x, 0.f, z.z }.normalized();
- set_movement_direction(x * this->move_input.x + z * this->move_input.y);
-}
-
-void PlayerActor::input_move(Vector2 movement) {
- this->move_input = movement;
-}
-
-void PlayerActor::_notification(int what) {
- if (Engine::get_singleton()->is_editor_hint()) {
- return;
- }
- switch (what) {
- default:
- return;
- case NOTIFICATION_READY:
- ready();
- return;
- case NOTIFICATION_PROCESS:
- process(get_process_delta_time());
- return;
- }
-}
diff --git a/modules/authority/player_actor.h b/modules/authority/player_actor.h
deleted file mode 100644
index 6cdcdc59..00000000
--- a/modules/authority/player_actor.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#ifndef PLAYER_ACTOR_H
-#define PLAYER_ACTOR_H
-
-#include "actor_body.h"
-
-class PlayerActor : public ActorBody {
- GDCLASS(PlayerActor, ActorBody);
- static void _bind_methods();
- void ready();
- void process(double delta);
- void input_move(Vector2 movement);
-
-public:
- void _notification(int what);
-
-private:
- Vector2 move_input{ 0.f, 0.f };
-};
-
-#endif // !PLAYER_ACTOR_H
diff --git a/modules/authority/player_camera.h b/modules/authority/player_camera.h
new file mode 100644
index 00000000..a16c13a6
--- /dev/null
+++ b/modules/authority/player_camera.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "scene/3d/camera_3d.h"
+
+class PlayerCamera : public Camera3D {
+ GDCLASS(PlayerCamera, Camera3D);
+ static void _bind_methods();
+};
diff --git a/modules/authority/player_character.cpp b/modules/authority/player_character.cpp
new file mode 100644
index 00000000..34610c5d
--- /dev/null
+++ b/modules/authority/player_character.cpp
@@ -0,0 +1,36 @@
+#include "player_character.h"
+#include "authority/nav_marker.h"
+
+void PlayerCharacter::_bind_methods() {}
+
+void PlayerCharacter::_notification(int what) {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_ENTER_TREE:
+ instance = this;
+ return;
+ case NOTIFICATION_READY:
+ for (Variant var : find_children("*", "NavMarker")) {
+ if (NavMarker * marker{ cast_to(var) }) {
+ this->party_follow_markers.push_back(marker);
+ }
+ }
+ ERR_FAIL_COND_EDMSG(this->party_follow_markers.size() < 4, "PlayerCharacter should have at least 4 follow NavMarkers for party members");
+ return;
+ case NOTIFICATION_EXIT_TREE:
+ if (instance == this) {
+ instance = nullptr;
+ }
+ return;
+ }
+}
+
+PlayerCharacter *PlayerCharacter::instance{ nullptr };
+
+PlayerCharacter *PlayerCharacter::get_singleton() {
+ return instance;
+}
diff --git a/modules/authority/player_character.h b/modules/authority/player_character.h
new file mode 100644
index 00000000..4b74dc4a
--- /dev/null
+++ b/modules/authority/player_character.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "authority/character.h"
+#include "authority/nav_marker.h"
+#include "macros.h"
+
+class PlayerCharacter : public Character {
+ GDCLASS(PlayerCharacter, Character);
+ static void _bind_methods();
+
+protected:
+ void _notification(int what);
+
+private:
+ Vector party_follow_markers{};
+ static PlayerCharacter *instance;
+
+public:
+ static PlayerCharacter *get_singleton();
+ GET_SET_FNS(Vector const &, party_follow_markers);
+};
diff --git a/modules/authority/player_input.cpp b/modules/authority/player_input.cpp
deleted file mode 100644
index bfd00590..00000000
--- a/modules/authority/player_input.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-#include "player_input.h"
-
-#include "core/input/input_event.h"
-#include "macros.h"
-
-String const PlayerInput::signal_movement{ "movement" };
-String const PlayerInput::signal_look{ "look" };
-String const PlayerInput::signal_mouselook{ "mouselook" };
-
-void PlayerInput::_bind_methods() {
- BIND_PROPERTY(Variant::STRING, action_move_left);
- BIND_PROPERTY(Variant::STRING, action_move_right);
- BIND_PROPERTY(Variant::STRING, action_move_forward);
- BIND_PROPERTY(Variant::STRING, action_move_back);
- BIND_PROPERTY(Variant::STRING, action_look_left);
- BIND_PROPERTY(Variant::STRING, action_look_right);
- BIND_PROPERTY(Variant::STRING, action_look_up);
- BIND_PROPERTY(Variant::STRING, action_look_down);
- ADD_SIGNAL(MethodInfo(self_type::signal_movement, PropertyInfo(Variant::VECTOR2, "movement_directions")));
- ADD_SIGNAL(MethodInfo(self_type::signal_look, PropertyInfo(Variant::VECTOR2, "look_delta")));
- ADD_SIGNAL(MethodInfo(self_type::signal_mouselook, PropertyInfo(Variant::VECTOR2, "mouse_delta")));
-}
-
-void PlayerInput::_notification(int what) {
- if (Engine::get_singleton()->is_editor_hint()) {
- return;
- }
- switch (what) {
- default:
- return;
- case NOTIFICATION_READY:
- set_process_unhandled_input(true);
- Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
- return;
- }
-}
-
-void PlayerInput::unhandled_input(Ref const &event) {
- Input *input{ Input::get_singleton() };
-
- bool is_movement_action{ event->is_action(self_type::action_move_left) };
- is_movement_action |= event->is_action(self_type::action_move_right);
- is_movement_action |= event->is_action(self_type::action_move_forward);
- is_movement_action |= event->is_action(self_type::action_move_back);
- if (is_movement_action) {
- float const h_axis{ input->get_axis(self_type::action_move_left, self_type::action_move_right) };
- float const v_axis{ input->get_axis(self_type::action_move_forward, self_type::action_move_back) };
- emit_signal(self_type::signal_movement, Vector2{ h_axis, v_axis });
- }
-
- bool is_look_action{ event->is_action(self_type::action_look_left) };
- is_look_action |= event->is_action(self_type::action_look_right);
- is_look_action |= event->is_action(self_type::action_look_up);
- is_look_action |= event->is_action(self_type::action_look_down);
- if (is_look_action) {
- float const h_axis{ input->get_axis(self_type::action_look_right, self_type::action_look_left) };
- float const v_axis{ input->get_axis(self_type::action_look_down, self_type::action_look_up) };
- emit_signal(self_type::signal_look, Vector2{ h_axis, v_axis } * this->look_speed);
- }
-
- Ref mouse_motion_action{ event };
- if (mouse_motion_action.is_valid()) {
- Vector2 const velocity{ mouse_motion_action->get_relative() };
- emit_signal(self_type::signal_mouselook, velocity * this->mouselook_speed);
- }
-}
-
-void PlayerInput::set_mouselook_speed(Vector2 speed) {
- this->mouselook_speed = speed;
-}
-
-Vector2 PlayerInput::get_mouselook_speed() const {
- return this->mouselook_speed;
-}
-
-void PlayerInput::set_look_speed(Vector2 speed) {
- this->look_speed = speed;
-}
-
-Vector2 PlayerInput::get_look_speed() const {
- return this->look_speed;
-}
-
-void PlayerInput::set_action_move_left(String action) {
- this->action_move_left = action;
-}
-
-String PlayerInput::get_action_move_left() const {
- return this->action_move_left;
-}
-
-void PlayerInput::set_action_move_right(String action) {
- this->action_move_right = action;
-}
-
-String PlayerInput::get_action_move_right() const {
- return this->action_move_right;
-}
-
-void PlayerInput::set_action_move_forward(String action) {
- this->action_move_forward = action;
-}
-
-String PlayerInput::get_action_move_forward() const {
- return this->action_move_forward;
-}
-
-void PlayerInput::set_action_move_back(String action) {
- this->action_move_back = action;
-}
-
-String PlayerInput::get_action_move_back() const {
- return this->action_move_back;
-}
-
-void PlayerInput::set_action_look_left(String action) {
- this->action_look_left = action;
-}
-
-String PlayerInput::get_action_look_left() const {
- return this->action_look_left;
-}
-
-void PlayerInput::set_action_look_right(String action) {
- this->action_look_right = action;
-}
-
-String PlayerInput::get_action_look_right() const {
- return this->action_look_right;
-}
-
-void PlayerInput::set_action_look_up(String action) {
- this->action_look_up = action;
-}
-
-String PlayerInput::get_action_look_up() const {
- return this->action_look_up;
-}
-
-void PlayerInput::set_action_look_down(String action) {
- this->action_look_down = action;
-}
-
-String PlayerInput::get_action_look_down() const {
- return this->action_look_down;
-}
diff --git a/modules/authority/player_input.h b/modules/authority/player_input.h
deleted file mode 100644
index 977dc2a1..00000000
--- a/modules/authority/player_input.h
+++ /dev/null
@@ -1,58 +0,0 @@
-#ifndef PLAYER_INPUT_H
-#define PLAYER_INPUT_H
-
-#include "core/input/input_event.h"
-#include "scene/main/node.h"
-
-class PlayerInput : public Node {
- GDCLASS(PlayerInput, Node);
- static void _bind_methods();
-
-public:
- void _notification(int what);
- virtual void unhandled_input(Ref const &event) override;
-
- void set_mouselook_speed(Vector2 speed);
- Vector2 get_mouselook_speed() const;
- void set_look_speed(Vector2 speed);
- Vector2 get_look_speed() const;
-
- void set_action_move_left(String action);
- String get_action_move_left() const;
- void set_action_move_right(String action);
- String get_action_move_right() const;
- void set_action_move_forward(String action);
- String get_action_move_forward() const;
- void set_action_move_back(String action);
- String get_action_move_back() const;
-
- void set_action_look_left(String action);
- String get_action_look_left() const;
- void set_action_look_right(String action);
- String get_action_look_right() const;
- void set_action_look_up(String action);
- String get_action_look_up() const;
- void set_action_look_down(String action);
- String get_action_look_down() const;
-
-public:
- static String const signal_movement;
- static String const signal_look;
- static String const signal_mouselook;
-
-private:
- Vector2 mouselook_speed{ 0.001f, 0.001f };
- Vector2 look_speed{ 1.75f, 1.75f };
-
- String action_move_left{ "move_left" };
- String action_move_right{ "move_right" };
- String action_move_forward{ "move_forward" };
- String action_move_back{ "move_back" };
-
- String action_look_left{ "look_left" };
- String action_look_right{ "look_right" };
- String action_look_up{ "look_up" };
- String action_look_down{ "look_down" };
-};
-
-#endif // !PLAYER_INPUT_H
diff --git a/modules/authority/player_states.cpp b/modules/authority/player_states.cpp
new file mode 100644
index 00000000..393730d8
--- /dev/null
+++ b/modules/authority/player_states.cpp
@@ -0,0 +1,75 @@
+#include "player_states.h"
+#include "core/input/input.h"
+#include "core/math/basis.h"
+#include "scene/3d/camera_3d.h"
+#include "scene/main/viewport.h"
+
+void PlayerInputState::_bind_methods() {}
+
+void PlayerInputState::unhandled_input(Ref const &what) {
+ bool const is_move_vertical{ what->is_action("move_forward") || what->is_action("move_backward") };
+ bool const is_move_horizontal{ what->is_action("move_right") || what->is_action("move_left") };
+ if (is_move_vertical || is_move_horizontal) {
+ stack_state_dependent("PlayerMovementState");
+ get_viewport()->set_input_as_handled();
+ }
+}
+
+void PlayerInputState::state_entered() {
+ set_process_unhandled_input(true);
+}
+
+void PlayerInputState::state_exited() {
+ set_process_unhandled_input(false);
+}
+
+void PlayerMovementState::_bind_methods() {}
+
+void PlayerMovementState::process(double delta) {
+ Basis const basis{ get_viewport()->get_camera_3d()->get_global_basis() };
+ Vector2 backward{ basis.get_column(0).x, basis.get_column(0).z };
+ if (backward.is_zero_approx()) {
+ backward = Vector2{ basis.get_column(1).x, basis.get_column(1).z };
+ }
+ Vector2 const right{ basis.get_column(2).x, basis.get_column(2).z };
+ get_character()->set_movement((backward.normalized() * this->movement.x + right.normalized() * this->movement.y) * get_character()->get_data()->get_speed());
+}
+
+void PlayerMovementState::_notification(int what) {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_READY:
+ set_process_input(true);
+ return;
+ case NOTIFICATION_PROCESS:
+ process(get_process_delta_time());
+ return;
+ }
+}
+
+void PlayerMovementState::input(Ref const &what) {
+ bool const is_move_vertical{ what->is_action("move_forward") || what->is_action("move_backward") };
+ bool const is_move_horizontal{ what->is_action("move_right") || what->is_action("move_left") };
+ if (is_move_vertical || is_move_horizontal) {
+ this->movement = {
+ Input::get_singleton()->get_axis("move_left", "move_right"),
+ Input::get_singleton()->get_axis("move_forward", "move_backward")
+ };
+ this->movement.normalize();
+ }
+ if (this->get_state_active() && this->movement.is_zero_approx()) {
+ set_state_active(false);
+ }
+}
+
+void PlayerMovementState::state_entered() {
+ set_process(true);
+}
+
+void PlayerMovementState::state_exited() {
+ set_process(false);
+}
diff --git a/modules/authority/player_states.h b/modules/authority/player_states.h
new file mode 100644
index 00000000..733725be
--- /dev/null
+++ b/modules/authority/player_states.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "authority/character.h"
+
+class PlayerInputState : public CharacterState {
+ GDCLASS(PlayerInputState, CharacterState);
+ static void _bind_methods();
+
+protected:
+ void unhandled_input(Ref const &event) override;
+ void state_entered() override;
+ void state_exited() override;
+};
+
+class PlayerMovementState : public CharacterState {
+ GDCLASS(PlayerMovementState, CharacterState);
+ static void _bind_methods();
+ void ready();
+ void process(double delta);
+
+protected:
+ void _notification(int what);
+ void input(Ref const &event) override;
+ void state_entered() override;
+ void state_exited() override;
+
+private:
+ Vector2 movement{};
+};
diff --git a/modules/authority/register_types.cpp b/modules/authority/register_types.cpp
index 8d674102..d2ad6d36 100644
--- a/modules/authority/register_types.cpp
+++ b/modules/authority/register_types.cpp
@@ -1,34 +1,28 @@
#include "register_types.h"
-#include "authority/actor_body.h"
-#include "authority/game_state.h"
-#include "authority/player_actor.h"
-#include "authority/player_input.h"
-#include "authority/third_person_camera.h"
-#include "core/config/engine.h"
+#include "authority/character.h"
+#include "authority/nav_marker.h"
+#include "authority/party_member_states.h"
+#include "authority/player_character.h"
+#include "authority/player_states.h"
#include "core/object/class_db.h"
-GameState *game_state{ nullptr };
-
void initialize_authority_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
- GDREGISTER_CLASS(GameState);
- GDREGISTER_CLASS(ActorBody);
- GDREGISTER_CLASS(PlayerActor);
- GDREGISTER_CLASS(PlayerInput);
- GDREGISTER_CLASS(ThirdPersonCamera);
-
- game_state = memnew(GameState);
- Engine::get_singleton()->add_singleton(Engine::Singleton("GameState", GameState::get_singleton()));
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
}
void uninitialize_authority_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
- if (game_state) {
- memdelete(game_state);
- }
}
diff --git a/modules/authority/third_person_camera.cpp b/modules/authority/third_person_camera.cpp
deleted file mode 100644
index 04077cc6..00000000
--- a/modules/authority/third_person_camera.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-#include "third_person_camera.h"
-#include "player_input.h"
-
-void ThirdPersonCamera::_bind_methods() {
-}
-
-void ThirdPersonCamera::ready() {
- set_process(true);
- if (PlayerInput * player_input{ cast_to(get_node(NodePath("%PlayerInput"))) }) {
- player_input->connect(PlayerInput::signal_look, callable_mp(this, &self_type::on_look_input));
- player_input->connect(PlayerInput::signal_mouselook, callable_mp(this, &self_type::on_mouselook_input));
- }
-}
-
-void ThirdPersonCamera::process(double delta) {
- this->look_rotation += look_rotation_motion * delta;
- this->look_rotation.y = CLAMP(this->look_rotation.y, -Math_PI / 2.5, Math_PI / 2.5);
- Vector3 pivot{ get_parent_node_3d()->get_global_position() };
- pivot.y += this->height;
- Vector3 offset{ 0.f, 0.f, this->distance };
- offset.rotate({ 1.f, 0.f, 0.f }, this->look_rotation.y);
- offset.rotate({ 0.f, 1.f, 0.f }, this->look_rotation.x);
- look_at_from_position(pivot + offset, pivot);
-}
-
-void ThirdPersonCamera::on_look_input(Vector2 input) {
- this->look_rotation_motion = input;
-}
-
-void ThirdPersonCamera::on_mouselook_input(Vector2 input) {
- this->look_rotation -= input;
-}
-
-void ThirdPersonCamera::_notification(int what) {
- if (Engine::get_singleton()->is_editor_hint()) {
- return;
- }
- switch (what) {
- case NOTIFICATION_READY:
- ready();
- break;
- case NOTIFICATION_PROCESS:
- process(get_process_delta_time());
- break;
- }
-}
-
-void ThirdPersonCamera::set_base_rotation_speed(Vector2 value) {
- this->base_rotation_speed = value;
-}
-
-Vector2 ThirdPersonCamera::get_base_rotation_speed() const {
- return this->base_rotation_speed;
-}
diff --git a/modules/authority/third_person_camera.h b/modules/authority/third_person_camera.h
deleted file mode 100644
index 9ec2935d..00000000
--- a/modules/authority/third_person_camera.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef THIRD_PERSON_CAMERA_H
-#define THIRD_PERSON_CAMERA_H
-
-#include "scene/3d/camera_3d.h"
-
-class ThirdPersonCamera : public Camera3D {
- GDCLASS(ThirdPersonCamera, Camera3D);
- static void _bind_methods();
- void ready();
- void process(double delta);
- void on_look_input(Vector2 input);
- void on_mouselook_input(Vector2 input);
-
-public:
- void _notification(int what);
-
- void set_base_rotation_speed(Vector2 value);
- Vector2 get_base_rotation_speed() const;
-
-private:
- Vector2 look_rotation_motion{ 0.f, 0.f };
- Vector2 look_rotation{ 0.f, 0.f };
-
- Vector2 base_rotation_speed{ 0.1f, 0.1f };
- float distance{ 3.f };
- float height{ 2.2f };
-};
-
-#endif // !THIRD_PERSON_CAMERA_H
diff --git a/modules/authority/macros.h b/modules/macros.h
similarity index 51%
rename from modules/authority/macros.h
rename to modules/macros.h
index 53be3185..c139f1f0 100644
--- a/modules/authority/macros.h
+++ b/modules/macros.h
@@ -17,4 +17,29 @@
ADD_PROPERTY(PropertyInfo(m_type, #m_property), "set_" #m_property, \
"get_" #m_property)
+#define GET_SET_FNS(m_type, m_property) \
+ m_type get_##m_property() const { \
+ return this->m_property; \
+ } \
+ void set_##m_property(m_type value) { \
+ this->m_property = value; \
+ }
+
+#define GET_SET_FNS_EX(m_type, m_property, m_ex) \
+ m_type get_##m_property() const { \
+ return this->m_property; \
+ } \
+ void set_##m_property(m_type value) { \
+ m_ex; \
+ this->m_property = value; \
+ }
+
+#define __VA_ARGS__STRING(...) String(#__VA_ARGS__)
+
+#define GDENUM(M_Name, ...) \
+ enum M_Name { __VA_ARGS__ }; \
+ static String M_Name##_hint() { \
+ return __VA_ARGS__STRING(__VA_ARGS__); \
+ }
+
#endif // !GODOT_EXTRA_MACROS_H
diff --git a/modules/terrain/SCsub b/modules/terrain/SCsub
new file mode 100644
index 00000000..2760ab7d
--- /dev/null
+++ b/modules/terrain/SCsub
@@ -0,0 +1,3 @@
+Import('env')
+
+env.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/terrain/config.py b/modules/terrain/config.py
new file mode 100644
index 00000000..58c88bf1
--- /dev/null
+++ b/modules/terrain/config.py
@@ -0,0 +1,5 @@
+def can_build(env, platform):
+ return True;
+
+def configure(env):
+ pass;
diff --git a/modules/terrain/register_types.cpp b/modules/terrain/register_types.cpp
new file mode 100644
index 00000000..deaa9de4
--- /dev/null
+++ b/modules/terrain/register_types.cpp
@@ -0,0 +1,24 @@
+#include "register_types.h"
+
+#include "core/object/class_db.h"
+#include "terrain/terrain.h"
+#include "terrain/terrain_chunk.h"
+#include "terrain/terrain_modifier.h"
+
+void initialize_terrain_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+ ClassDB::register_class();
+ ClassDB::register_abstract_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+}
+
+void uninitialize_terrain_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+}
diff --git a/modules/terrain/register_types.h b/modules/terrain/register_types.h
new file mode 100644
index 00000000..85003560
--- /dev/null
+++ b/modules/terrain/register_types.h
@@ -0,0 +1,9 @@
+#ifndef TERRAIN_REGISTER_TYPES_H
+#define TERRAIN_REGISTER_TYPES_H
+
+#include "modules/register_module_types.h"
+
+void initialize_terrain_module(ModuleInitializationLevel p_level);
+void uninitialize_terrain_module(ModuleInitializationLevel p_level);
+
+#endif // !TERRAIN_REGISTER_TYPES_H
diff --git a/modules/terrain/terrain.cpp b/modules/terrain/terrain.cpp
new file mode 100644
index 00000000..a925b6b5
--- /dev/null
+++ b/modules/terrain/terrain.cpp
@@ -0,0 +1,208 @@
+#include "terrain.h"
+#include "terrain/terrain_chunk.h"
+#include "terrain/terrain_modifier.h"
+
+void Terrain::_bind_methods() {
+ BIND_PROPERTY(Variant::INT, side_length);
+ BIND_PROPERTY(Variant::INT, chunk_size);
+ BIND_PROPERTY(Variant::INT, detail);
+ BIND_PROPERTY(Variant::INT, thread_count);
+}
+
+void Terrain::child_order_changed() {
+ this->modifiers.clear();
+ for (Variant var : get_children()) {
+ if (TerrainModifier * mod{ cast_to(var) }) {
+ mod->set_terrain(this);
+ this->modifiers.push_back(mod);
+ }
+ }
+}
+
+void Terrain::update_meshes() {
+ size_t num{ 1 };
+ this->dirty_meshes_lock.lock();
+ num = num > this->dirty_meshes.size() ? this->dirty_meshes.size() : num;
+ this->dirty_meshes_lock.unlock();
+ for (size_t i{ 0 }; i < num; i++) {
+ this->dirty_meshes_lock.lock();
+ TerrainChunkMesh *mesh{ this->dirty_meshes[0] };
+ this->dirty_meshes.remove_at(0);
+ this->dirty_meshes_lock.unlock();
+ mesh->apply_new_mesh();
+ }
+ if (this->dirty_meshes.is_empty()) {
+ set_process(false);
+ }
+}
+
+void Terrain::_notification(int what) {
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_ENTER_TREE:
+ if (!is_ready()) {
+ connect("child_order_changed", callable_mp(this, &self_type::child_order_changed));
+ }
+ return;
+ case NOTIFICATION_READY:
+ construct_chunk_grid();
+ return;
+ case NOTIFICATION_PROCESS:
+ update_meshes();
+ return;
+ case NOTIFICATION_EXIT_TREE:
+ this->workload_lock.lock();
+ this->threads_stop = true;
+ this->workload_lock.unlock();
+ for (Thread &thread : this->threads) {
+ thread.wait_to_finish();
+ }
+ return;
+ }
+}
+
+void Terrain::generate_meshes_thread(void *terrain) {
+ Terrain *self{ static_cast(terrain) };
+ print_line("thread", Thread::get_caller_id(), "start");
+ for (;;) {
+ self->workload_lock.lock();
+ if (self->threads_stop) {
+ self->workload_lock.unlock();
+ print_line(Thread::get_caller_id(), "exiting");
+ break;
+ }
+ if (self->workload.is_empty()) {
+ self->workload_lock.unlock();
+ Thread::yield();
+ continue;
+ }
+ TerrainChunkMesh *mesh{ self->workload[0] };
+ self->workload.remove_at(0);
+ self->workload_lock.unlock();
+ if (!mesh->is_inside_tree()) {
+ print_line(Thread::get_caller_id(), "mesh is outside tree, exiting");
+ break;
+ }
+ mesh->update_mesh();
+ Thread::yield();
+ }
+ print_line(Thread::get_caller_id(), "done");
+ return;
+}
+
+void Terrain::construct_chunk_grid() {
+ this->workload_lock.lock();
+ this->threads_stop = true;
+ this->workload_lock.unlock();
+ for (Thread &thread : this->threads) {
+ thread.wait_to_finish();
+ }
+ this->workload_lock.lock();
+ for (TerrainChunkMesh *mesh : this->meshes) {
+ remove_child(mesh);
+ mesh->queue_free();
+ }
+ size_t const chunks_per_side{ this->side_length / this->chunk_size };
+ Vector3 const origin{ (float)this->chunk_size / 2.f, 0.f, (float)this->chunk_size / 2.f };
+ this->meshes.clear();
+ this->workload.clear();
+ for (size_t y{ 0 }; y < chunks_per_side; ++y) {
+ for (size_t x{ 0 }; x < chunks_per_side; ++x) {
+ TerrainChunkMesh *chunk{ memnew(TerrainChunkMesh) };
+ chunk->set_size(this->chunk_size);
+ chunk->set_detail(this->detail);
+ chunk->set_terrain(this);
+ chunk->set_position(origin + Vector3{ (float)this->chunk_size * (float)x, 0.f, (float)this->chunk_size * (float)y });
+ add_child(chunk);
+ chunk->set_owner(this);
+ this->meshes.push_back(chunk);
+ this->workload.push_back(chunk);
+ }
+ }
+ this->threads_stop = false;
+ this->dirty_meshes.clear();
+ this->workload_lock.unlock();
+ for (Thread &thread : this->threads) {
+ thread.start(&self_type::generate_meshes_thread, this);
+ }
+}
+
+float Terrain::height_at(Vector2 world_coordinate) {
+ float height{ 0 };
+ for (TerrainModifier *mod : this->modifiers) {
+ if (!mod->is_inside_tree()) {
+ return height;
+ }
+ height = mod->evaluate_at(world_coordinate, height);
+ }
+ return height;
+}
+
+void Terrain::push_changed(Rect2 area) {
+ for (TerrainChunkMesh *mesh : this->meshes) {
+ this->workload_lock.lock();
+ if (area.intersects(mesh->get_bounds()) && !this->workload.has(mesh)) {
+ workload.push_back(mesh);
+ }
+ this->workload_lock.unlock();
+ }
+}
+
+void Terrain::mesh_dirty(TerrainChunkMesh *mesh) {
+ this->dirty_meshes_lock.lock();
+ this->dirty_meshes.push_back(mesh);
+ callable_mp(cast_to(this), &self_type::set_process).call_deferred(true);
+ this->dirty_meshes_lock.unlock();
+}
+
+void Terrain::set_side_length(size_t length) {
+ this->side_length = length;
+ if (is_inside_tree()) {
+ construct_chunk_grid();
+ }
+}
+
+size_t Terrain::get_side_length() const {
+ return this->side_length;
+}
+
+void Terrain::set_chunk_size(size_t size) {
+ this->chunk_size = size;
+ if (is_inside_tree()) {
+ construct_chunk_grid();
+ }
+}
+
+size_t Terrain::get_chunk_size() const {
+ return this->chunk_size;
+}
+
+void Terrain::set_detail(size_t detail) {
+ this->detail = detail;
+ if (is_inside_tree()) {
+ construct_chunk_grid();
+ }
+}
+
+size_t Terrain::get_detail() const {
+ return this->detail;
+}
+
+void Terrain::set_thread_count(size_t num) {
+ this->workload_lock.lock();
+ this->threads_stop = true;
+ this->workload_lock.unlock();
+ for (Thread &thread : this->threads) {
+ thread.wait_to_finish();
+ }
+ this->threads_stop = false;
+ this->threads.resize_initialized(num);
+ for (Thread &thread : this->threads) {
+ thread.start(&self_type::generate_meshes_thread, this);
+ }
+}
+
+size_t Terrain::get_thread_count() const {
+ return this->threads.size();
+}
diff --git a/modules/terrain/terrain.h b/modules/terrain/terrain.h
new file mode 100644
index 00000000..64177a7f
--- /dev/null
+++ b/modules/terrain/terrain.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "core/math/rect2.h"
+#include "core/os/mutex.h"
+#include "core/os/thread.h"
+#include "core/templates/vector.h"
+#include "scene/main/node.h"
+class TerrainChunkMesh;
+class TerrainModifier;
+
+class Terrain : public Node {
+ GDCLASS(Terrain, Node);
+ static void _bind_methods();
+ void child_order_changed();
+ void update_meshes();
+
+protected:
+ void _notification(int what);
+ static void generate_meshes_thread(void *terrain);
+
+public:
+ void construct_chunk_grid();
+ float height_at(Vector2 world_coordinate);
+ void push_changed(Rect2 area);
+ void mesh_dirty(TerrainChunkMesh *mesh);
+
+private:
+ Vector workload{};
+ bool threads_stop{ false };
+ Mutex workload_lock;
+
+ Vector dirty_meshes{};
+ Mutex dirty_meshes_lock{};
+
+ Vector meshes{};
+ Vector modifiers{};
+ LocalVector threads{};
+
+ size_t side_length{ 200 };
+ size_t chunk_size{ 50 };
+ size_t detail{ 1 };
+
+public:
+ void set_side_length(size_t length);
+ size_t get_side_length() const;
+ void set_chunk_size(size_t size);
+ size_t get_chunk_size() const;
+ void set_detail(size_t detail);
+ size_t get_detail() const;
+ void set_thread_count(size_t num);
+ size_t get_thread_count() const;
+};
diff --git a/modules/terrain/terrain_chunk.cpp b/modules/terrain/terrain_chunk.cpp
new file mode 100644
index 00000000..1a5777eb
--- /dev/null
+++ b/modules/terrain/terrain_chunk.cpp
@@ -0,0 +1,89 @@
+#include "terrain_chunk.h"
+#include "core/variant/variant.h"
+#include "scene/resources/surface_tool.h"
+#include "terrain/terrain.h"
+
+void TerrainChunkMesh::_bind_methods() {}
+
+void TerrainChunkMesh::generate_vertices() {
+ ERR_FAIL_COND_EDMSG(this->terrain == nullptr, "TerrainChunkMesh::generate_vertices: no terrain assigned");
+ ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate_vertices: size <= 0");
+ ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate_vertices: points per side <= 0");
+ float const half_extent{ (float)this->size / 2.f };
+ float const point_distance{ (float)this->size / ((float)points_per_side() - 1) };
+ Vector3 origin{ this->safe_position - Vector3{ half_extent, 0, half_extent } };
+ for (size_t x{ 0 }; x < points_per_side(); ++x) {
+ for (size_t y{ 0 }; y < points_per_side(); ++y) {
+ Vector2 const coordinate{ origin.x + point_distance * x, origin.z + point_distance * y };
+ this->surface->set_uv({ (float)x / (float)points_per_side(), (float)y / (float)points_per_side() });
+ this->surface->add_vertex({ coordinate.x - this->safe_position.x, this->terrain->height_at(coordinate), coordinate.y - this->safe_position.z });
+ }
+ }
+}
+
+void TerrainChunkMesh::generate_faces() {
+ LocalVector &verts{ this->surface->get_vertex_array() };
+ ERR_FAIL_COND_EDMSG(verts.size() == 0, "TerrainChunkMesh::generate_faces: no vertices in surface, call generate_vertices first");
+ size_t const faces_per_side{ points_per_side() - 1 };
+ for (size_t x{ 0 }; x < faces_per_side; ++x) {
+ for (size_t y{ 0 }; y < faces_per_side; ++y) {
+ size_t const tl{ x + y * points_per_side() };
+ float tl_br{ verts[tl].vertex.distance_to(verts[tl + points_per_side() + 1].vertex) };
+ float tr_bl{ verts[tl + 1].vertex.distance_to(verts[tl + points_per_side()].vertex) };
+ if (tl_br < tr_bl) {
+ surface->add_index(tl);
+ surface->add_index(tl + points_per_side() + 1);
+ surface->add_index(tl + 1);
+ surface->add_index(tl);
+ surface->add_index(tl + points_per_side());
+ surface->add_index(tl + points_per_side() + 1);
+ } else {
+ surface->add_index(tl + points_per_side());
+ surface->add_index(tl + points_per_side() + 1);
+ surface->add_index(tl + 1);
+ surface->add_index(tl + 1);
+ surface->add_index(tl);
+ surface->add_index(tl + points_per_side());
+ }
+ }
+ }
+}
+
+void TerrainChunkMesh::_notification(int what) {
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_READY:
+ this->safe_position = get_global_position();
+ float const sizef{ (float)get_size() };
+ this->bounds.position = { this->safe_position.x - sizef / 2.f, this->safe_position.z - sizef / 2.f };
+ this->bounds.size = { sizef, sizef };
+ return;
+ }
+}
+
+void TerrainChunkMesh::apply_new_mesh() {
+ this->lock.lock();
+ set_mesh(this->new_mesh);
+ this->lock.unlock();
+}
+
+void TerrainChunkMesh::update_mesh() {
+ ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate: size <= 0");
+ ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate: points per side <= 0");
+ this->lock.lock();
+ this->surface = memnew(SurfaceTool);
+ this->surface->begin(Mesh::PRIMITIVE_TRIANGLES);
+ generate_vertices();
+ generate_faces();
+ this->surface->generate_normals();
+ this->surface->generate_tangents();
+ this->new_mesh = memnew(ArrayMesh);
+ this->surface->commit(this->new_mesh);
+ this->lock.unlock();
+ this->terrain->mesh_dirty(this);
+}
+
+size_t TerrainChunkMesh::points_per_side() const {
+ return this->size * this->detail;
+}
diff --git a/modules/terrain/terrain_chunk.h b/modules/terrain/terrain_chunk.h
new file mode 100644
index 00000000..2e9ea49e
--- /dev/null
+++ b/modules/terrain/terrain_chunk.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "core/math/rect2.h"
+#include "macros.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/resources/mesh.h"
+#include "scene/resources/surface_tool.h"
+class Terrain;
+
+class TerrainChunkMesh : public MeshInstance3D {
+ GDCLASS(TerrainChunkMesh, MeshInstance3D);
+ static void _bind_methods();
+ void generate_vertices();
+ void generate_faces();
+
+protected:
+ void _notification(int what);
+
+public:
+ void apply_new_mesh();
+ void update_mesh();
+ size_t points_per_side() const;
+
+private:
+ Mutex lock{};
+ Vector3 safe_position{};
+ Ref surface{};
+ Ref new_mesh{};
+ Terrain *terrain{ nullptr };
+ size_t detail{ 1 };
+ size_t size{ 1 };
+ Rect2 bounds{};
+
+public:
+ GET_SET_FNS(Rect2, bounds);
+ GET_SET_FNS(Terrain *, terrain);
+ GET_SET_FNS(size_t, detail);
+ GET_SET_FNS(size_t, size);
+};
diff --git a/modules/terrain/terrain_modifier.cpp b/modules/terrain/terrain_modifier.cpp
new file mode 100644
index 00000000..2d5e2a85
--- /dev/null
+++ b/modules/terrain/terrain_modifier.cpp
@@ -0,0 +1,392 @@
+#include "terrain_modifier.h"
+#include "core/config/engine.h"
+#include "core/math/math_funcs.h"
+#include "core/variant/variant.h"
+#include "macros.h"
+#include "terrain/terrain.h"
+#include
+
+void TerrainModifier::_bind_methods() {}
+
+void TerrainModifier::_notification(int what) {
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_ENTER_TREE:
+ if (Engine::get_singleton()->is_editor_hint()) {
+ set_notify_transform(true);
+ }
+ this->thread_safe_global_position = get_global_position();
+ case NOTIFICATION_TRANSFORM_CHANGED:
+ this->thread_safe_global_position = get_global_position();
+ return;
+ }
+}
+
+void TerrainModifier::push_changed(Rect2 area) {
+ if (this->terrain) {
+ this->terrain->push_changed(area);
+ }
+}
+
+float TerrainModifier::evaluate_at(Vector2 world_coordinate, float before) {
+ Vector3 const global_position{ get_thread_safe_global_position() };
+ return global_position.y;
+}
+
+void TerrainModifier::set_bounds(Rect2 bounds) {
+ if (this->bounds != bounds) {
+ push_changed(bounds);
+ push_changed(this->bounds);
+ this->bounds = bounds;
+ }
+}
+
+Rect2 TerrainModifier::get_bounds() const {
+ return this->bounds;
+}
+
+Vector3 TerrainModifier::get_thread_safe_global_position() const {
+ return this->thread_safe_global_position;
+}
+
+void SharedMutex::lock_shared() {
+ this->lock.lock();
+ this->shared_count++;
+ this->lock.unlock();
+}
+
+void SharedMutex::unlock_shared() {
+ this->lock.lock();
+ this->shared_count--;
+ this->lock.unlock();
+}
+
+void SharedMutex::lock_exclusive() {
+ while (true) {
+ this->lock.lock();
+ if (this->shared_count == 0) {
+ return;
+ }
+ this->lock.unlock();
+ }
+}
+
+void SharedMutex::unlock_exclusive() {
+ this->lock.unlock();
+}
+
+void TerrainModifierDistance::_bind_methods() {
+ BIND_HPROPERTY(Variant::OBJECT, distance_weight_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
+}
+
+void TerrainModifierDistance::curves_changed() {
+ if (!update_bounds()) {
+ push_changed(get_bounds());
+ }
+}
+
+bool TerrainModifierDistance::update_bounds() {
+ Rect2 const before{ get_bounds() };
+ Rect2 bounds{};
+ Vector3 position{ get_thread_safe_global_position() };
+ bounds.position = { position.x, position.z };
+ bounds.size = { 0, 0 };
+ this->lock.lock_shared();
+ if (this->distance_weight_curve.is_valid()) {
+ float const max_radius{ this->distance_weight_curve->get_max_domain() };
+ float const max_diameter{ 2.f * max_radius };
+ bounds.size = { max_diameter, max_diameter };
+ bounds.position -= { max_radius, max_radius };
+ }
+ this->lock.unlock_shared();
+ this->lock.lock_exclusive();
+ bool const changed{ before != bounds };
+ set_bounds(bounds);
+ this->lock.unlock_exclusive();
+ return changed;
+}
+
+void TerrainModifierDistance::_notification(int what) {
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_READY:
+ update_bounds();
+ set_notify_transform(true);
+ return;
+ case NOTIFICATION_TRANSFORM_CHANGED:
+ if (is_inside_tree()) {
+ if (!update_bounds()) {
+ push_changed(get_bounds());
+ }
+ }
+ return;
+ }
+}
+
+float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) {
+ Vector3 const global_position{ get_thread_safe_global_position() };
+ return world_coordinate.distance_to({ global_position.x, global_position.z });
+}
+
+float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) {
+ this->lock.lock_shared();
+ if (this->distance_weight_curve.is_null()) {
+ this->lock.unlock_shared();
+ return before;
+ }
+ float const distance{ distance_at(world_coordinate) };
+ if (distance >= this->distance_weight_curve->get_max_domain()) {
+ this->lock.unlock_shared();
+ return before;
+ }
+ float const weight_offset{
+ std::clamp(distance, this->distance_weight_curve->get_min_domain(), this->distance_weight_curve->get_max_domain())
+ };
+ float const weight{ this->distance_weight_curve->sample(weight_offset) };
+ float out{ weight <= 0.f ? before : Math::lerp(before, get_thread_safe_global_position().y, weight) };
+ this->lock.unlock_shared();
+
+ return out;
+}
+
+PackedStringArray TerrainModifierDistance::get_configuration_warnings() const {
+ PackedStringArray warnings{ super_type::get_configuration_warnings() };
+ if (this->distance_weight_curve.is_null()) {
+ warnings.push_back("distance_weight_curve is invalid, add a valid distance_weight_curve");
+ }
+ return warnings;
+}
+
+void TerrainModifierDistance::set_distance_weight_curve(Ref curve) {
+ this->lock.lock_exclusive();
+ if (Engine::get_singleton()->is_editor_hint()) {
+ if (this->distance_weight_curve.is_valid()) {
+ this->distance_weight_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed));
+ }
+ if (curve.is_valid()) {
+ curve->connect_changed(callable_mp(this, &self_type::curves_changed));
+ }
+ }
+ this->distance_weight_curve = curve;
+ this->lock.unlock_exclusive();
+ curves_changed();
+ update_configuration_warnings();
+}
+
+Ref TerrainModifierDistance::get_distance_weight_curve() const {
+ return this->distance_weight_curve;
+}
+
+void TerrainModifierPathPoint::_bind_methods() {}
+
+void TerrainModifierPathPoint::_notification(int what) {
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_ENTER_TREE:
+ if ((this->path = cast_to(get_parent()))) {
+ this->path->path_changed();
+ }
+ return;
+ case NOTIFICATION_READY:
+ set_notify_transform(true);
+ return;
+ case NOTIFICATION_TRANSFORM_CHANGED:
+ if (this->path) {
+ this->path->path_changed();
+ }
+ return;
+ case NOTIFICATION_EXIT_TREE:
+ this->path = nullptr;
+ return;
+ }
+}
+
+void TerrainModifierPath::_bind_methods() {
+ BIND_HPROPERTY(Variant::OBJECT, curve_left, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
+ BIND_HPROPERTY(Variant::OBJECT, curve_right, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
+}
+
+void TerrainModifierPath::curves_changed() {
+ if (!update_bounds()) {
+ push_changed(get_bounds());
+ }
+}
+
+bool TerrainModifierPath::update_bounds() {
+ Vector2 min{}, max{};
+ this->lock.lock_shared();
+ if (this->points.is_empty() || this->curve_left.is_null() || this->curve_right.is_null()) {
+ Vector3 point{ this->get_thread_safe_global_position() };
+ min.x = point.x;
+ min.y = point.z;
+ max = min;
+ } else {
+ max = min = { this->points[0].x, this->points[0].y };
+ for (Vector3 const &point : this->points) {
+ max.x = max.x > point.x ? max.x : point.x;
+ max.y = max.y > point.z ? max.y : point.z;
+ min.x = min.x < point.x ? min.x : point.x;
+ min.y = min.y < point.z ? min.y : point.z;
+ }
+ float max_distance_left{ this->curve_left->get_max_domain() };
+ float max_distance_right{ this->curve_right->get_max_domain() };
+ float max_distance{ max_distance_left > max_distance_right ? max_distance_left : max_distance_right };
+ min -= { max_distance, max_distance };
+ max += { max_distance, max_distance };
+ }
+ Rect2 bounds{ min, max - min };
+ bool const changed{ bounds != get_bounds() };
+ this->lock.unlock_shared();
+ set_bounds(bounds);
+ return changed;
+}
+
+void TerrainModifierPath::_notification(int what) {
+ switch (what) {
+ default:
+ return;
+ case NOTIFICATION_READY:
+ update_bounds();
+ set_notify_transform(true);
+ return;
+ case NOTIFICATION_TRANSFORM_CHANGED:
+ if (is_inside_tree()) {
+ if (!update_bounds()) {
+ push_changed(get_bounds());
+ }
+ }
+ return;
+ }
+}
+
+float TerrainModifierPath::evaluate_line(Vector3 a, bool a_end, Vector3 b, bool b_end, Vector2 world_coordinate, float &out_dot, float &out_distance) {
+ Vector2 a2{ a.x, a.z }, b2{ b.x, b.z };
+ Vector2 const relative_coordinate{ world_coordinate - a2 };
+ Vector2 const difference2{ b2 - a2 };
+ float w{ difference2.normalized().dot(relative_coordinate) / difference2.length() };
+ Vector3 const difference{ b - a };
+ Vector3 const closest_on_line{ a + difference * (w > 0 ? (w < 1 ? w : 1) : 0) };
+ Vector2 const right{ -difference.z, difference.x };
+ out_dot = right.normalized().dot(relative_coordinate);
+ out_distance = world_coordinate.distance_to({ closest_on_line.x, closest_on_line.z });
+ if (a_end) {
+ w = w > 0 ? w : 0;
+ }
+ if (b_end) {
+ w = w < 1 ? w : 1;
+ }
+ return a.y + (b.y - a.y) * w;
+}
+
+float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) {
+ this->lock.lock_shared();
+ if (this->curve_left.is_null() || this->curve_right.is_null() || this->points.size() <= 1) {
+ this->lock.unlock_shared();
+ return before;
+ }
+ float const max_distance{ this->curve_left->get_max_domain() > this->curve_right->get_max_domain() ? this->curve_left->get_max_domain() : this->curve_right->get_max_domain() };
+ float out_score{ -INFINITY };
+ float out_height{ 0 };
+ long const count{ this->closed ? this->points.size() : this->points.size() - 1 };
+ for (int i{ 0 }; i < count; i++) {
+ float dot, distance;
+ float const height{ evaluate_line(this->points[i], !this->closed && i == 0, this->points[Math::wrapi(i + 1, 0, this->points.size())], !this->closed && i == count - 1, world_coordinate, dot, distance) };
+ float const left{ this->curve_left->sample(distance) };
+ float const right{ this->curve_right->sample(distance) };
+ float const ndot{ dot / distance };
+ float separation{ ndot / 2.f + 0.5f };
+ if (closed || (i > 0 && i < count - 1)) {
+ separation = Math::round(separation);
+ }
+ float const weight{ left * (1 - separation) + right * separation };
+ float const blended_height{ Math::lerp(before, height, weight) };
+ float const score{ weight - (1 - Math::abs(ndot)) * 2.f - (distance / max_distance) };
+ if (score > out_score) {
+ out_score = score;
+ out_height = blended_height;
+ }
+ }
+ this->lock.unlock_shared();
+ return out_height;
+}
+
+void TerrainModifierPath::path_changed() {
+ this->lock.lock_exclusive();
+ this->points.clear();
+ this->min_height = INFINITY;
+ this->max_height = -INFINITY;
+ Vector3 last{ INFINITY, INFINITY, INFINITY };
+ for (Variant var : get_children()) {
+ if (TerrainModifierPathPoint * point{ cast_to(var) }) {
+ Vector3 position{ point->get_global_position() };
+ if (position != last) {
+ this->points.push_back(position);
+ if (position.y > this->max_height) {
+ this->max_height = position.y;
+ }
+ if (position.y < this->min_height) {
+ this->min_height = position.y;
+ }
+ }
+ }
+ last = var;
+ }
+ this->lock.unlock_exclusive();
+ if (!update_bounds()) {
+ push_changed(get_bounds());
+ }
+}
+
+PackedStringArray TerrainModifierPath::get_configuration_warnings() const {
+ PackedStringArray warnings{ super_type::get_configuration_warnings() };
+ if (this->curve_left.is_null()) {
+ warnings.push_back("distance_weight_curve is invalid, add a valid curve_left");
+ }
+ if (this->curve_right.is_null()) {
+ warnings.push_back("distance_weight_curve is invalid, add a valid curve_right");
+ }
+ return warnings;
+}
+
+void TerrainModifierPath::set_curve_left(Ref curve) {
+ this->lock.lock_exclusive();
+ if (Engine::get_singleton()->is_editor_hint()) {
+ if (this->curve_left.is_valid()) {
+ this->curve_left->disconnect_changed(callable_mp(this, &self_type::curves_changed));
+ }
+ if (curve.is_valid()) {
+ curve->connect_changed(callable_mp(this, &self_type::curves_changed));
+ }
+ }
+ this->curve_left = curve;
+ this->lock.unlock_exclusive();
+ curves_changed();
+ update_configuration_warnings();
+}
+
+Ref TerrainModifierPath::get_curve_left() const {
+ return this->curve_left;
+}
+
+void TerrainModifierPath::set_curve_right(Ref curve) {
+ this->lock.lock_exclusive();
+ if (Engine::get_singleton()->is_editor_hint()) {
+ if (this->curve_right.is_valid()) {
+ this->curve_right->disconnect_changed(callable_mp(this, &self_type::curves_changed));
+ }
+ if (curve.is_valid()) {
+ curve->connect_changed(callable_mp(this, &self_type::curves_changed));
+ }
+ }
+ this->curve_right = curve;
+ this->lock.unlock_exclusive();
+ curves_changed();
+ update_configuration_warnings();
+}
+
+Ref TerrainModifierPath::get_curve_right() const {
+ return this->curve_right;
+}
diff --git a/modules/terrain/terrain_modifier.h b/modules/terrain/terrain_modifier.h
new file mode 100644
index 00000000..4585a6f6
--- /dev/null
+++ b/modules/terrain/terrain_modifier.h
@@ -0,0 +1,110 @@
+#pragma once
+
+#include "core/object/object.h"
+#include "core/variant/variant.h"
+#include "macros.h"
+#include "scene/3d/marker_3d.h"
+#include "scene/resources/curve.h"
+#include
+class Terrain;
+
+class TerrainModifier : public Marker3D {
+ GDCLASS(TerrainModifier, Marker3D);
+ static void _bind_methods();
+
+protected:
+ void _notification(int what);
+ void push_changed(Rect2 bounds);
+
+public:
+ virtual float evaluate_at(Vector2 world_coordinate, float before);
+
+private:
+ Vector3 thread_safe_global_position{};
+ Terrain *terrain{ nullptr };
+ Rect2 bounds{ { -INFINITY, -INFINITY }, { INFINITY, INFINITY } };
+
+protected:
+ void set_bounds(Rect2 bounds);
+ Rect2 get_bounds() const;
+
+public:
+ Vector3 get_thread_safe_global_position() const;
+ GET_SET_FNS(Terrain *, terrain);
+};
+
+struct SharedMutex {
+ void lock_shared();
+ void unlock_shared();
+ void lock_exclusive();
+ void unlock_exclusive();
+
+private:
+ Mutex lock{};
+ int shared_count{};
+};
+
+class TerrainModifierDistance : public TerrainModifier {
+ GDCLASS(TerrainModifierDistance, TerrainModifier);
+ static void _bind_methods();
+ void curves_changed();
+ bool update_bounds();
+
+protected:
+ void _notification(int what);
+ float distance_at(Vector2 const &world_coordinate);
+
+public:
+ float evaluate_at(Vector2 world_coordinate, float before) override;
+ PackedStringArray get_configuration_warnings() const override;
+
+private:
+ SharedMutex lock{};
+ Ref distance_weight_curve{};
+
+public:
+ void set_distance_weight_curve(Ref curve);
+ Ref get_distance_weight_curve() const;
+};
+
+class TerrainModifierPathPoint : public Marker3D {
+ GDCLASS(TerrainModifierPathPoint, Marker3D);
+ static void _bind_methods();
+
+protected:
+ void _notification(int what);
+
+public:
+ class TerrainModifierPath *path{ nullptr };
+};
+
+class TerrainModifierPath : public TerrainModifier {
+ GDCLASS(TerrainModifierPath, TerrainModifier);
+ static void _bind_methods();
+ void curves_changed();
+ bool update_bounds();
+
+protected:
+ void _notification(int what);
+ float evaluate_line(Vector3 a, bool a_end, Vector3 b, bool b_end, Vector2 world_coordinate, float &out_dot, float &out_distance);
+
+public:
+ float evaluate_at(Vector2 world_coordinate, float before) override;
+ void path_changed();
+ PackedStringArray get_configuration_warnings() const override;
+
+private:
+ SharedMutex lock{};
+ float min_height{};
+ float max_height{};
+ bool closed{ false };
+ Vector points{};
+ Ref curve_left{};
+ Ref curve_right{};
+
+public:
+ void set_curve_left(Ref curve);
+ Ref get_curve_left() const;
+ void set_curve_right(Ref curve);
+ Ref get_curve_right() const;
+};
diff --git a/project/assets/characters/player_fem/character_fem.blend b/project/assets/characters/player_fem/character_fem.blend
new file mode 100644
index 00000000..d6a83316
Binary files /dev/null and b/project/assets/characters/player_fem/character_fem.blend differ
diff --git a/project/assets/characters/player_fem/character_fem.blend.import b/project/assets/characters/player_fem/character_fem.blend.import
new file mode 100644
index 00000000..f3b42a40
--- /dev/null
+++ b/project/assets/characters/player_fem/character_fem.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://grb3q5nd2uds"
+path="res://.godot/imported/character_fem.blend-e169cb46816e89cf00aa8e7f988a0574.scn"
+
+[deps]
+
+source_file="res://assets/characters/player_fem/character_fem.blend"
+dest_files=["res://.godot/imported/character_fem.blend-e169cb46816e89cf00aa8e7f988a0574.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/characters/player_fem/character_fem.blend1 b/project/assets/characters/player_fem/character_fem.blend1
new file mode 100644
index 00000000..b63624e8
Binary files /dev/null and b/project/assets/characters/player_fem/character_fem.blend1 differ
diff --git a/project/assets/characters/player_fem/textures/Face.png b/project/assets/characters/player_fem/textures/Face.png
new file mode 100644
index 00000000..942b4264
Binary files /dev/null and b/project/assets/characters/player_fem/textures/Face.png differ
diff --git a/project/assets/characters/player_fem/textures/Face.png.import b/project/assets/characters/player_fem/textures/Face.png.import
new file mode 100644
index 00000000..ee9ab627
--- /dev/null
+++ b/project/assets/characters/player_fem/textures/Face.png.import
@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://biqq268lccpng"
+path.s3tc="res://.godot/imported/Face.png-08c0111f3b71fa077c35243a4c740f6e.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/characters/player_fem/textures/Face.png"
+dest_files=["res://.godot/imported/Face.png-08c0111f3b71fa077c35243a4c740f6e.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/project/assets/characters/player_fem/textures/Texture.png b/project/assets/characters/player_fem/textures/Texture.png
new file mode 100644
index 00000000..bbb69001
Binary files /dev/null and b/project/assets/characters/player_fem/textures/Texture.png differ
diff --git a/project/assets/characters/player_fem/textures/Texture.png.import b/project/assets/characters/player_fem/textures/Texture.png.import
new file mode 100644
index 00000000..eba1bf9a
--- /dev/null
+++ b/project/assets/characters/player_fem/textures/Texture.png.import
@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cwqc2g4616eun"
+path.s3tc="res://.godot/imported/Texture.png-98af1a158e1830cbcd0a13178176c442.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/characters/player_fem/textures/Texture.png"
+dest_files=["res://.godot/imported/Texture.png-98af1a158e1830cbcd0a13178176c442.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/project/assets/characters/player_masc/character_masc.blend b/project/assets/characters/player_masc/character_masc.blend
new file mode 100644
index 00000000..a6c7dba2
Binary files /dev/null and b/project/assets/characters/player_masc/character_masc.blend differ
diff --git a/project/assets/characters/player_masc/character_masc.blend.import b/project/assets/characters/player_masc/character_masc.blend.import
new file mode 100644
index 00000000..1da1533b
--- /dev/null
+++ b/project/assets/characters/player_masc/character_masc.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://bsdvnyn6nhiaa"
+path="res://.godot/imported/character_masc.blend-0037c94467e2e85d7ec685a838d7e95d.scn"
+
+[deps]
+
+source_file="res://assets/characters/player_masc/character_masc.blend"
+dest_files=["res://.godot/imported/character_masc.blend-0037c94467e2e85d7ec685a838d7e95d.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/characters/player_masc/character_masc.blend1 b/project/assets/characters/player_masc/character_masc.blend1
new file mode 100644
index 00000000..a0e310b7
Binary files /dev/null and b/project/assets/characters/player_masc/character_masc.blend1 differ
diff --git a/project/assets/characters/player_masc/textures/Face.png b/project/assets/characters/player_masc/textures/Face.png
new file mode 100644
index 00000000..e66061c5
Binary files /dev/null and b/project/assets/characters/player_masc/textures/Face.png differ
diff --git a/project/assets/characters/player_masc/textures/Face.png.import b/project/assets/characters/player_masc/textures/Face.png.import
new file mode 100644
index 00000000..9659496c
--- /dev/null
+++ b/project/assets/characters/player_masc/textures/Face.png.import
@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7n0rgtlub4r7"
+path.s3tc="res://.godot/imported/Face.png-f5f833f7c71137a9e4aac5fe268e4dd4.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/characters/player_masc/textures/Face.png"
+dest_files=["res://.godot/imported/Face.png-f5f833f7c71137a9e4aac5fe268e4dd4.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/project/assets/characters/player_masc/textures/Texture.png b/project/assets/characters/player_masc/textures/Texture.png
new file mode 100644
index 00000000..bbb69001
Binary files /dev/null and b/project/assets/characters/player_masc/textures/Texture.png differ
diff --git a/project/assets/characters/player_masc/textures/Texture.png.import b/project/assets/characters/player_masc/textures/Texture.png.import
new file mode 100644
index 00000000..e3208081
--- /dev/null
+++ b/project/assets/characters/player_masc/textures/Texture.png.import
@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://lea3dgv2585y"
+path.s3tc="res://.godot/imported/Texture.png-51f4b86d2d244a13f05f399cc30a0d6b.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/characters/player_masc/textures/Texture.png"
+dest_files=["res://.godot/imported/Texture.png-51f4b86d2d244a13f05f399cc30a0d6b.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/project/assets/environments/blockouts/cliffs_blockout.blend b/project/assets/environments/blockouts/cliffs_blockout.blend
new file mode 100644
index 00000000..d49eddce
Binary files /dev/null and b/project/assets/environments/blockouts/cliffs_blockout.blend differ
diff --git a/project/assets/environments/blockouts/cliffs_blockout.blend.import b/project/assets/environments/blockouts/cliffs_blockout.blend.import
new file mode 100644
index 00000000..bb039223
--- /dev/null
+++ b/project/assets/environments/blockouts/cliffs_blockout.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://dw4p3s74f1pdg"
+path="res://.godot/imported/cliffs_blockout.blend-f86a374f2c48645fd5614df18445a45a.scn"
+
+[deps]
+
+source_file="res://assets/environments/blockouts/cliffs_blockout.blend"
+dest_files=["res://.godot/imported/cliffs_blockout.blend-f86a374f2c48645fd5614df18445a45a.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/environments/blockouts/cliffs_blockout.blend1 b/project/assets/environments/blockouts/cliffs_blockout.blend1
new file mode 100644
index 00000000..64456248
Binary files /dev/null and b/project/assets/environments/blockouts/cliffs_blockout.blend1 differ
diff --git a/project/assets/environments/blockouts/terrain.png b/project/assets/environments/blockouts/terrain.png
new file mode 100644
index 00000000..c1ad7ab3
Binary files /dev/null and b/project/assets/environments/blockouts/terrain.png differ
diff --git a/project/assets/environments/blockouts/terrain.png.import b/project/assets/environments/blockouts/terrain.png.import
new file mode 100644
index 00000000..d9ec829f
--- /dev/null
+++ b/project/assets/environments/blockouts/terrain.png.import
@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dwmur2qflotv6"
+path.bptc="res://.godot/imported/terrain.png-52ea7eaf6b989ed8d9fc2c0c5a398fdf.bptc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/environments/blockouts/terrain.png"
+dest_files=["res://.godot/imported/terrain.png-52ea7eaf6b989ed8d9fc2c0c5a398fdf.bptc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=true
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/project/assets/environments/props/flower.blend b/project/assets/environments/props/flower.blend
new file mode 100644
index 00000000..1779896f
Binary files /dev/null and b/project/assets/environments/props/flower.blend differ
diff --git a/project/assets/environments/props/flower.blend.import b/project/assets/environments/props/flower.blend.import
new file mode 100644
index 00000000..40504356
--- /dev/null
+++ b/project/assets/environments/props/flower.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://dyt1mwbep2012"
+path="res://.godot/imported/flower.blend-a54f993c4db9e1bce792f272b6b1a837.scn"
+
+[deps]
+
+source_file="res://assets/environments/props/flower.blend"
+dest_files=["res://.godot/imported/flower.blend-a54f993c4db9e1bce792f272b6b1a837.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/environments/props/flower.blend1 b/project/assets/environments/props/flower.blend1
new file mode 100644
index 00000000..4c4147f4
Binary files /dev/null and b/project/assets/environments/props/flower.blend1 differ
diff --git a/project/assets/environments/props/grass_a.png b/project/assets/environments/props/grass_a.png
new file mode 100644
index 00000000..cd8fb434
Binary files /dev/null and b/project/assets/environments/props/grass_a.png differ
diff --git a/project/assets/environments/props/grass_a.png.import b/project/assets/environments/props/grass_a.png.import
new file mode 100644
index 00000000..b237508f
--- /dev/null
+++ b/project/assets/environments/props/grass_a.png.import
@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://uujfed6yrp8p"
+path.s3tc="res://.godot/imported/grass_a.png-df3280112d606c2f3fb6a8ca84baa85d.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/environments/props/grass_a.png"
+dest_files=["res://.godot/imported/grass_a.png-df3280112d606c2f3fb6a8ca84baa85d.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/project/assets/environments/props/grass_a.png~ b/project/assets/environments/props/grass_a.png~
new file mode 100644
index 00000000..8f6c4024
Binary files /dev/null and b/project/assets/environments/props/grass_a.png~ differ
diff --git a/project/assets/environments/props/rock_a.blend b/project/assets/environments/props/rock_a.blend
new file mode 100644
index 00000000..2dae9d3d
Binary files /dev/null and b/project/assets/environments/props/rock_a.blend differ
diff --git a/project/assets/environments/props/rock_a.blend.import b/project/assets/environments/props/rock_a.blend.import
new file mode 100644
index 00000000..e2848818
--- /dev/null
+++ b/project/assets/environments/props/rock_a.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://db6ddpj53gl5w"
+path="res://.godot/imported/rock_a.blend-b8d8f34d9e140f1b8adb493b605dd07e.scn"
+
+[deps]
+
+source_file="res://assets/environments/props/rock_a.blend"
+dest_files=["res://.godot/imported/rock_a.blend-b8d8f34d9e140f1b8adb493b605dd07e.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/environments/props/rock_a.blend1 b/project/assets/environments/props/rock_a.blend1
new file mode 100644
index 00000000..6054e159
Binary files /dev/null and b/project/assets/environments/props/rock_a.blend1 differ
diff --git a/project/assets/environments/props/rock_a.png b/project/assets/environments/props/rock_a.png
new file mode 100644
index 00000000..e8b104a4
Binary files /dev/null and b/project/assets/environments/props/rock_a.png differ
diff --git a/project/textures/grids/tent.png.import b/project/assets/environments/props/rock_a.png.import
similarity index 56%
rename from project/textures/grids/tent.png.import
rename to project/assets/environments/props/rock_a.png.import
index 1381a79f..59595df6 100644
--- a/project/textures/grids/tent.png.import
+++ b/project/assets/environments/props/rock_a.png.import
@@ -2,8 +2,8 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://bh68a5vqm5h7l"
-path.s3tc="res://.godot/imported/tent.png-683c31f751d990e004be3a99db100a94.s3tc.ctex"
+uid="uid://b4rklg0l7aqdm"
+path.s3tc="res://.godot/imported/rock_a.png-3ff1a5814ef260ae524170318024cca4.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
@@ -11,14 +11,16 @@ metadata={
[deps]
-source_file="res://textures/grids/tent.png"
-dest_files=["res://.godot/imported/tent.png-683c31f751d990e004be3a99db100a94.s3tc.ctex"]
+source_file="res://assets/environments/props/rock_a.png"
+dest_files=["res://.godot/imported/rock_a.png-3ff1a5814ef260ae524170318024cca4.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@@ -26,6 +28,10 @@ mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
diff --git a/project/assets/environments/props/rock_b.blend b/project/assets/environments/props/rock_b.blend
new file mode 100644
index 00000000..b89718cd
Binary files /dev/null and b/project/assets/environments/props/rock_b.blend differ
diff --git a/project/assets/environments/props/rock_b.blend.import b/project/assets/environments/props/rock_b.blend.import
new file mode 100644
index 00000000..7160e95a
--- /dev/null
+++ b/project/assets/environments/props/rock_b.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://517yqaw110pf"
+path="res://.godot/imported/rock_b.blend-d76791249d4c14d1c951b842f0208090.scn"
+
+[deps]
+
+source_file="res://assets/environments/props/rock_b.blend"
+dest_files=["res://.godot/imported/rock_b.blend-d76791249d4c14d1c951b842f0208090.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/environments/props/rock_b.blend1 b/project/assets/environments/props/rock_b.blend1
new file mode 100644
index 00000000..13c4e5f0
Binary files /dev/null and b/project/assets/environments/props/rock_b.blend1 differ
diff --git a/project/assets/environments/props/rock_b.png b/project/assets/environments/props/rock_b.png
new file mode 100644
index 00000000..1df0c28d
Binary files /dev/null and b/project/assets/environments/props/rock_b.png differ
diff --git a/project/textures/grids/grass.png.import b/project/assets/environments/props/rock_b.png.import
similarity index 56%
rename from project/textures/grids/grass.png.import
rename to project/assets/environments/props/rock_b.png.import
index 10e02de2..92fd56bf 100644
--- a/project/textures/grids/grass.png.import
+++ b/project/assets/environments/props/rock_b.png.import
@@ -2,8 +2,8 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://f8djywm2jlah"
-path.s3tc="res://.godot/imported/grass.png-94a6319b2d9ea30e834183f2653f3455.s3tc.ctex"
+uid="uid://dpn3sfcjnnool"
+path.s3tc="res://.godot/imported/rock_b.png-063dafa9684f12d7486304024d1aaecf.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
@@ -11,14 +11,16 @@ metadata={
[deps]
-source_file="res://textures/grids/grass.png"
-dest_files=["res://.godot/imported/grass.png-94a6319b2d9ea30e834183f2653f3455.s3tc.ctex"]
+source_file="res://assets/environments/props/rock_b.png"
+dest_files=["res://.godot/imported/rock_b.png-063dafa9684f12d7486304024d1aaecf.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@@ -26,6 +28,10 @@ mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
diff --git a/project/assets/environments/props/rock_c.blend b/project/assets/environments/props/rock_c.blend
new file mode 100644
index 00000000..b369c50e
Binary files /dev/null and b/project/assets/environments/props/rock_c.blend differ
diff --git a/project/assets/environments/props/rock_c.blend.import b/project/assets/environments/props/rock_c.blend.import
new file mode 100644
index 00000000..899d0256
--- /dev/null
+++ b/project/assets/environments/props/rock_c.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://8n8fduklxduk"
+path="res://.godot/imported/rock_c.blend-26ab78e8b85061bd62ef5331656df04c.scn"
+
+[deps]
+
+source_file="res://assets/environments/props/rock_c.blend"
+dest_files=["res://.godot/imported/rock_c.blend-26ab78e8b85061bd62ef5331656df04c.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/environments/props/rock_c.blend1 b/project/assets/environments/props/rock_c.blend1
new file mode 100644
index 00000000..53c1ef52
Binary files /dev/null and b/project/assets/environments/props/rock_c.blend1 differ
diff --git a/project/assets/environments/props/rock_c.png b/project/assets/environments/props/rock_c.png
new file mode 100644
index 00000000..16ec8ed9
Binary files /dev/null and b/project/assets/environments/props/rock_c.png differ
diff --git a/project/textures/grids/mud.png.import b/project/assets/environments/props/rock_c.png.import
similarity index 56%
rename from project/textures/grids/mud.png.import
rename to project/assets/environments/props/rock_c.png.import
index 7a14f648..ff773ee7 100644
--- a/project/textures/grids/mud.png.import
+++ b/project/assets/environments/props/rock_c.png.import
@@ -2,8 +2,8 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://br64q04tpxmli"
-path.s3tc="res://.godot/imported/mud.png-f0a13a766854ada7e9976a2898dea1c2.s3tc.ctex"
+uid="uid://drf6dro5di2ap"
+path.s3tc="res://.godot/imported/rock_c.png-a276885ef75cf615c39e79d768211422.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
@@ -11,14 +11,16 @@ metadata={
[deps]
-source_file="res://textures/grids/mud.png"
-dest_files=["res://.godot/imported/mud.png-f0a13a766854ada7e9976a2898dea1c2.s3tc.ctex"]
+source_file="res://assets/environments/props/rock_c.png"
+dest_files=["res://.godot/imported/rock_c.png-a276885ef75cf615c39e79d768211422.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@@ -26,6 +28,10 @@ mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
diff --git a/project/assets/environments/props/tree.blend b/project/assets/environments/props/tree.blend
new file mode 100644
index 00000000..7c45b961
Binary files /dev/null and b/project/assets/environments/props/tree.blend differ
diff --git a/project/assets/environments/props/tree.blend.import b/project/assets/environments/props/tree.blend.import
new file mode 100644
index 00000000..373da191
--- /dev/null
+++ b/project/assets/environments/props/tree.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://bnm0go8negvo7"
+path="res://.godot/imported/tree.blend-20cb45b772c5003289278d8ec4060a00.scn"
+
+[deps]
+
+source_file="res://assets/environments/props/tree.blend"
+dest_files=["res://.godot/imported/tree.blend-20cb45b772c5003289278d8ec4060a00.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/environments/props/tree.blend1 b/project/assets/environments/props/tree.blend1
new file mode 100644
index 00000000..1c795ec8
Binary files /dev/null and b/project/assets/environments/props/tree.blend1 differ
diff --git a/project/assets/environments/props/tree.png b/project/assets/environments/props/tree.png
new file mode 100644
index 00000000..3345b586
Binary files /dev/null and b/project/assets/environments/props/tree.png differ
diff --git a/project/assets/environments/props/tree.png.import b/project/assets/environments/props/tree.png.import
new file mode 100644
index 00000000..7f195343
--- /dev/null
+++ b/project/assets/environments/props/tree.png.import
@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cvfsvh8tdpflf"
+path.bptc="res://.godot/imported/tree.png-b3baca9a70a35e7b7075603680fb265f.bptc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/environments/props/tree.png"
+dest_files=["res://.godot/imported/tree.png-b3baca9a70a35e7b7075603680fb265f.bptc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=true
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/project/assets/style/base_outline_material.tres b/project/assets/style/base_outline_material.tres
new file mode 100644
index 00000000..723181a0
--- /dev/null
+++ b/project/assets/style/base_outline_material.tres
@@ -0,0 +1,16 @@
+[gd_resource type="StandardMaterial3D" format=3 uid="uid://cd4vnmrmj8cj7"]
+
+[resource]
+transparency = 2
+alpha_scissor_threshold = 0.5
+alpha_antialiasing_mode = 0
+cull_mode = 1
+shading_mode = 0
+diffuse_mode = 3
+specular_mode = 2
+vertex_color_use_as_albedo = true
+albedo_color = Color(0.121152334, 0.121152334, 0.121152334, 1)
+grow = true
+grow_amount = 0.02
+proximity_fade_distance = 0.1
+stencil_outline_thickness = 0.029
diff --git a/project/assets/style/detail_outline_material.tres b/project/assets/style/detail_outline_material.tres
new file mode 100644
index 00000000..189cd35a
--- /dev/null
+++ b/project/assets/style/detail_outline_material.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StandardMaterial3D" format=3 uid="uid://02s3lq67141v"]
+
+[resource]
+transparency = 2
+alpha_scissor_threshold = 0.94
+alpha_antialiasing_mode = 0
+cull_mode = 1
+shading_mode = 0
+diffuse_mode = 3
+specular_mode = 2
+vertex_color_use_as_albedo = true
+albedo_color = Color(0.121152334, 0.121152334, 0.121152334, 1)
+grow = true
+grow_amount = 0.02
+proximity_fade_enabled = true
+proximity_fade_distance = 0.3
+stencil_outline_thickness = 0.029
diff --git a/project/assets/style/model_importer.gd b/project/assets/style/model_importer.gd
new file mode 100644
index 00000000..f00c0533
--- /dev/null
+++ b/project/assets/style/model_importer.gd
@@ -0,0 +1,36 @@
+@tool
+extends EditorScenePostImport
+
+var regular_outline_material : StandardMaterial3D
+var detail_outline_material : StandardMaterial3D
+var thin_outline_material : StandardMaterial3D
+
+func _post_import(root : Node):
+ regular_outline_material = ResourceLoader.load("res://assets/style/base_outline_material.tres") as StandardMaterial3D
+ detail_outline_material = ResourceLoader.load("res://assets/style/detail_outline_material.tres") as StandardMaterial3D
+ thin_outline_material = ResourceLoader.load("res://assets/style/thin_outline_material.tres") as StandardMaterial3D
+ apply_outline_recursive(root)
+ return root
+
+func get_flag(node : Node, flag : String) -> bool:
+ if node.name.contains(flag):
+ node.name = node.name.erase(node.name.find(flag), flag.length())
+ return true
+ else:
+ return false
+
+func apply_outline_recursive(node : Node):
+ if node != null:
+ var outline : bool = not get_flag(node, "-nooutline")
+ if outline and node is MeshInstance3D:
+ var detail : bool = get_flag(node, "-detailoutline")
+ var thin : bool = get_flag(node, "-thinoutline")
+ var mesh : MeshInstance3D = (node as MeshInstance3D)
+ if detail and detail_outline_material:
+ mesh.material_overlay = detail_outline_material
+ elif thin and thin_outline_material:
+ mesh.material_overlay = thin_outline_material
+ elif regular_outline_material:
+ mesh.material_overlay = regular_outline_material
+ for child in node.get_children():
+ apply_outline_recursive(child)
diff --git a/project/assets/style/model_importer.gd.uid b/project/assets/style/model_importer.gd.uid
new file mode 100644
index 00000000..f6d2d38b
--- /dev/null
+++ b/project/assets/style/model_importer.gd.uid
@@ -0,0 +1 @@
+uid://ba7qlhj5ylm3d
diff --git a/project/assets/style/thin_outline_material.tres b/project/assets/style/thin_outline_material.tres
new file mode 100644
index 00000000..c9b353e8
--- /dev/null
+++ b/project/assets/style/thin_outline_material.tres
@@ -0,0 +1,22 @@
+[gd_resource type="StandardMaterial3D" format=3 uid="uid://vo4kk73alewq"]
+
+[ext_resource type="Material" uid="uid://02s3lq67141v" path="res://assets/style/detail_outline_material.tres" id="1_jjhui"]
+
+[resource]
+next_pass = ExtResource("1_jjhui")
+transparency = 2
+alpha_scissor_threshold = 0.9
+alpha_antialiasing_mode = 0
+cull_mode = 1
+shading_mode = 0
+diffuse_mode = 3
+specular_mode = 2
+vertex_color_use_as_albedo = true
+albedo_color = Color(0.121152334, 0.121152334, 0.121152334, 1)
+grow = true
+grow_amount = 0.007
+proximity_fade_distance = 0.1
+distance_fade_mode = 1
+distance_fade_min_distance = 10.0
+distance_fade_max_distance = 9.0
+stencil_outline_thickness = 0.029
diff --git a/project/assets/vehicles/bike.blend b/project/assets/vehicles/bike.blend
new file mode 100644
index 00000000..ef724d37
Binary files /dev/null and b/project/assets/vehicles/bike.blend differ
diff --git a/project/assets/vehicles/bike.blend.import b/project/assets/vehicles/bike.blend.import
new file mode 100644
index 00000000..fe627e68
--- /dev/null
+++ b/project/assets/vehicles/bike.blend.import
@@ -0,0 +1,60 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://dkq8b07op54lx"
+path="res://.godot/imported/bike.blend-8d249f74417ff10660977200f19cdfc5.scn"
+
+[deps]
+
+source_file="res://assets/vehicles/bike.blend"
+dest_files=["res://.godot/imported/bike.blend-8d249f74417ff10660977200f19cdfc5.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path="uid://ba7qlhj5ylm3d"
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+blender/nodes/visible=0
+blender/nodes/active_collection_only=false
+blender/nodes/punctual_lights=true
+blender/nodes/cameras=true
+blender/nodes/custom_properties=true
+blender/nodes/modifiers=1
+blender/meshes/colors=false
+blender/meshes/uvs=true
+blender/meshes/normals=true
+blender/meshes/export_geometry_nodes_instances=false
+blender/meshes/gpu_instances=false
+blender/meshes/tangents=true
+blender/meshes/skins=2
+blender/meshes/export_bones_deforming_mesh_only=false
+blender/materials/unpack_enabled=true
+blender/materials/export_materials=1
+blender/animation/limit_playback=true
+blender/animation/always_sample=true
+blender/animation/group_tracks=true
+gltf/naming_version=2
diff --git a/project/assets/vehicles/bike.blend1 b/project/assets/vehicles/bike.blend1
new file mode 100644
index 00000000..7c3cc2b0
Binary files /dev/null and b/project/assets/vehicles/bike.blend1 differ
diff --git a/project/assets/vehicles/sidecar.png b/project/assets/vehicles/sidecar.png
new file mode 100644
index 00000000..a6bfd9cb
Binary files /dev/null and b/project/assets/vehicles/sidecar.png differ
diff --git a/project/textures/grids/bricks.png.import b/project/assets/vehicles/sidecar.png.import
similarity index 57%
rename from project/textures/grids/bricks.png.import
rename to project/assets/vehicles/sidecar.png.import
index 84da4842..e50d6c10 100644
--- a/project/textures/grids/bricks.png.import
+++ b/project/assets/vehicles/sidecar.png.import
@@ -2,8 +2,8 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://cd4cchmulwnc5"
-path.s3tc="res://.godot/imported/bricks.png-217e783fed9aafaab4854f1be96e4aed.s3tc.ctex"
+uid="uid://csscfssydx1n4"
+path.s3tc="res://.godot/imported/sidecar.png-10b8daaf663ef9108162d2fab75bc493.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
@@ -11,14 +11,16 @@ metadata={
[deps]
-source_file="res://textures/grids/bricks.png"
-dest_files=["res://.godot/imported/bricks.png-217e783fed9aafaab4854f1be96e4aed.s3tc.ctex"]
+source_file="res://assets/vehicles/sidecar.png"
+dest_files=["res://.godot/imported/sidecar.png-10b8daaf663ef9108162d2fab75bc493.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@@ -26,6 +28,10 @@ mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
diff --git a/project/data/character_data/fallback_character_data.tres b/project/data/character_data/fallback_character_data.tres
new file mode 100644
index 00000000..8c0d83cb
--- /dev/null
+++ b/project/data/character_data/fallback_character_data.tres
@@ -0,0 +1,4 @@
+[gd_resource type="CharacterData" format=3 uid="uid://d28pn4xekwh6p"]
+
+[resource]
+speed = 2.0
diff --git a/project/data/default_player_character.tres b/project/data/default_player_character.tres
new file mode 100644
index 00000000..3fb998bd
--- /dev/null
+++ b/project/data/default_player_character.tres
@@ -0,0 +1,4 @@
+[gd_resource type="CharacterData" format=3 uid="uid://bmudhddb0vedg"]
+
+[resource]
+speed = 2.0
diff --git a/project/icon.svg.import b/project/icon.svg.import
index 869157e0..f082c774 100644
--- a/project/icon.svg.import
+++ b/project/icon.svg.import
@@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@@ -25,6 +27,10 @@ mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
diff --git a/project/level_props/tent.tscn b/project/level_props/tent.tscn
deleted file mode 100644
index 065f9c5c..00000000
--- a/project/level_props/tent.tscn
+++ /dev/null
@@ -1,13 +0,0 @@
-[gd_scene load_steps=3 format=3 uid="uid://dkgep77ogr1tv"]
-
-[ext_resource type="Material" uid="uid://cupy5mpdsngcl" path="res://materials/grids/tent.tres" id="1_yfc7x"]
-
-[sub_resource type="BoxMesh" id="BoxMesh_dudpm"]
-size = Vector3(3, 1.3, 2)
-
-[node name="Tent" type="Node3D"]
-
-[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.658981, 0)
-material_override = ExtResource("1_yfc7x")
-mesh = SubResource("BoxMesh_dudpm")
diff --git a/project/locales/city.tscn b/project/locales/city.tscn
deleted file mode 100644
index c31fb667..00000000
--- a/project/locales/city.tscn
+++ /dev/null
@@ -1,39 +0,0 @@
-[gd_scene load_steps=6 format=3 uid="uid://bqaoxvqgrbi3v"]
-
-[ext_resource type="Material" uid="uid://cbuk8uxxuj7j5" path="res://materials/grids/grass.tres" id="1_etye1"]
-[ext_resource type="PackedScene" uid="uid://cykd1a23ria6k" path="res://objects/characters/player_actor.tscn" id="3_vuk2b"]
-
-[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sb1vi"]
-sky_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
-ground_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
-
-[sub_resource type="Sky" id="Sky_vuk2b"]
-sky_material = SubResource("ProceduralSkyMaterial_sb1vi")
-
-[sub_resource type="Environment" id="Environment_wwygw"]
-background_mode = 2
-sky = SubResource("Sky_vuk2b")
-tonemap_mode = 2
-glow_enabled = true
-
-[node name="City" type="Node"]
-
-[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
-environment = SubResource("Environment_wwygw")
-
-[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
-transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
-shadow_enabled = true
-
-[node name="CampEntrance" type="Node3D" parent="."]
-unique_name_in_owner = true
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16.2797, -0.00393486, -42.0831)
-
-[node name="PlayerActor" parent="." instance=ExtResource("3_vuk2b")]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0171704, -0.00572681, 0.28793)
-
-[node name="CSGBox3D" type="CSGBox3D" parent="."]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.505729, 0)
-use_collision = true
-size = Vector3(100, 1, 100)
-material = ExtResource("1_etye1")
diff --git a/project/materials/grids/bricks.tres b/project/materials/grids/bricks.tres
deleted file mode 100644
index 1911e356..00000000
--- a/project/materials/grids/bricks.tres
+++ /dev/null
@@ -1,7 +0,0 @@
-[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://ke4yek3xtin5"]
-
-[ext_resource type="Texture2D" uid="uid://cd4cchmulwnc5" path="res://textures/grids/bricks.png" id="1_ustix"]
-
-[resource]
-albedo_texture = ExtResource("1_ustix")
-uv1_triplanar = true
diff --git a/project/materials/grids/grass.tres b/project/materials/grids/grass.tres
deleted file mode 100644
index ebb67f75..00000000
--- a/project/materials/grids/grass.tres
+++ /dev/null
@@ -1,7 +0,0 @@
-[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://cbuk8uxxuj7j5"]
-
-[ext_resource type="Texture2D" uid="uid://f8djywm2jlah" path="res://textures/grids/grass.png" id="1_lqti4"]
-
-[resource]
-albedo_texture = ExtResource("1_lqti4")
-uv1_triplanar = true
diff --git a/project/materials/grids/mud.tres b/project/materials/grids/mud.tres
deleted file mode 100644
index 6b69d38e..00000000
--- a/project/materials/grids/mud.tres
+++ /dev/null
@@ -1,7 +0,0 @@
-[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://blcccgo88gl7c"]
-
-[ext_resource type="Texture2D" uid="uid://br64q04tpxmli" path="res://textures/grids/mud.png" id="1_kexk5"]
-
-[resource]
-albedo_texture = ExtResource("1_kexk5")
-uv1_triplanar = true
diff --git a/project/materials/grids/tent.tres b/project/materials/grids/tent.tres
deleted file mode 100644
index 6f791f11..00000000
--- a/project/materials/grids/tent.tres
+++ /dev/null
@@ -1,7 +0,0 @@
-[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://cupy5mpdsngcl"]
-
-[ext_resource type="Texture2D" uid="uid://bh68a5vqm5h7l" path="res://textures/grids/tent.png" id="1_ifivq"]
-
-[resource]
-albedo_texture = ExtResource("1_ifivq")
-uv1_triplanar = true
diff --git a/project/objects/characters/player_actor.tscn b/project/objects/characters/player_actor.tscn
deleted file mode 100644
index c499369d..00000000
--- a/project/objects/characters/player_actor.tscn
+++ /dev/null
@@ -1,21 +0,0 @@
-[gd_scene load_steps=3 format=3 uid="uid://cykd1a23ria6k"]
-
-[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7py3r"]
-
-[sub_resource type="CylinderMesh" id="CylinderMesh_66ixd"]
-
-[node name="PlayerActor" type="PlayerActor"]
-
-[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
-shape = SubResource("CapsuleShape3D_7py3r")
-
-[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
-mesh = SubResource("CylinderMesh_66ixd")
-
-[node name="PlayerInput" type="PlayerInput" parent="."]
-unique_name_in_owner = true
-
-[node name="Camera3D" type="ThirdPersonCamera" parent="."]
-transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 4.76837e-07, 1.8675, -2.90407)
diff --git a/project/objects/party_member.tscn b/project/objects/party_member.tscn
new file mode 100644
index 00000000..815bf819
--- /dev/null
+++ b/project/objects/party_member.tscn
@@ -0,0 +1,24 @@
+[gd_scene format=3 uid="uid://dfbdn64i7vfuc"]
+
+[ext_resource type="CharacterData" uid="uid://d28pn4xekwh6p" path="res://data/character_data/fallback_character_data.tres" id="1_0torn"]
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_0torn"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4iifp"]
+
+[node name="PartyMember" type="Character" unique_id=2124931928]
+data = ExtResource("1_0torn")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=2087924260]
+mesh = SubResource("CylinderMesh_0torn")
+skeleton = NodePath("../CollisionShape3D")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=705075802]
+shape = SubResource("CapsuleShape3D_4iifp")
+
+[node name="PartyMemberFollow" type="PartyMemberFollow" parent="." unique_id=1261360781]
+start_active = true
+
+[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=1509674092]
+unique_name_in_owner = true
+avoidance_enabled = true
diff --git a/project/objects/player_character.tscn b/project/objects/player_character.tscn
new file mode 100644
index 00000000..d94c6468
--- /dev/null
+++ b/project/objects/player_character.tscn
@@ -0,0 +1,42 @@
+[gd_scene format=3 uid="uid://dcqd0wo5y5a1g"]
+
+[ext_resource type="CharacterData" uid="uid://bmudhddb0vedg" path="res://data/default_player_character.tres" id="1_jy05a"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_vcg8s"]
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_5kd2n"]
+
+[node name="PlayerCharacter" type="PlayerCharacter" unique_id=159035892]
+data = ExtResource("1_jy05a")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=511026275]
+shape = SubResource("CapsuleShape3D_vcg8s")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=957221075]
+mesh = SubResource("CylinderMesh_5kd2n")
+
+[node name="Camera3D" type="Camera3D" parent="." unique_id=932811285]
+transform = Transform3D(-1, 5.660465e-08, -6.662324e-08, 0, 0.762081, 0.64748174, 8.742278e-08, 0.64748174, -0.762081, 9.536743e-07, 5.8584056, -4.4809494)
+current = true
+far = 1000.0
+
+[node name="PlayerInputState" type="PlayerInputState" parent="." unique_id=1290843255]
+start_active = true
+
+[node name="PlayerMovementState" type="PlayerMovementState" parent="." unique_id=71639209]
+
+[node name="NavMarker" type="NavMarker" parent="." unique_id=2076207950]
+transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 2.0248106, 0, -1.4610382)
+gizmo_extents = 3.0
+
+[node name="NavMarker2" type="NavMarker" parent="." unique_id=786944405]
+transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, -0.54701775, 9.536743e-07, -2.7108493)
+gizmo_extents = 3.0
+
+[node name="NavMarker4" type="NavMarker" parent="." unique_id=1781147686]
+transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 0.8027056, 9.536743e-07, -2.7077246)
+gizmo_extents = 3.0
+
+[node name="NavMarker3" type="NavMarker" parent="." unique_id=430426412]
+transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, -2.0046833, 9.536743e-07, -1.4623514)
+gizmo_extents = 3.0
diff --git a/project/objects/player_vehicle.tscn b/project/objects/player_vehicle.tscn
new file mode 100644
index 00000000..c9282027
--- /dev/null
+++ b/project/objects/player_vehicle.tscn
@@ -0,0 +1,78 @@
+[gd_scene format=3 uid="uid://bedet0our63p0"]
+
+[ext_resource type="PackedScene" uid="uid://dkq8b07op54lx" path="res://assets/vehicles/bike.blend" id="1_qt1cm"]
+[ext_resource type="PackedScene" uid="uid://grb3q5nd2uds" path="res://assets/characters/player_fem/character_fem.blend" id="2_buo3h"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_7eqww"]
+size = Vector3(0.36328125, 0.5884735, 0.9517517)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_we6jx"]
+size = Vector3(0.21679688, 0.29020843, 0.87223816)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_kok0e"]
+size = Vector3(0.8516388, 0.81409013, 1.4329796)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_khxbi"]
+size = Vector3(0.7451172, 0.59351885, 0.5995445)
+
+[node name="PlayerVehicle" type="VehicleBody3D" unique_id=2037675333]
+mass = 400.0
+
+[node name="bike" parent="." unique_id=1819523012 instance=ExtResource("1_qt1cm")]
+transform = Transform3D(1.0000004, 0, 0, 0, 1.0000001, 0, 0, 0, 0.9999994, -0.48627073, 0, 0)
+
+[node name="character_fem" parent="bike" unique_id=111626287 instance=ExtResource("2_buo3h")]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1803684826]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.48627073, 0.7048377, 0.074576735)
+shape = SubResource("BoxShape3D_7eqww")
+
+[node name="CollisionShape3D3" type="CollisionShape3D" parent="." unique_id=781828471]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.48627073, 0.8603005, 0.938115)
+shape = SubResource("BoxShape3D_we6jx")
+
+[node name="CollisionShape3D2" type="CollisionShape3D" parent="." unique_id=1277628977]
+transform = Transform3D(1, 0, 0, 0, 0.9983196, 0.057947356, 0, -0.057947356, 0.9983196, 0.2234863, 0.62375516, 0.6136677)
+shape = SubResource("BoxShape3D_kok0e")
+
+[node name="CollisionShape3D4" type="CollisionShape3D" parent="." unique_id=2030366048]
+transform = Transform3D(1, 0, 0, 0, 0.72955376, -0.6839234, 0, 0.6839234, 0.72955376, 0.2234863, 0.5898677, -0.092960924)
+shape = SubResource("BoxShape3D_khxbi")
+
+[node name="SidecarWheel" type="VehicleWheel3D" parent="." unique_id=1869360183]
+transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 0.7563137, 0.37474027, 0.7443161)
+wheel_roll_influence = 0.3
+wheel_radius = 0.389
+wheel_rest_length = 0.05
+wheel_friction_slip = 1.0
+suspension_travel = 0.1
+suspension_stiffness = 500.0
+suspension_max_force = 10500.0
+damping_compression = 10.0
+damping_relaxation = 11.0
+
+[node name="BackWheel" type="VehicleWheel3D" parent="." unique_id=128773497]
+transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, -0.4791991, 0.37716705, 0.9504494)
+use_as_traction = true
+wheel_roll_influence = 0.3
+wheel_radius = 0.389
+wheel_rest_length = 0.05
+wheel_friction_slip = 1.0
+suspension_travel = 0.1
+suspension_stiffness = 500.0
+suspension_max_force = 10500.0
+damping_compression = 10.0
+damping_relaxation = 11.0
+
+[node name="FrontWheel" type="VehicleWheel3D" parent="." unique_id=1222187628]
+transform = Transform3D(-1, 6.4255117e-09, 8.7186315e-08, 4.371139e-08, 0.90043265, 0.43499538, -7.5710346e-08, 0.43499538, -0.90043265, -0.479199, 0.3713468, -0.73007745)
+use_as_steering = true
+wheel_roll_influence = 0.3
+wheel_radius = 0.389
+wheel_rest_length = 0.05
+wheel_friction_slip = 1.0
+suspension_travel = 0.1
+suspension_stiffness = 500.0
+suspension_max_force = 10500.0
+damping_compression = 10.0
+damping_relaxation = 11.0
diff --git a/project/project.godot b/project/project.godot
index 0ee1b8b4..477ade1f 100644
--- a/project/project.godot
+++ b/project/project.godot
@@ -8,60 +8,53 @@
config_version=5
+[animation]
+
+compatibility/default_parent_skeleton_in_mesh_instance_3d=true
+
[application]
config/name="authority"
-run/main_scene="uid://bqaoxvqgrbi3v"
-config/features=PackedStringArray("4.4", "Forward Plus")
+run/main_scene="uid://1qvpc2ej32pd"
+config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg"
[display]
-window/vsync/vsync_mode=2
+window/size/viewport_width=1920
+window/size/viewport_height=1080
[input]
move_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
-, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
]
}
move_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
-, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
]
}
move_forward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
-, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
-move_back={
+move_backward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
-, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
-]
-}
-look_left={
-"deadzone": 0.2,
-"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
-]
-}
-look_right={
-"deadzone": 0.2,
-"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null)
-]
-}
-look_up={
-"deadzone": 0.2,
-"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null)
-]
-}
-look_down={
-"deadzone": 0.2,
-"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null)
]
}
+
+[physics]
+
+3d/physics_engine="Jolt Physics"
+
+[rendering]
+
+lights_and_shadows/directional_shadow/soft_shadow_filter_quality=3
+lights_and_shadows/positional_shadow/soft_shadow_filter_quality=3
+shading/overrides/force_vertex_shading=true
+anti_aliasing/quality/msaa_3d=1
+anti_aliasing/quality/use_debanding=true
diff --git a/project/scenes/style_test_blockout.scn b/project/scenes/style_test_blockout.scn
new file mode 100644
index 00000000..af2e6443
Binary files /dev/null and b/project/scenes/style_test_blockout.scn differ
diff --git a/project/scenes/terrain_test.tscn b/project/scenes/terrain_test.tscn
new file mode 100644
index 00000000..e955b2b6
--- /dev/null
+++ b/project/scenes/terrain_test.tscn
@@ -0,0 +1,74 @@
+[gd_scene format=3 uid="uid://d2w73ie2k01xg"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_kbmr5"]
+sky_horizon_color = Color(0.6590071, 0.7017287, 0.7452071, 1)
+ground_bottom_color = Color(0.110199995, 0.21208663, 0.29, 1)
+ground_horizon_color = Color(0.6590071, 0.7017287, 0.7452071, 1)
+
+[sub_resource type="Sky" id="Sky_w3uoq"]
+sky_material = SubResource("ProceduralSkyMaterial_kbmr5")
+
+[sub_resource type="Environment" id="Environment_o3i6r"]
+background_mode = 2
+sky = SubResource("Sky_w3uoq")
+
+[sub_resource type="Curve" id="Curve_kbmr5"]
+_limits = [0.0, 1.0, 0.0, 100.0]
+_data = [Vector2(0, 1), 0.0, -0.0015643721, 0, 0, Vector2(60.370926, 0.60930693), -0.018071167, -0.018071167, 0, 0, Vector2(100, 0), 0.0, 0.0, 0, 0]
+point_count = 3
+
+[sub_resource type="Curve" id="Curve_w3uoq"]
+_limits = [0.0, 1.0, 0.0, 101.07341]
+_data = [Vector2(0, 1), 0.0, -0.009893799, 0, 1, Vector2(101.07341, 0), -0.009893799, 0.0, 1, 0]
+point_count = 2
+
+[sub_resource type="Curve" id="Curve_chm2y"]
+_limits = [0.0, 1.0, 0.0, 300.0]
+_data = [Vector2(0, 1), -0.005856493, -0.0020244052, 0, 0, Vector2(195.24551, 0.17229712), -0.0043557375, -0.0043557375, 0, 0, Vector2(300, 0), 0.00031304389, -0.05797184, 0, 0]
+point_count = 3
+
+[sub_resource type="BoxMesh" id="BoxMesh_kbmr5"]
+
+[node name="Node3D" type="Node3D" unique_id=289500437]
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=212607290]
+environment = SubResource("Environment_o3i6r")
+
+[node name="Terrain" type="Terrain" parent="." unique_id=1169843565]
+side_length = 1000
+chunk_size = 100
+thread_count = 5
+
+[node name="TerrainModifierPath" type="TerrainModifierPath" parent="Terrain" unique_id=462259542]
+transform = Transform3D(2.7896824, 0, 0, 0, 1, 0, 0, 0, 3.9856973, 188.70874, 151.32993, 528.5824)
+curve_left = SubResource("Curve_kbmr5")
+curve_right = SubResource("Curve_w3uoq")
+
+[node name="TerrainModifierPathPoint" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=1975236067]
+transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.9999999, -6.678116, -33.99875, -74.15768)
+
+[node name="TerrainModifierPathPoint5" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=2007122252]
+transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, 41.63815, 4.6532288, -38.86402)
+
+[node name="TerrainModifierPathPoint2" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=88875414]
+transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, 4.2666435, -27.249336, 5.506424)
+
+[node name="TerrainModifierPathPoint3" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=910243114]
+transform = Transform3D(-0.08673841, 0, 0.9962309, 0, 1, 0, -0.9962308, 0, -0.08673839, 77.83667, 84.083954, 12.383522)
+
+[node name="TerrainModifierPathPoint4" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=738726374]
+transform = Transform3D(-0.08673839, 0, 0.9962309, 0, 1, 0, -0.9962308, 0, -0.08673841, 124.05687, -50.373947, -21.531578)
+
+[node name="TerrainModifierPathPoint6" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=868243973]
+transform = Transform3D(-0.08673839, 0, 0.9962309, 0, 1, 0, -0.99623066, 0, -0.08673841, 163.78015, -50.373917, 24.09636)
+
+[node name="TerrainModifierPathPoint7" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=601656436]
+transform = Transform3D(-0.08673839, 0, 0.9962309, 0, 1, 0, -0.99623054, 0, -0.08673841, 106.59272, 12.878235, 75.88286)
+
+[node name="TerrainModifierDistance8" type="TerrainModifierDistance" parent="Terrain" unique_id=1993490768]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 784.91595, 251.11382, 135.92102)
+distance_weight_curve = SubResource("Curve_chm2y")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1089775425]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 625.56537, 191.07982, 795.4992)
+mesh = SubResource("BoxMesh_kbmr5")
diff --git a/project/scenes/test_world.tscn b/project/scenes/test_world.tscn
new file mode 100644
index 00000000..663f3d0d
--- /dev/null
+++ b/project/scenes/test_world.tscn
@@ -0,0 +1,50 @@
+[gd_scene format=3 uid="uid://cv0ub3llm3jew"]
+
+[ext_resource type="PackedScene" uid="uid://dcqd0wo5y5a1g" path="res://objects/player_character.tscn" id="1_kyfjp"]
+[ext_resource type="PackedScene" uid="uid://dfbdn64i7vfuc" path="res://objects/party_member.tscn" id="2_amxg5"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_kyfjp"]
+sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
+ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
+
+[sub_resource type="Sky" id="Sky_amxg5"]
+sky_material = SubResource("ProceduralSkyMaterial_kyfjp")
+
+[sub_resource type="Environment" id="Environment_3263u"]
+background_mode = 2
+sky = SubResource("Sky_amxg5")
+tonemap_mode = 2
+glow_enabled = true
+
+[sub_resource type="NavigationMesh" id="NavigationMesh_amxg5"]
+vertices = PackedVector3Array(-8.5, 0.5, -9, -6.5, 0.5, -9, -6.5, 0.5, -49.5, -49.5, 0.5, -7.5, -8.75, 0.5, -7.25, -49.5, 0.5, -49.5, -4.25, 0.5, -9, -4.25, 0.5, -49.5, -2, 0.5, -8.75, 49.5, 0.5, -6.5, 49.5, 0.5, -49.5, -2, 0.5, -6.5, -7.75, 2.5, -8, -7.75, 2.5, -5.25, -3, 2.5, -5.25, -3, 2.5, -8, -49.5, 0.5, -5.75, -8.75, 0.5, -6, -2, 0.5, -4.5, -3.75, 0.5, -4.25, -3.5, 0.5, 49.5, 49.5, 0.5, 49.5, -8.75, 0.5, -4.5, -7, 0.5, -4.25, -49.5, 0.5, 49.5, -7.25, 0.5, 49.5)
+polygons = [PackedInt32Array(2, 1, 0), PackedInt32Array(4, 3, 0), PackedInt32Array(0, 3, 5), PackedInt32Array(0, 5, 2), PackedInt32Array(2, 7, 1), PackedInt32Array(1, 7, 6), PackedInt32Array(6, 7, 8), PackedInt32Array(8, 7, 10), PackedInt32Array(8, 10, 9), PackedInt32Array(9, 11, 8), PackedInt32Array(15, 14, 12), PackedInt32Array(12, 14, 13), PackedInt32Array(17, 16, 4), PackedInt32Array(4, 16, 3), PackedInt32Array(18, 11, 9), PackedInt32Array(18, 9, 19), PackedInt32Array(19, 9, 20), PackedInt32Array(20, 9, 21), PackedInt32Array(16, 17, 22), PackedInt32Array(22, 23, 16), PackedInt32Array(16, 23, 25), PackedInt32Array(16, 25, 24), PackedInt32Array(23, 19, 25), PackedInt32Array(25, 19, 20)]
+
+[node name="TestWorld" type="Node3D" unique_id=262419127]
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1185961481]
+environment = SubResource("Environment_3263u")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1382994887]
+transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
+shadow_enabled = true
+
+[node name="PlayerCharacter" parent="." unique_id=1435471129 instance=ExtResource("1_kyfjp")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+
+[node name="PartyMember" parent="." unique_id=2124931928 instance=ExtResource("2_amxg5")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 5)
+
+[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=357996274]
+navigation_mesh = SubResource("NavigationMesh_amxg5")
+
+[node name="CSGCombiner3D" type="CSGCombiner3D" parent="NavigationRegion3D" unique_id=885387983]
+use_collision = true
+
+[node name="CSGBox3D" type="CSGBox3D" parent="NavigationRegion3D/CSGCombiner3D" unique_id=1853081325]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
+size = Vector3(100, 1, 100)
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="NavigationRegion3D/CSGCombiner3D" unique_id=40055740]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.293318, 1.1054688, -6.6544046)
+size = Vector3(5.5302734, 2.2109375, 3.6298828)
diff --git a/project/textures/grids/bricks.png b/project/textures/grids/bricks.png
deleted file mode 100644
index 788d510e..00000000
Binary files a/project/textures/grids/bricks.png and /dev/null differ
diff --git a/project/textures/grids/grass.png b/project/textures/grids/grass.png
deleted file mode 100644
index 1d54a6fe..00000000
Binary files a/project/textures/grids/grass.png and /dev/null differ
diff --git a/project/textures/grids/mud.png b/project/textures/grids/mud.png
deleted file mode 100644
index 529ceab8..00000000
Binary files a/project/textures/grids/mud.png and /dev/null differ
diff --git a/project/textures/grids/rock.png b/project/textures/grids/rock.png
deleted file mode 100644
index 3380a55a..00000000
Binary files a/project/textures/grids/rock.png and /dev/null differ
diff --git a/project/textures/grids/rock.png.import b/project/textures/grids/rock.png.import
deleted file mode 100644
index 12ea2192..00000000
--- a/project/textures/grids/rock.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://cawkm8y8ejdjs"
-path="res://.godot/imported/rock.png-674cefcf962a6d115a3102bd49c5ab77.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://textures/grids/rock.png"
-dest_files=["res://.godot/imported/rock.png-674cefcf962a6d115a3102bd49c5ab77.ctex"]
-
-[params]
-
-compress/mode=0
-compress/high_quality=false
-compress/lossy_quality=0.7
-compress/hdr_compression=1
-compress/normal_map=0
-compress/channel_pack=0
-mipmaps/generate=false
-mipmaps/limit=-1
-roughness/mode=0
-roughness/src_normal=""
-process/fix_alpha_border=true
-process/premult_alpha=false
-process/normal_map_invert_y=false
-process/hdr_as_srgb=false
-process/hdr_clamp_exposure=false
-process/size_limit=0
-detect_3d/compress_to=1
diff --git a/project/textures/grids/tent.png b/project/textures/grids/tent.png
deleted file mode 100644
index b21693da..00000000
Binary files a/project/textures/grids/tent.png and /dev/null differ