diff --git a/.gitignore b/.gitignore
index b8680775..3f82628a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,10 +5,16 @@
# When configure fails, SCons outputs these
config.log
.sconf_temp
+.config
+
+# build artefacts
+.cache/
+*.o
+compile_commands.json
engine/.github
project/.godot
-build/PROJECT.pck
-build/PROJECT.x86_64
-build/PROJECT.exe
+build/authority.pck
+build/authority.x86_64
+build/authority.exe
build.zip
diff --git a/design/monarchist_camp.svg b/design/monarchist_camp.svg
new file mode 100644
index 00000000..23a1e008
--- /dev/null
+++ b/design/monarchist_camp.svg
@@ -0,0 +1,183 @@
+
+
+
+
diff --git a/engine b/engine
index 215acd52..e531f3eb 160000
--- a/engine
+++ b/engine
@@ -1 +1 @@
-Subproject commit 215acd52e82f4c575abb715e25e54558deeef998
+Subproject commit e531f3eb7b13a9adaaf5b15ac9b3aebc4c2030cd
diff --git a/justfile b/justfile
index ad0c5d09..48c61a88 100644
--- a/justfile
+++ b/justfile
@@ -1,6 +1,6 @@
set export
-BUILD_NAME := "change_me"
+BUILD_NAME := "authority"
build: format
# Compiling Editor
diff --git a/modules/PROJECT/register_types.cpp b/modules/PROJECT/register_types.cpp
deleted file mode 100644
index a367b16d..00000000
--- a/modules/PROJECT/register_types.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "register_types.h"
-
-#include "core/object/class_db.h"
-
-void initialize_PROJECT_module(ModuleInitializationLevel p_level) {
- if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
- return;
- }
-}
-
-void uninitialize_PROJECT_module(ModuleInitializationLevel p_level) {
- if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
- return;
- }
-}
diff --git a/modules/PROJECT/register_types.h b/modules/PROJECT/register_types.h
deleted file mode 100644
index 2a1d0257..00000000
--- a/modules/PROJECT/register_types.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef PROJECT_REGISTER_TYPES_H
-#define PROJECT_REGISTER_TYPES_H
-
-#include "modules/register_module_types.h"
-
-void initialize_PROJECT_module(ModuleInitializationLevel p_level);
-void uninitialize_PROJECT_module(ModuleInitializationLevel p_level);
-
-#endif // !PROJECT_REGISTER_TYPES_H
diff --git a/modules/PROJECT/SCsub b/modules/authority/SCsub
similarity index 100%
rename from modules/PROJECT/SCsub
rename to modules/authority/SCsub
diff --git a/modules/authority/__pycache__/config.cpython-313.pyc b/modules/authority/__pycache__/config.cpython-313.pyc
new file mode 100644
index 00000000..8d3597ed
Binary files /dev/null and b/modules/authority/__pycache__/config.cpython-313.pyc differ
diff --git a/modules/authority/actor_body.cpp b/modules/authority/actor_body.cpp
new file mode 100644
index 00000000..1f995409
--- /dev/null
+++ b/modules/authority/actor_body.cpp
@@ -0,0 +1,81 @@
+#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
new file mode 100644
index 00000000..fa7fbaac
--- /dev/null
+++ b/modules/authority/actor_body.h
@@ -0,0 +1,37 @@
+#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/PROJECT/config.py b/modules/authority/config.py
similarity index 100%
rename from modules/PROJECT/config.py
rename to modules/authority/config.py
diff --git a/modules/authority/game_state.cpp b/modules/authority/game_state.cpp
new file mode 100644
index 00000000..d10da4fd
--- /dev/null
+++ b/modules/authority/game_state.cpp
@@ -0,0 +1,18 @@
+#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
new file mode 100644
index 00000000..0170bd94
--- /dev/null
+++ b/modules/authority/game_state.h
@@ -0,0 +1,19 @@
+#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/PROJECT/macros.h b/modules/authority/macros.h
similarity index 100%
rename from modules/PROJECT/macros.h
rename to modules/authority/macros.h
diff --git a/modules/authority/player_actor.cpp b/modules/authority/player_actor.cpp
new file mode 100644
index 00000000..0401ec04
--- /dev/null
+++ b/modules/authority/player_actor.cpp
@@ -0,0 +1,42 @@
+#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
new file mode 100644
index 00000000..6cdcdc59
--- /dev/null
+++ b/modules/authority/player_actor.h
@@ -0,0 +1,20 @@
+#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_input.cpp b/modules/authority/player_input.cpp
new file mode 100644
index 00000000..bfd00590
--- /dev/null
+++ b/modules/authority/player_input.cpp
@@ -0,0 +1,146 @@
+#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
new file mode 100644
index 00000000..977dc2a1
--- /dev/null
+++ b/modules/authority/player_input.h
@@ -0,0 +1,58 @@
+#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/register_types.cpp b/modules/authority/register_types.cpp
new file mode 100644
index 00000000..8d674102
--- /dev/null
+++ b/modules/authority/register_types.cpp
@@ -0,0 +1,34 @@
+#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 "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()));
+}
+
+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/register_types.h b/modules/authority/register_types.h
new file mode 100644
index 00000000..d55a194f
--- /dev/null
+++ b/modules/authority/register_types.h
@@ -0,0 +1,9 @@
+#ifndef authority_REGISTER_TYPES_H
+#define authority_REGISTER_TYPES_H
+
+#include "modules/register_module_types.h"
+
+void initialize_authority_module(ModuleInitializationLevel p_level);
+void uninitialize_authority_module(ModuleInitializationLevel p_level);
+
+#endif // !authority_REGISTER_TYPES_H
diff --git a/modules/authority/third_person_camera.cpp b/modules/authority/third_person_camera.cpp
new file mode 100644
index 00000000..04077cc6
--- /dev/null
+++ b/modules/authority/third_person_camera.cpp
@@ -0,0 +1,54 @@
+#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
new file mode 100644
index 00000000..9ec2935d
--- /dev/null
+++ b/modules/authority/third_person_camera.h
@@ -0,0 +1,29 @@
+#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/project/export_presets.cfg b/project/export_presets.cfg
index a04762ac..fae5030f 100644
--- a/project/export_presets.cfg
+++ b/project/export_presets.cfg
@@ -9,7 +9,7 @@ custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
-export_path="../build/PROJECT.x86_64"
+export_path="../build/authority.x86_64"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
@@ -51,7 +51,7 @@ custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
-export_path="../build/PROJECT.exe"
+export_path="../build/authority.exe"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
diff --git a/project/level_props/tent.tscn b/project/level_props/tent.tscn
new file mode 100644
index 00000000..065f9c5c
--- /dev/null
+++ b/project/level_props/tent.tscn
@@ -0,0 +1,13 @@
+[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
new file mode 100644
index 00000000..c31fb667
--- /dev/null
+++ b/project/locales/city.tscn
@@ -0,0 +1,39 @@
+[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
new file mode 100644
index 00000000..1911e356
--- /dev/null
+++ b/project/materials/grids/bricks.tres
@@ -0,0 +1,7 @@
+[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
new file mode 100644
index 00000000..ebb67f75
--- /dev/null
+++ b/project/materials/grids/grass.tres
@@ -0,0 +1,7 @@
+[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
new file mode 100644
index 00000000..6b69d38e
--- /dev/null
+++ b/project/materials/grids/mud.tres
@@ -0,0 +1,7 @@
+[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
new file mode 100644
index 00000000..6f791f11
--- /dev/null
+++ b/project/materials/grids/tent.tres
@@ -0,0 +1,7 @@
+[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
new file mode 100644
index 00000000..c499369d
--- /dev/null
+++ b/project/objects/characters/player_actor.tscn
@@ -0,0 +1,21 @@
+[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/project.godot b/project/project.godot
index bd5ab3b0..0ee1b8b4 100644
--- a/project/project.godot
+++ b/project/project.godot
@@ -10,6 +10,58 @@ config_version=5
[application]
-config/name="PROJECT"
+config/name="authority"
+run/main_scene="uid://bqaoxvqgrbi3v"
config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg"
+
+[display]
+
+window/vsync/vsync_mode=2
+
+[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={
+"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)
+]
+}
diff --git a/project/textures/grids/bricks.png b/project/textures/grids/bricks.png
new file mode 100644
index 00000000..788d510e
Binary files /dev/null and b/project/textures/grids/bricks.png differ
diff --git a/project/textures/grids/bricks.png.import b/project/textures/grids/bricks.png.import
new file mode 100644
index 00000000..84da4842
--- /dev/null
+++ b/project/textures/grids/bricks.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cd4cchmulwnc5"
+path.s3tc="res://.godot/imported/bricks.png-217e783fed9aafaab4854f1be96e4aed.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/grids/bricks.png"
+dest_files=["res://.godot/imported/bricks.png-217e783fed9aafaab4854f1be96e4aed.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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/textures/grids/grass.png b/project/textures/grids/grass.png
new file mode 100644
index 00000000..1d54a6fe
Binary files /dev/null and b/project/textures/grids/grass.png differ
diff --git a/project/textures/grids/grass.png.import b/project/textures/grids/grass.png.import
new file mode 100644
index 00000000..10e02de2
--- /dev/null
+++ b/project/textures/grids/grass.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://f8djywm2jlah"
+path.s3tc="res://.godot/imported/grass.png-94a6319b2d9ea30e834183f2653f3455.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/grids/grass.png"
+dest_files=["res://.godot/imported/grass.png-94a6319b2d9ea30e834183f2653f3455.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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/textures/grids/mud.png b/project/textures/grids/mud.png
new file mode 100644
index 00000000..529ceab8
Binary files /dev/null and b/project/textures/grids/mud.png differ
diff --git a/project/textures/grids/mud.png.import b/project/textures/grids/mud.png.import
new file mode 100644
index 00000000..7a14f648
--- /dev/null
+++ b/project/textures/grids/mud.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://br64q04tpxmli"
+path.s3tc="res://.godot/imported/mud.png-f0a13a766854ada7e9976a2898dea1c2.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/grids/mud.png"
+dest_files=["res://.godot/imported/mud.png-f0a13a766854ada7e9976a2898dea1c2.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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/textures/grids/rock.png b/project/textures/grids/rock.png
new file mode 100644
index 00000000..3380a55a
Binary files /dev/null and b/project/textures/grids/rock.png differ
diff --git a/project/textures/grids/rock.png.import b/project/textures/grids/rock.png.import
new file mode 100644
index 00000000..12ea2192
--- /dev/null
+++ b/project/textures/grids/rock.png.import
@@ -0,0 +1,34 @@
+[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
new file mode 100644
index 00000000..b21693da
Binary files /dev/null and b/project/textures/grids/tent.png differ
diff --git a/project/textures/grids/tent.png.import b/project/textures/grids/tent.png.import
new file mode 100644
index 00000000..1381a79f
--- /dev/null
+++ b/project/textures/grids/tent.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bh68a5vqm5h7l"
+path.s3tc="res://.godot/imported/tent.png-683c31f751d990e004be3a99db100a94.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/grids/tent.png"
+dest_files=["res://.godot/imported/tent.png-683c31f751d990e004be3a99db100a94.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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