From aa5e8c67208ed4143f6ba0d89a1281a3db879c6c Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:18:21 +0300 Subject: [PATCH] add menu, settings, world generation --- clients/godot/config/loader.gd | 7 +- clients/godot/entities/ents.gd | 21 +- clients/godot/main.gd | 574 +++++++++--------- clients/godot/main.tscn | 656 ++++++++++++++++++++- clients/godot/models/generator.gd | 62 +- clients/godot/network/networks.gd | 4 +- clients/godot/player/controller.gd | 34 +- clients/godot/player/weapons.gd | 223 +++++++ clients/godot/player/weapons.gd.uid | 1 + clients/godot/shaders/terrain.gdshader | 28 + clients/godot/shaders/terrain.gdshader.uid | 1 + clients/godot/ui/hud.gd | 164 ++++++ clients/godot/ui/hud.gd.uid | 1 + clients/godot/ui/login_menu.gd | 61 ++ clients/godot/ui/login_menu.gd.uid | 1 + clients/godot/ui/menu.gd | 41 ++ clients/godot/ui/menu.gd.uid | 1 + clients/godot/ui/pause_menu.gd | 45 ++ clients/godot/ui/pause_menu.gd.uid | 1 + clients/godot/ui/settings_menu.gd | 130 ++++ clients/godot/ui/settings_menu.gd.uid | 1 + clients/godot/world/chunk.gd | 485 ++++++++++----- clients/godot/world/terrain.gd | 4 +- clients/godot/world/world.gd | 120 ++-- 24 files changed, 2110 insertions(+), 556 deletions(-) create mode 100644 clients/godot/player/weapons.gd create mode 100644 clients/godot/player/weapons.gd.uid create mode 100644 clients/godot/shaders/terrain.gdshader create mode 100644 clients/godot/shaders/terrain.gdshader.uid create mode 100644 clients/godot/ui/hud.gd create mode 100644 clients/godot/ui/hud.gd.uid create mode 100644 clients/godot/ui/login_menu.gd create mode 100644 clients/godot/ui/login_menu.gd.uid create mode 100644 clients/godot/ui/menu.gd create mode 100644 clients/godot/ui/menu.gd.uid create mode 100644 clients/godot/ui/pause_menu.gd create mode 100644 clients/godot/ui/pause_menu.gd.uid create mode 100644 clients/godot/ui/settings_menu.gd create mode 100644 clients/godot/ui/settings_menu.gd.uid diff --git a/clients/godot/config/loader.gd b/clients/godot/config/loader.gd index bf81373..33e19d3 100644 --- a/clients/godot/config/loader.gd +++ b/clients/godot/config/loader.gd @@ -446,7 +446,7 @@ func save_config() -> bool: config_file.set_value(section, key, config[section][key]) # Update meta info - var current_time = Time.get_unix_time_from_system() + var _current_time = Time.get_unix_time_from_system() config_file.set_value("meta", "last_used_version", "1.0.0") config_file.set_value("meta", "settings_version", 1) @@ -585,7 +585,7 @@ func apply_audio_settings(): print("Audio settings applied") func apply_control_settings(): - var controls = get_controls_config() + var _controls = get_controls_config() # Input map would be updated here # For example: @@ -620,8 +620,7 @@ func get_key_name(keycode: int) -> String: 9: "Tab", 13: "Enter", 44: "Print Screen", - 192: "`", - 86: "V" + 192: "`" } return key_names.get(keycode, "Key " + str(keycode)) diff --git a/clients/godot/entities/ents.gd b/clients/godot/entities/ents.gd index 5ddbdf8..228fcd5 100644 --- a/clients/godot/entities/ents.gd +++ b/clients/godot/entities/ents.gd @@ -221,7 +221,7 @@ func create_entity_model(entity_type: int, data: Dictionary) -> MeshInstance3D: return model -func add_collision_to_entity(entity_node: Node3D, entity_type: int, data: Dictionary): +func add_collision_to_entity(entity_node: Node3D, entity_type: int, _data: Dictionary): var collision_shape = CollisionShape3D.new() match entity_type: @@ -251,7 +251,7 @@ func add_collision_to_entity(entity_node: Node3D, entity_type: int, data: Dictio entity_node.add_child(collision_shape) -func add_interaction_area(entity_node: Node3D, entity_type: int, data: Dictionary): +func add_interaction_area(entity_node: Node3D, _entity_type: int, _data: Dictionary): var area = Area3D.new() area.collision_layer = 2 # Interaction layer area.collision_mask = 1 # Player layer @@ -269,14 +269,15 @@ func add_interaction_area(entity_node: Node3D, entity_type: int, data: Dictionar entity_node.add_child(area) func add_health_display(entity_node: Node3D, data: Dictionary): - # Create health bar UI var health_bar = Sprite3D.new() - health_bar.texture = preload("res://textures/health_bar.svg") health_bar.pixel_size = 0.01 health_bar.billboard = BaseMaterial3D.BILLBOARD_ENABLED health_bar.position = Vector3(0, 1.5, 0) - # Store health data + var material = StandardMaterial3D.new() + material.albedo_color = Color(0.8, 0.2, 0.2) + health_bar.material_override = material + entity_node.set_meta("health", data.get("health", 100)) entity_node.set_meta("max_health", data.get("max_health", 100)) @@ -305,11 +306,11 @@ func update_health_display(entity_node: Node3D, data: Dictionary): var health_ratio = float(health) / max_health health_bar.scale.x = health_ratio -func update_entity_animation(entity_node: Node3D, state: String): +func update_entity_animation(_entity_node: Node3D, _state: String): # This would typically control animation player pass -func _on_entity_interacted(camera, event, position, normal, shape_idx, entity_node): +func _on_entity_interacted(_camera, event, _position, _normal, _shape_idx, entity_node): if event is InputEventMouseButton and event.pressed: # Find entity ID from node var entity_name = entity_node.name @@ -347,7 +348,7 @@ func _process(delta): # Network message handlers func handle_spawn_message(data: Dictionary): - var entity_id = data.get("entity_id") + var _entity_id = data.get("entity_id") var entity_type = data.get("entity_type") var position = Vector3( data.position.x, @@ -365,6 +366,4 @@ func handle_despawn_message(data: Dictionary): var entity_id = data.get("entity_id") despawn_entity(entity_id) -signal entity_interacted(entity_id: int) -signal entity_spawned(entity_id: int, entity_type: int) -signal entity_despawned(entity_id: int) \ No newline at end of file +signal entity_interacted(entity_id: int) \ No newline at end of file diff --git a/clients/godot/main.gd b/clients/godot/main.gd index a412719..7f8e04b 100644 --- a/clients/godot/main.gd +++ b/clients/godot/main.gd @@ -3,78 +3,87 @@ extends Node3D class_name GameClient # Scene references -@onready var world = $World -@onready var player = $Player -@onready var ui = $UI -@onready var network_manager = $NetworkManager -@onready var model_generator = $ModelGenerator -@onready var entity_manager = $EntityManager # ADDED -@onready var config_loader = $ConfigLoader # ADDED +@onready var world: GameWorld = $World +@onready var player: PlayerController = $Player +@onready var ui: Control = $UI +@onready var network_manager: NetworkManager = $NetworkManager +@onready var model_generator: ModelGenerator = $ModelGenerator +@onready var entity_manager: EntityManager = $EntityManager +@onready var config_loader: ConfigLoader = $ConfigLoader + +# UI references +@onready var main_menu: MainMenu = $UI/MainMenu +@onready var login_menu: LoginMenu = $UI/LoginMenu +@onready var settings_menu: SettingsMenu = $UI/SettingsMenu +@onready var hud: GameHUD = $UI/HUD +@onready var pause_menu: PauseMenu = $PauseMenu # Game state var current_chunks: Dictionary = {} var entities: Dictionary = {} var player_entity_id: int = 0 var local_player_position: Vector3 = Vector3.ZERO +var game_state: String = "menu" # menu, playing, paused # Settings var server_address: String = "127.0.0.1" var server_port: int = 8080 var username: String = "" +var password: String = "" var render_distance: int = 3 -var chunk_size: int = 16 +var chunk_size: int = 32 var chunk_height: int = 64 # Performance var lod_distances: Array = [20.0, 40.0, 80.0] var max_entities: int = 100 -func setup_input(): - # Input is already configured via project settings. - # Additional runtime input setup can go here. - pass - func _ready(): load_settings() + setup_menu_signals() setup_network_signals() - setup_input() - - # Initialize world + initialize_world() + show_main_menu() + +func _process(delta): + if game_state == "playing": + update_gameplay(delta) + +func _input(event): + if game_state == "playing": + if event is InputEventKey: + if event.pressed and event.keycode == KEY_F1: + hud.toggle_debug() + elif event.pressed and event.keycode == KEY_F2: + world.toggle_wireframe() + elif event.pressed and event.keycode == KEY_F3: + network_manager.prediction_enabled = not network_manager.prediction_enabled + +func initialize_world(): world.initialize(chunk_size, chunk_height, render_distance, lod_distances) - - # Initialize entity manager if entity_manager: entity_manager.max_entities = max_entities entity_manager.despawn_distance = 150.0 -func _process(delta): - if network_manager.connection_state == NetworkManager.ConnectionState.CONNECTED: - # Update player position to server - var current_position = player.global_transform.origin - if current_position.distance_to(local_player_position) > 0.1: - local_player_position = current_position - network_manager.update_player_position( - current_position, - player.rotation, - Vector3.ZERO # Simplified velocity - ) - - # Request chunks around player - request_nearby_chunks(current_position) - - # Update world LOD - world.update_lod(current_position) +func setup_menu_signals(): + main_menu.play_pressed.connect(_on_play_pressed) + main_menu.local_play_pressed.connect(_on_local_play_pressed) + main_menu.settings_pressed.connect(_on_main_settings_pressed) + main_menu.quit_pressed.connect(_on_quit_pressed) + + login_menu.login_submitted.connect(_on_login_submitted) + login_menu.back_pressed.connect(_on_login_back_pressed) + + settings_menu.back_pressed.connect(_on_settings_back_pressed) + settings_menu.settings_saved.connect(_on_settings_saved) + + hud.chat_submitted.connect(_on_chat_submitted) + + pause_menu.resume_pressed.connect(_on_resume_pressed) + pause_menu.settings_pressed.connect(_on_pause_settings_pressed) + pause_menu.disconnect_pressed.connect(_on_disconnect_pressed) + pause_menu.quit_pressed.connect(_on_quit_pressed) -func _input(event): - if event is InputEventKey: - if event.pressed and event.keycode == KEY_F1: - ui.toggle_debug_info() - elif event.pressed and event.keycode == KEY_F2: - world.toggle_wireframe() - elif event.pressed and event.keycode == KEY_F3: - network_manager.prediction_enabled = !network_manager.prediction_enabled - -# Network setup func setup_network_signals(): network_manager.connection_state_changed.connect(_on_connection_state_changed) network_manager.authentication_result.connect(_on_authentication_result) @@ -82,45 +91,167 @@ func setup_network_signals(): network_manager.error_occurred.connect(_on_error_occurred) network_manager.player_position_corrected.connect(_on_position_corrected) +func show_main_menu(): + game_state = "menu" + main_menu.visible = true + login_menu.visible = false + settings_menu.visible = false + hud.visible = false + pause_menu.visible = false + get_tree().paused = false + +func show_login_menu(): + main_menu.visible = false + login_menu.visible = true + settings_menu.visible = false + login_menu.set_credentials(server_address, server_port, username, password) + +func show_settings_menu(from: String = "main"): + settings_menu.visible = true + main_menu.visible = false + login_menu.visible = false + hud.visible = false + settings_menu.set_meta("from", from) + +func show_hud(): + main_menu.visible = false + login_menu.visible = false + settings_menu.visible = false + hud.show_hud() + pause_menu.visible = false + +func start_game(): + game_state = "playing" + show_hud() + player.visible = true + player.set_process(true) + player.set_physics_process(true) + hud.update_health(100, 100) + hud.update_mana(100, 100) + hud.update_xp(0, 100) + generate_local_chunks(player.global_transform.origin) + +func pause_game(): + if game_state == "playing": + game_state = "paused" + pause_menu.visible = true + get_tree().paused = true + +func resume_game(): + game_state = "playing" + pause_menu.visible = false + get_tree().paused = false + +func update_gameplay(_delta): + var current_position = player.global_transform.origin + if current_position.distance_to(local_player_position) > 0.1: + local_player_position = current_position + if network_manager.connection_state == NetworkManager.ConnectionState.CONNECTED: + network_manager.update_player_position( + current_position, + player.rotation, + Vector3.ZERO + ) + request_nearby_chunks(current_position) + world.update_lod(current_position) + +func request_nearby_chunks(pos: Vector3): + var chunk_x = int(floor(pos.x / chunk_size)) + var chunk_z = int(floor(pos.z / chunk_size)) + for x in range(-render_distance, render_distance + 1): + for z in range(-render_distance, render_distance + 1): + var target_x = chunk_x + x + var target_z = chunk_z + z + var chunk_key = Vector2(target_x, target_z) + if not current_chunks.has(chunk_key): + if network_manager.connection_state == NetworkManager.ConnectionState.CONNECTED: + network_manager.request_chunk(target_x, target_z) + else: + world.create_chunk(target_x, target_z) + current_chunks[chunk_key] = true + +func generate_local_chunks(pos: Vector3): + current_chunks.clear() + request_nearby_chunks(pos) + +func clear_world(): + for entity_id in entities.keys(): + despawn_entity(entity_id) + entities.clear() + current_chunks.clear() + world.clear_chunks() + func connect_to_server(): - print("Connecting to server...") - network_manager.connect_to_server(server_address, server_port, NetworkManager.ProtocolType.BINARY) + network_manager.server_address = login_menu.get_server_address() + network_manager.server_port = login_menu.get_server_port() + network_manager.connect_to_server( + network_manager.server_address, + network_manager.server_port, + NetworkManager.ProtocolType.BINARY + ) func disconnect_from_server(): - print("Disconnecting from server...") network_manager.disconnect_from_server() clear_world() -func authenticate(): - if username == "": - ui.show_login_panel() - else: - # In a real game, you'd use proper authentication - var password = "demo_password" # This should come from UI - network_manager.authenticate(username, password) +@warning_ignore("shadowed_variable") +func authenticate(user: String, password: String): + username = user + network_manager.authenticate(user, password) -# Message handlers -func _on_message_received(message_type: int, data): - match message_type: - NetworkManager.MessageType.MESSAGE_TYPE_CHUNK_DATA: - handle_chunk_data(data) - NetworkManager.MessageType.MESSAGE_TYPE_ENTITY_SPAWN: - handle_entity_spawn(data) - NetworkManager.MessageType.MESSAGE_TYPE_ENTITY_UPDATE: - handle_entity_update(data) - NetworkManager.MessageType.MESSAGE_TYPE_ENTITY_DESPAWN: - handle_entity_despawn(data) - NetworkManager.MessageType.MESSAGE_TYPE_PLAYER_POSITION_CORRECTION: - handle_position_correction(data) - NetworkManager.MessageType.MESSAGE_TYPE_CHAT_MESSAGE: - handle_chat_message(data) - NetworkManager.MessageType.MESSAGE_TYPE_INVENTORY_UPDATE: - handle_inventory_update(data) - NetworkManager.MessageType.MESSAGE_TYPE_HEALTH_UPDATE: - handle_health_update(data) +func spawn_entity(entity_id: int, entity_type: String, pos: Vector3, data: Dictionary): + if entities.size() >= max_entities: + remove_farthest_entity(pos) + var entity_node = create_entity_node(entity_type, data) + entity_node.global_transform.origin = pos + entity_node.name = "Entity_%s" % entity_id + add_child(entity_node) + entities[entity_id] = { + "node": entity_node, + "type": entity_type, + "data": data, + "position": pos + } + +func update_entity(entity_id: int, data: Dictionary): + if entities.has(entity_id): + var entity = entities[entity_id] + if entity.node and data.has("position"): + var new_pos = Vector3(data.position.x, data.position.y, data.position.z) + entity.node.global_transform.origin = entity.node.global_transform.origin.lerp(new_pos, 0.5) + entity.position = new_pos + +func despawn_entity(entity_id: int): + if entities.has(entity_id): + var entity = entities[entity_id] + if entity.node: + entity.node.queue_free() + entities.erase(entity_id) + +func remove_farthest_entity(from_position: Vector3): + var farthest_id = -1 + var farthest_distance = 0.0 + for entity_id in entities.keys(): + var entity = entities[entity_id] + var distance = entity.position.distance_to(from_position) + if distance > farthest_distance: + farthest_distance = distance + farthest_id = entity_id + if farthest_id != -1: + despawn_entity(farthest_id) + +func create_entity_node(entity_type: String, data: Dictionary) -> Node3D: + var entity_node = Node3D.new() + var model = model_generator.generate_model(entity_type, data) + if model: + entity_node.add_child(model) + var collision = CollisionShape3D.new() + collision.shape = SphereShape3D.new() + collision.shape.radius = 0.5 + entity_node.add_child(collision) + return entity_node func handle_chunk_data(data: PackedByteArray): - # Parse chunk data (simplified - would need proper deserialization) var json_string = data.get_string_from_utf8() if json_string: var json = JSON.new() @@ -128,8 +259,6 @@ func handle_chunk_data(data: PackedByteArray): var chunk_data = json.data var chunk_x = chunk_data.chunk_x var chunk_z = chunk_data.chunk_z - - # Create chunk in world world.create_chunk(chunk_x, chunk_z, chunk_data) current_chunks[Vector2(chunk_x, chunk_z)] = true @@ -141,17 +270,11 @@ func handle_entity_spawn(data: PackedByteArray): var entity_data = json.data var entity_id = entity_data.entity_id var entity_type = entity_data.entity_type - var position = Vector3( - entity_data.position.x, - entity_data.position.y, - entity_data.position.z - ) - - # Use entity manager instead of direct spawn + var pos = Vector3(entity_data.position.x, entity_data.position.y, entity_data.position.z) if entity_manager: entity_manager.handle_spawn_message(entity_data) else: - spawn_entity(entity_id, entity_type, position, entity_data) + spawn_entity(entity_id, entity_type, pos, entity_data) func handle_entity_update(data: PackedByteArray): var json_string = data.get_string_from_utf8() @@ -160,8 +283,6 @@ func handle_entity_update(data: PackedByteArray): if json.parse(json_string) == OK: var update_data = json.data var entity_id = update_data.entity_id - - # Use entity manager instead of direct update if entity_manager: entity_manager.handle_update_message(update_data) elif entities.has(entity_id): @@ -174,25 +295,18 @@ func handle_entity_despawn(data: PackedByteArray): if json.parse(json_string) == OK: var despawn_data = json.data var entity_id = despawn_data.entity_id - - # Use entity manager instead of direct despawn if entity_manager: entity_manager.handle_despawn_message(despawn_data) elif entities.has(entity_id): despawn_entity(entity_id) -func handle_position_correction(data): - # Position correction handled by network manager - # We can optionally smooth the correction here - pass - func handle_chat_message(data: PackedByteArray): var json_string = data.get_string_from_utf8() if json_string: var json = JSON.new() if json.parse(json_string) == OK: var chat_data = json.data - ui.add_chat_message(chat_data.sender, chat_data.message, chat_data.channel) + hud.add_chat_message(chat_data.sender, chat_data.message, chat_data.channel) func handle_inventory_update(data: PackedByteArray): var json_string = data.get_string_from_utf8() @@ -200,7 +314,7 @@ func handle_inventory_update(data: PackedByteArray): var json = JSON.new() if json.parse(json_string) == OK: var inventory_data = json.data - ui.update_inventory(inventory_data) + hud.update_inventory(inventory_data) func handle_health_update(data: PackedByteArray): var json_string = data.get_string_from_utf8() @@ -208,209 +322,133 @@ func handle_health_update(data: PackedByteArray): var json = JSON.new() if json.parse(json_string) == OK: var health_data = json.data - ui.update_health(health_data.health, health_data.max_health) - -# Entity management - Kept for backward compatibility -func spawn_entity(entity_id: int, entity_type: String, position: Vector3, data: Dictionary): - if entities.size() >= max_entities: - # Remove farthest entity - remove_farthest_entity(position) - - var entity_node = create_entity_node(entity_type, data) - entity_node.global_transform.origin = position - entity_node.name = "Entity_%s" % entity_id - add_child(entity_node) - - entities[entity_id] = { - "node": entity_node, - "type": entity_type, - "data": data, - "position": position - } - - print("Spawned entity ", entity_id, " of type ", entity_type) - -func update_entity(entity_id: int, data: Dictionary): - var entity = entities[entity_id] - if entity and entity.node: - # Update position if provided - if data.has("position"): - var new_pos = Vector3( - data.position.x, - data.position.y, - data.position.z - ) - - # Interpolate position for smooth movement - var current_pos = entity.node.global_transform.origin - entity.node.global_transform.origin = current_pos.lerp(new_pos, 0.5) - entity.position = new_pos - - # Update other properties - if data.has("rotation"): - entity.node.rotation = Vector3( - data.rotation.x, - data.rotation.y, - data.rotation.z - ) - - # Update entity data - entity.data.merge(data, true) + hud.update_health(health_data.health, health_data.max_health) + +# Menu callbacks +func _on_play_pressed(): + show_login_menu() + +func _on_local_play_pressed(): + player.global_transform.origin = Vector3(0, 2, 0) + start_game() + +func _on_main_settings_pressed(): + show_settings_menu("main") + +@warning_ignore("shadowed_variable") +func _on_login_submitted(user: String, password: String): + login_menu.set_loading(true) + connect_to_server() + authenticate(user, password) + +func _on_login_back_pressed(): + show_main_menu() + +func _on_settings_back_pressed(): + var from = settings_menu.get_meta("from", "main") + settings_menu.visible = false + match from: + "main": + show_main_menu() + "pause": + show_hud() + pause_game() + +func _on_settings_saved(settings: Dictionary): + apply_settings(settings) + save_settings() -func despawn_entity(entity_id: int): - if entities.has(entity_id): - var entity = entities[entity_id] - if entity.node: - entity.node.queue_free() - entities.erase(entity_id) +func _on_chat_submitted(message: String): + if network_manager.connection_state == NetworkManager.ConnectionState.CONNECTED: + network_manager.send_chat_message(message) -func remove_farthest_entity(from_position: Vector3): - var farthest_id = -1 - var farthest_distance = 0.0 - - for entity_id in entities.keys(): - var entity = entities[entity_id] - var distance = entity.position.distance_to(from_position) - if distance > farthest_distance: - farthest_distance = distance - farthest_id = entity_id - - if farthest_id != -1: - despawn_entity(farthest_id) +func _on_resume_pressed(): + resume_game() -func create_entity_node(entity_type: String, data: Dictionary) -> Node3D: - # Create appropriate entity based on type - var entity_node = Node3D.new() - - # Add model - var model = model_generator.generate_model(entity_type, data) - if model: - entity_node.add_child(model) - - # Add collision shape - var collision = CollisionShape3D.new() - collision.shape = SphereShape3D.new() - collision.shape.radius = 0.5 - entity_node.add_child(collision) - - # Add interaction area - var area = Area3D.new() - area.collision_layer = 2 - area.collision_mask = 1 - area.input_event.connect(_on_entity_interacted.bind(entity_node)) - entity_node.add_child(area) - - return entity_node +func _on_pause_settings_pressed(): + show_settings_menu("pause") -func _on_entity_interacted(camera, event, position, normal, shape_idx, entity_node): - if event is InputEventMouseButton and event.pressed: - # Find entity ID from node name - var entity_name = entity_node.name - if entity_name.begins_with("Entity_"): - var entity_id = int(entity_name.replace("Entity_", "")) - network_manager.interact_with_entity(entity_id) - -# World management -func request_nearby_chunks(position: Vector3): - var chunk_x = int(position.x / chunk_size) - var chunk_z = int(position.z / chunk_size) - - for x in range(-render_distance, render_distance + 1): - for z in range(-render_distance, render_distance + 1): - var target_x = chunk_x + x - var target_z = chunk_z + z - var chunk_key = Vector2(target_x, target_z) - - if not current_chunks.has(chunk_key): - network_manager.request_chunk(target_x, target_z) +func _on_disconnect_pressed(): + disconnect_from_server() + show_main_menu() -func clear_world(): - # Clear entities - for entity_id in entities.keys(): - despawn_entity(entity_id) - entities.clear() - - # Clear chunks - current_chunks.clear() - world.clear_chunks() +func _on_quit_pressed(): + save_settings() + get_tree().quit() -# UI callbacks +# Network callbacks func _on_connection_state_changed(state: int): + hud.update_connection_status(state) match state: NetworkManager.ConnectionState.CONNECTED: - ui.show_message("Connected to server") + hud.add_chat_message("System", "Connected to server", "system") NetworkManager.ConnectionState.DISCONNECTED: - ui.show_message("Disconnected from server") - clear_world() + hud.add_chat_message("System", "Disconnected from server", "system") + if game_state == "playing": + clear_world() NetworkManager.ConnectionState.ERROR: - ui.show_message("Connection error") - - ui.update_connection_status(state) + hud.add_chat_message("System", "Connection error", "system") + login_menu.set_loading(false) func _on_authentication_result(success: bool, message: String): + login_menu.set_loading(false) if success: - ui.show_message("Authentication successful") - ui.hide_login_panel() + login_menu.set_status("Login successful!", Color.GREEN) + start_game() else: - ui.show_message("Authentication failed: " + message) + login_menu.set_status("Login failed: " + message, Color.RED) + +func _on_message_received(message_type: int, data): + match message_type: + NetworkManager.MessageType.MESSAGE_TYPE_CHUNK_DATA: + handle_chunk_data(data) + NetworkManager.MessageType.MESSAGE_TYPE_ENTITY_SPAWN: + handle_entity_spawn(data) + NetworkManager.MessageType.MESSAGE_TYPE_ENTITY_UPDATE: + handle_entity_update(data) + NetworkManager.MessageType.MESSAGE_TYPE_ENTITY_DESPAWN: + handle_entity_despawn(data) + NetworkManager.MessageType.MESSAGE_TYPE_CHAT_MESSAGE: + handle_chat_message(data) + NetworkManager.MessageType.MESSAGE_TYPE_INVENTORY_UPDATE: + handle_inventory_update(data) + NetworkManager.MessageType.MESSAGE_TYPE_HEALTH_UPDATE: + handle_health_update(data) func _on_error_occurred(error_code: int, message: String): - ui.show_message("Error %d: %s" % [error_code, message]) + hud.add_chat_message("System", "Error %d: %s" % [error_code, message], "system") + login_menu.set_loading(false) -func _on_position_corrected(position: Vector3): - # Smoothly correct player position - player.global_transform.origin = player.global_transform.origin.lerp(position, 0.3) +func _on_position_corrected(pos: Vector3): + player.global_transform.origin = player.global_transform.origin.lerp(pos, 0.3) -func _on_login_submitted(user: String, passw: String): # Changed from 'pass' to 'passw' - username = user - authenticate() +func apply_settings(settings: Dictionary): + if settings.has("host"): + server_address = settings.host + network_manager.server_address = server_address + if settings.has("port"): + server_port = settings.port + network_manager.server_port = server_port + if settings.has("login"): + username = settings.login + if settings.has("password"): + password = settings.password + if settings.has("render_distance"): + render_distance = settings.render_distance + if settings.has("mouse_sensitivity"): + player.mouse_sensitivity = settings.mouse_sensitivity -# Settings func load_settings(): - # Load from config file if config_loader: config_loader.load_config() - - # Apply loaded settings var network_config = config_loader.get_network_config() server_address = network_config.get("server_address", "127.0.0.1") server_port = network_config.get("server_port", 8080) render_distance = config_loader.get_value("graphics", "render_distance", 3) - - # Apply to network manager network_manager.server_address = server_address network_manager.server_port = server_port - - # Apply graphics settings config_loader.apply_graphics_settings() - else: - # Fallback to old method - var config = ConfigFile.new() - var err = config.load("user://settings.cfg") - - if err == OK: - server_address = config.get_value("network", "server_address", "127.0.0.1") - server_port = config.get_value("network", "server_port", 8080) - username = config.get_value("player", "username", "") - render_distance = config.get_value("graphics", "render_distance", 3) - - # Apply settings to network manager - network_manager.server_address = server_address - network_manager.server_port = server_port func save_settings(): if config_loader: config_loader.save_config() - else: - var config = ConfigFile.new() - - config.set_value("network", "server_address", server_address) - config.set_value("network", "server_port", server_port) - config.set_value("player", "username", username) - config.set_value("graphics", "render_distance", render_distance) - - config.save("user://settings.cfg") - -func _exit_tree(): - save_settings() - disconnect_from_server() \ No newline at end of file diff --git a/clients/godot/main.tscn b/clients/godot/main.tscn index bfac8f6..c4561b3 100644 --- a/clients/godot/main.tscn +++ b/clients/godot/main.tscn @@ -1,16 +1,38 @@ -[gd_scene load_steps=2 format=3 uid="uid://c1234"] +[gd_scene load_steps=11 format=3 uid="uid://c1234"] [ext_resource type="Script" path="res://main.gd" id="1"] +[ext_resource type="Script" path="res://ui/menu.gd" id="2"] +[ext_resource type="Script" path="res://ui/login_menu.gd" id="3"] +[ext_resource type="Script" path="res://ui/settings_menu.gd" id="4"] +[ext_resource type="Script" path="res://ui/hud.gd" id="5"] +[ext_resource type="Script" path="res://ui/pause_menu.gd" id="6"] +[ext_resource type="Script" path="res://world/world.gd" id="7"] +[ext_resource type="Script" path="res://player/controller.gd" id="8"] +[ext_resource type="Script" path="res://network/networks.gd" id="9"] +[ext_resource type="Script" path="res://models/generator.gd" id="10"] +[ext_resource type="Script" path="res://entities/ents.gd" id="11"] +[ext_resource type="Script" path="res://config/loader.gd" id="12"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_player"] +radius = 0.3 +height = 1.8 [node name="GameClient" type="Node3D"] script = ExtResource("1") [node name="World" type="Node3D" parent="."] +script = ExtResource("7") [node name="Player" type="CharacterBody3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) +script = ExtResource("8") [node name="CollisionShape3D" type="CollisionShape3D" parent="Player"] +shape = SubResource("CapsuleShape3D_player") + +[node name="PlayerModel" type="MeshInstance3D" parent="Player"] + +[node name="AnimationPlayer" type="AnimationPlayer" parent="Player"] [node name="CameraPivot" type="Node3D" parent="Player"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0) @@ -27,10 +49,642 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 +[node name="MainMenu" type="Control" parent="UI"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("2") + +[node name="Background" type="ColorRect" parent="UI/MainMenu"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.1, 0.12, 0.15, 1) + +[node name="VBoxContainer" type="VBoxContainer" parent="UI/MainMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -150.0 +offset_top = -150.0 +offset_right = 150.0 +offset_bottom = 150.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 20 + +[node name="TitleLabel" type="Label" parent="UI/MainMenu/VBoxContainer"] +layout_mode = 2 +text = "GAME CLIENT" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="LocalPlayButton" type="Button" parent="UI/MainMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "PLAY" + +[node name="PlayButton" type="Button" parent="UI/MainMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "CONNECT TO SERVER" + +[node name="SettingsButton" type="Button" parent="UI/MainMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "SETTINGS" + +[node name="QuitButton" type="Button" parent="UI/MainMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "QUIT" + +[node name="VersionLabel" type="Label" parent="UI/MainMenu"] +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -100.0 +offset_top = -30.0 +grow_horizontal = 0 +grow_vertical = 0 +text = "v0.1.0" +horizontal_alignment = 2 + +[node name="StatusLabel" type="Label" parent="UI/MainMenu"] +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -100.0 +offset_top = -80.0 +offset_right = 100.0 +offset_bottom = -50.0 +grow_horizontal = 2 +text = "" +horizontal_alignment = 1 + +[node name="LoginMenu" type="Control" parent="UI"] +visible = false +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("3") + +[node name="Background" type="ColorRect" parent="UI/LoginMenu"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.1, 0.12, 0.15, 1) + +[node name="VBoxContainer" type="VBoxContainer" parent="UI/LoginMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -150.0 +offset_top = -150.0 +offset_right = 150.0 +offset_bottom = 150.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 15 + +[node name="TitleLabel" type="Label" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +text = "LOGIN" +horizontal_alignment = 1 + +[node name="ServerAddressInput" type="LineEdit" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Server Address" + +[node name="ServerPortInput" type="LineEdit" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Server Port" + +[node name="UsernameInput" type="LineEdit" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Username" + +[node name="PasswordInput" type="LineEdit" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Password" +secret = true + +[node name="LoginButton" type="Button" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 40) +text = "LOGIN" + +[node name="BackButton" type="Button" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 40) +text = "BACK" + +[node name="StatusLabel" type="Label" parent="UI/LoginMenu/VBoxContainer"] +layout_mode = 2 +text = "" +horizontal_alignment = 1 + +[node name="SettingsMenu" type="Control" parent="UI"] +visible = false +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("4") + +[node name="Background" type="ColorRect" parent="UI/SettingsMenu"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.1, 0.12, 0.15, 1) + +[node name="VBox" type="VBoxContainer" parent="UI/SettingsMenu"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 40.0 +offset_top = 20.0 +offset_right = -40.0 +offset_bottom = -20.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 10 + +[node name="TitleLabel" type="Label" parent="UI/SettingsMenu/VBox"] +layout_mode = 2 +text = "SETTINGS" +horizontal_alignment = 1 + +[node name="TabContainer" type="TabContainer" parent="UI/SettingsMenu/VBox"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Network" type="VBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer"] +layout_mode = 2 + +[node name="VBox" type="VBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Network"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 10 + +[node name="HostRow" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox/HostRow"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Host" + +[node name="HostInput" type="LineEdit" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "127.0.0.1" + +[node name="PortRow" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox/PortRow"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Port" + +[node name="PortInput" type="LineEdit" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "8080" + +[node name="ProtocolRow" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox/ProtocolRow"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Protocol" + +[node name="ProtocolOption" type="OptionButton" parent="UI/SettingsMenu/VBox/TabContainer/Network/VBox"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Account" type="VBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer"] +layout_mode = 2 + +[node name="VBox" type="VBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Account"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 10 + +[node name="LoginRow" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Account/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Account/VBox/LoginRow"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Login" + +[node name="LoginInput" type="LineEdit" parent="UI/SettingsMenu/VBox/TabContainer/Account/VBox"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Username" + +[node name="PasswordRow" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Account/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Account/VBox/PasswordRow"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Password" + +[node name="PasswordInput" type="LineEdit" parent="UI/SettingsMenu/VBox/TabContainer/Account/VBox"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Password" +secret = true + +[node name="Graphics" type="VBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer"] +layout_mode = 2 + +[node name="VBox" type="VBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Graphics"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 10 + +[node name="RenderDistance" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/RenderDistance"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Render Distance" + +[node name="Slider" type="HSlider" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/RenderDistance"] +layout_mode = 2 +size_flags_horizontal = 3 +min_value = 1.0 +max_value = 10.0 +value = 3.0 + +[node name="Value" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/RenderDistance"] +layout_mode = 2 +custom_minimum_size = Vector2(40, 0) +text = "3" + +[node name="VSync" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/VSync"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "VSync" + +[node name="CheckBox" type="CheckBox" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/VSync"] +layout_mode = 2 +button_pressed = true + +[node name="MSAA" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/MSAA"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "MSAA" + +[node name="CheckBox" type="CheckBox" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/MSAA"] +layout_mode = 2 +button_pressed = true + +[node name="Fullscreen" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/Fullscreen"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Fullscreen" + +[node name="CheckBox" type="CheckBox" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/Fullscreen"] +layout_mode = 2 + +[node name="MouseSensitivity" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/MouseSensitivity"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Mouse Sensitivity" + +[node name="Slider" type="HSlider" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/MouseSensitivity"] +layout_mode = 2 +size_flags_horizontal = 3 +min_value = 0.001 +max_value = 0.01 +step = 0.001 +value = 0.002 + +[node name="Value" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/MouseSensitivity"] +layout_mode = 2 +custom_minimum_size = Vector2(40, 0) +text = "0.002" + +[node name="Volume" type="HBoxContainer" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/Volume"] +layout_mode = 2 +custom_minimum_size = Vector2(120, 0) +text = "Volume" + +[node name="Slider" type="HSlider" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/Volume"] +layout_mode = 2 +size_flags_horizontal = 3 +min_value = 0.0 +max_value = 100.0 +value = 80.0 + +[node name="Value" type="Label" parent="UI/SettingsMenu/VBox/TabContainer/Graphics/VBox/Volume"] +layout_mode = 2 +custom_minimum_size = Vector2(40, 0) +text = "80%" + +[node name="Buttons" type="HBoxContainer" parent="UI/SettingsMenu/VBox"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="SaveButton" type="Button" parent="UI/SettingsMenu/VBox/Buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +custom_minimum_size = Vector2(0, 40) +text = "SAVE" + +[node name="BackButton" type="Button" parent="UI/SettingsMenu/VBox/Buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +custom_minimum_size = Vector2(0, 40) +text = "BACK" + +[node name="HUD" type="Control" parent="UI"] +process_mode = 3 +visible = false +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("5") + +[node name="MarginContainer" type="MarginContainer" parent="UI/HUD"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxLeft" type="VBoxContainer" parent="UI/HUD/MarginContainer"] +layout_mode = 2 +anchors_preset = 0 +offset_left = 10.0 +offset_top = 10.0 +offset_right = 250.0 +offset_bottom = 120.0 + +[node name="HealthBar" type="ProgressBar" parent="UI/HUD/MarginContainer/VBoxLeft"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 20) +max_value = 100.0 +value = 100.0 +show_percentage = false + +[node name="HealthLabel" type="Label" parent="UI/HUD/MarginContainer/VBoxLeft/HealthBar"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "100 / 100" +horizontal_alignment = 1 + +[node name="ManaBar" type="ProgressBar" parent="UI/HUD/MarginContainer/VBoxLeft"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 20) +max_value = 100.0 +value = 100.0 +show_percentage = false + +[node name="ManaLabel" type="Label" parent="UI/HUD/MarginContainer/VBoxLeft/ManaBar"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "100 / 100" +horizontal_alignment = 1 + +[node name="XPBar" type="ProgressBar" parent="UI/HUD/MarginContainer/VBoxLeft"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 20) +max_value = 100.0 +value = 0.0 +show_percentage = false + +[node name="XPLabel" type="Label" parent="UI/HUD/MarginContainer/VBoxLeft/XPBar"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "0 / 100" +horizontal_alignment = 1 + +[node name="VBoxRight" type="VBoxContainer" parent="UI/HUD/MarginContainer"] +layout_mode = 2 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -300.0 +offset_top = 10.0 +offset_right = -10.0 +offset_bottom = 200.0 + +[node name="ChatPanel" type="PanelContainer" parent="UI/HUD/MarginContainer/VBoxRight"] +visible = false +layout_mode = 2 +custom_minimum_size = Vector2(280, 180) + +[node name="ScrollContainer" type="ScrollContainer" parent="UI/HUD/MarginContainer/VBoxRight/ChatPanel"] +layout_mode = 2 + +[node name="ChatDisplay" type="RichTextLabel" parent="UI/HUD/MarginContainer/VBoxRight/ChatPanel/ScrollContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(260, 150) +bbcode_enabled = true + +[node name="ChatInput" type="LineEdit" parent="UI/HUD/MarginContainer/VBoxRight/ChatPanel"] +layout_mode = 2 +placeholder_text = "Press Enter to chat..." + +[node name="ConnectionStatus" type="Label" parent="UI/HUD"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -120.0 +offset_top = 10.0 +offset_right = -10.0 +offset_bottom = 30.0 +text = "Disconnected" +horizontal_alignment = 2 + +[node name="DebugInfo" type="Label" parent="UI/HUD"] +visible = false +layout_mode = 1 +anchors_preset = 0 +offset_left = 10.0 +offset_top = 130.0 +offset_right = 250.0 +offset_bottom = 250.0 +text = "DEBUG INFO" + +[node name="FPSLabel" type="Label" parent="UI/HUD"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -100.0 +offset_top = 30.0 +offset_right = -10.0 +offset_bottom = 50.0 +text = "FPS: 60" +horizontal_alignment = 2 + +[node name="Minimap" type="Control" parent="UI/HUD"] +visible = false +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 10.0 +offset_top = -160.0 +offset_right = 160.0 +offset_bottom = -10.0 + +[node name="MinimapFrame" type="PanelContainer" parent="UI/HUD/Minimap"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MinimapTexture" type="TextureRect" parent="UI/HUD/Minimap/MinimapFrame"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="PauseMenu" type="Control" parent="."] +process_mode = 3 +visible = false +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("6") + +[node name="Background" type="ColorRect" parent="PauseMenu"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.7) + +[node name="VBoxContainer" type="VBoxContainer" parent="PauseMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -100.0 +offset_top = -120.0 +offset_right = 100.0 +offset_bottom = 120.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 20 + +[node name="TitleLabel" type="Label" parent="PauseMenu/VBoxContainer"] +layout_mode = 2 +text = "PAUSED" +horizontal_alignment = 1 + +[node name="ResumeButton" type="Button" parent="PauseMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "RESUME" + +[node name="SettingsButton" type="Button" parent="PauseMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "SETTINGS" + +[node name="DisconnectButton" type="Button" parent="PauseMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "DISCONNECT" + +[node name="QuitButton" type="Button" parent="PauseMenu/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(200, 50) +text = "QUIT" + [node name="NetworkManager" type="Node" parent="."] +script = ExtResource("9") [node name="ModelGenerator" type="Node" parent="."] +script = ExtResource("10") [node name="EntityManager" type="Node" parent="."] +script = ExtResource("11") [node name="ConfigLoader" type="Node" parent="."] +script = ExtResource("12") diff --git a/clients/godot/models/generator.gd b/clients/godot/models/generator.gd index 84c96cb..cc98bcc 100644 --- a/clients/godot/models/generator.gd +++ b/clients/godot/models/generator.gd @@ -16,17 +16,44 @@ enum ModelType { } # Materials -@onready var player_material = preload("res://materials/player.tres") -@onready var npc_material = preload("res://materials/npc.tres") -@onready var mob_material = preload("res://materials/mob.tres") -@onready var tree_material = preload("res://materials/tree.tres") -@onready var stone_material = preload("res://materials/stone.tres") -@onready var water_material = preload("res://materials/water.tres") +var player_material: StandardMaterial3D +var npc_material: StandardMaterial3D +var mob_material: StandardMaterial3D +var tree_material: StandardMaterial3D +var stone_material: StandardMaterial3D +var water_material: StandardMaterial3D # Model presets var model_presets: Dictionary = {} func _ready(): + player_material = StandardMaterial3D.new() + player_material.albedo_color = Color(0.3, 0.5, 0.8) + player_material.roughness = 0.7 + + npc_material = StandardMaterial3D.new() + npc_material.albedo_color = Color(0.8, 0.6, 0.2) + npc_material.roughness = 0.7 + + mob_material = StandardMaterial3D.new() + mob_material.albedo_color = Color(0.8, 0.2, 0.2) + mob_material.roughness = 0.7 + + tree_material = StandardMaterial3D.new() + tree_material.albedo_color = Color(0.3, 0.5, 0.2) + tree_material.roughness = 0.8 + + stone_material = StandardMaterial3D.new() + stone_material.albedo_color = Color(0.5, 0.5, 0.5) + stone_material.roughness = 0.9 + stone_material.metallic = 0.1 + + water_material = StandardMaterial3D.new() + water_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + water_material.albedo_color = Color(0.2, 0.4, 0.8, 0.7) + water_material.metallic = 0.3 + water_material.roughness = 0.1 + initialize_presets() func initialize_presets(): @@ -175,7 +202,7 @@ func generate_procedural_model(model_type: String, data: Dictionary) -> MeshInst _: return generate_basic_model(model_type, data) -func generate_familiar_model(data: Dictionary) -> MeshInstance3D: +func generate_familiar_model(_data: Dictionary) -> MeshInstance3D: var mesh_instance = MeshInstance3D.new() # Create a simple familiar (floating crystal/creature) @@ -217,20 +244,17 @@ func generate_river_model(data: Dictionary) -> MeshInstance3D: mesh_instance.mesh = plane_mesh - var material = ShaderMaterial.new() - var shader = preload("res://shaders/water.gdshader") - material.shader = shader - - if data.has("color"): - material.set_shader_parameter("water_color", Color(data.color)) - else: - material.set_shader_parameter("water_color", Color(0.2, 0.4, 0.8, 0.6)) + var material = StandardMaterial3D.new() + material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + material.albedo_color = Color(0.2, 0.4, 0.8, 0.6) + material.metallic = 0.3 + material.roughness = 0.1 mesh_instance.material_override = material return mesh_instance -func generate_house_model(data: Dictionary) -> MeshInstance3D: +func generate_house_model(_data: Dictionary) -> MeshInstance3D: var mesh_instance = MeshInstance3D.new() # Create simple house from CSG nodes @@ -277,7 +301,7 @@ func generate_water_model(data: Dictionary) -> MeshInstance3D: return mesh_instance -func generate_basic_model(model_type: String, data: Dictionary) -> MeshInstance3D: +func generate_basic_model(model_type: String, _data: Dictionary) -> MeshInstance3D: # Generate a basic placeholder model var mesh_instance = MeshInstance3D.new() @@ -328,7 +352,7 @@ func generate_low_poly_rock(variation: int = 0) -> MeshInstance3D: return generate_from_preset(ModelType.STONE, {"variation": variation}) func generate_player_model(skin_color: Color = Color(0.8, 0.6, 0.4), - hair_color: Color = Color(0.3, 0.2, 0.1)) -> MeshInstance3D: + _hair_color: Color = Color(0.3, 0.2, 0.1)) -> MeshInstance3D: var mesh_instance = generate_from_preset(ModelType.PLAYER, {}) # Customize player appearance @@ -381,4 +405,4 @@ func generate_simple_weapon() -> MeshInstance3D: material.albedo_color = Color(0.5, 0.5, 0.5) weapon.material_override = material - return weapon \ No newline at end of file + return weapon diff --git a/clients/godot/network/networks.gd b/clients/godot/network/networks.gd index 6b285f0..68949cc 100644 --- a/clients/godot/network/networks.gd +++ b/clients/godot/network/networks.gd @@ -635,7 +635,7 @@ func cleanup(): auth_token = "" protocol_negotiated = false -func update_network_quality(success: bool): +func update_network_quality(_success: bool): pass # --- Heartbeat --- @@ -702,7 +702,7 @@ func store_input(input_id: int, position: Vector3, rotation: Vector3, velocity: if input_buffer.size() > 100: input_buffer.pop_front() -func update_prediction(delta: float): +func update_prediction(_delta: float): if not prediction_enabled or input_buffer.size() == 0: return diff --git a/clients/godot/player/controller.gd b/clients/godot/player/controller.gd index be95b7c..85ef2b3 100644 --- a/clients/godot/player/controller.gd +++ b/clients/godot/player/controller.gd @@ -14,8 +14,8 @@ class_name PlayerController @onready var camera = $CameraPivot/Camera3D # Components -@onready var animation_player = $AnimationPlayer -@onready var model = $PlayerModel +@onready var animation_player = $AnimationPlayer if has_node("AnimationPlayer") else null +@onready var model = $PlayerModel if has_node("PlayerModel") else self # State var current_speed: float = 0.0 @@ -73,7 +73,7 @@ func _input(event): func process_movement(delta): # Get input - move_input = Input.get_vector("move_left", "move_right", "move_forward", "move_back") + move_input = Input.get_vector("move_left", "move_right", "move_backward", "move_forward") # Apply gravity if not is_on_floor(): @@ -123,7 +123,7 @@ func process_movement(delta): # Move the character move_and_slide() -func process_camera(delta): +func process_camera(_delta): # Horizontal rotation (Y axis) camera_pivot.rotate_y(-look_input.x) @@ -136,31 +136,33 @@ func process_camera(delta): func process_animation(delta): if animation_player: - # Blend walking/running animations if is_grounded: if current_speed > 0: if is_sprinting: - animation_player.play("run") + play_anim("run") else: - animation_player.play("walk") + play_anim("walk") else: if is_crouching: - animation_player.play("crouch_idle") + play_anim("crouch_idle") else: - animation_player.play("idle") + play_anim("idle") else: if velocity.y > 0: - animation_player.play("jump_up") + play_anim("jump_up") else: - animation_player.play("jump_down") + play_anim("jump_down") - # Rotate model to face movement direction if move_input.length() > 0 and is_grounded: var direction = Vector3(velocity.x, 0, velocity.z).normalized() if direction.length() > 0.1: var target_rotation = atan2(direction.x, direction.z) model.rotation.y = lerp_angle(model.rotation.y, target_rotation, 10.0 * delta) +func play_anim(anim_name: String): + if animation_player.has_animation(anim_name): + animation_player.play(anim_name) + func setup_camera(): # Set initial camera position camera_pivot.position.y = 1.5 # Eye level @@ -183,9 +185,7 @@ func jump(): is_jumping = true func perform_attack(): - # Play attack animation - if animation_player: - animation_player.play("attack") + play_anim("attack") # Send combat event to server if network_manager: @@ -235,6 +235,6 @@ func _on_grounded_changed(is_now_grounded: bool): func get_camera() -> Camera3D: return camera -func set_model_visibility(visible: bool): +func set_model_visibility(vis: bool): if model: - model.visible = visible \ No newline at end of file + model.visible = vis \ No newline at end of file diff --git a/clients/godot/player/weapons.gd b/clients/godot/player/weapons.gd new file mode 100644 index 0000000..fcc67dc --- /dev/null +++ b/clients/godot/player/weapons.gd @@ -0,0 +1,223 @@ +extends Node3D + +class_name WeaponManager + +enum WeaponType { GUN, RIFLE, SHOTGUN, SNIPER, LAUNCHER } + +var current_weapon: int = WeaponType.GUN +var weapons: Dictionary = {} +var ammo_scene: PackedScene + +func _ready(): + initialize_weapons() + +func initialize_weapons(): + weapons[WeaponType.GUN] = { + "name": "Pistol", + "damage": 25, + "ammo_speed": 30.0, + "ammo_range": 50.0, + "cooldown": 0.3, + "magazine_size": 12, + "reload_time": 1.5, + "spread": 0.02 + } + weapons[WeaponType.RIFLE] = { + "name": "Rifle", + "damage": 35, + "ammo_speed": 40.0, + "ammo_range": 60.0, + "cooldown": 0.15, + "magazine_size": 30, + "reload_time": 2.0, + "spread": 0.01 + } + weapons[WeaponType.SHOTGUN] = { + "name": "Shotgun", + "damage": 15, + "ammo_speed": 25.0, + "ammo_range": 30.0, + "cooldown": 0.8, + "magazine_size": 8, + "reload_time": 2.5, + "spread": 0.1, + "pellets": 6 + } + weapons[WeaponType.SNIPER] = { + "name": "Sniper", + "damage": 100, + "ammo_speed": 60.0, + "ammo_range": 100.0, + "cooldown": 1.5, + "magazine_size": 5, + "reload_time": 3.0, + "spread": 0.005 + } + weapons[WeaponType.LAUNCHER] = { + "name": "Launcher", + "damage": 150, + "ammo_speed": 20.0, + "ammo_range": 40.0, + "cooldown": 2.0, + "magazine_size": 3, + "reload_time": 3.5, + "spread": 0.0, + "explosion_radius": 5.0 + } + +func get_weapon_data(weapon_type: int) -> Dictionary: + return weapons.get(weapon_type, {}) + +func switch_weapon(weapon_type: int): + if weapons.has(weapon_type): + current_weapon = weapon_type + +func get_current_weapon_data() -> Dictionary: + return get_weapon_data(current_weapon) + +func create_ammo(origin: Vector3, direction: Vector3) -> Node3D: + var data = get_current_weapon_data() + var ammo = Node3D.new() + ammo.set_meta("damage", data.get("damage", 25)) + ammo.set_meta("speed", data.get("ammo_speed", 30.0)) + ammo.set_meta("range", data.get("ammo_range", 50.0)) + ammo.set_meta("traveled", 0.0) + ammo.set_meta("active", true) + ammo.set_meta("direction", direction.normalized()) + ammo.global_transform.origin = origin + var mesh = SphereMesh.new() + mesh.radius = 0.08 + mesh.height = 0.16 + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(1.0, 0.8, 0.0) + mat.emission_enabled = true + mat.emission = Color(1.0, 0.6, 0.0) + mesh.material = mat + var mesh_inst = MeshInstance3D.new() + mesh_inst.mesh = mesh + ammo.add_child(mesh_inst) + return ammo + +func update_ammo(ammo: Node3D, delta: float): + if not ammo.get_meta("active", false): + return + var speed = ammo.get_meta("speed", 30.0) + var dir = ammo.get_meta("direction", Vector3.FORWARD) + var distance = speed * delta + ammo.global_transform.origin += dir * distance + var traveled = ammo.get_meta("traveled", 0.0) + distance + ammo.set_meta("traveled", traveled) + if traveled >= ammo.get_meta("range", 50.0): + ammo.set_meta("active", false) + ammo.queue_free() + +func get_weapon_model() -> Node3D: + var node = Node3D.new() + var data = get_current_weapon_data() + match current_weapon: + WeaponType.GUN: + return _create_gun_model() + WeaponType.RIFLE: + return _create_rifle_model() + WeaponType.SHOTGUN: + return _create_shotgun_model() + WeaponType.SNIPER: + return _create_sniper_model() + WeaponType.LAUNCHER: + return _create_launcher_model() + _: + return _create_gun_model() + return node + +func _create_gun_model() -> Node3D: + var node = Node3D.new() + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(0.2, 0.2, 0.2) + var barrel = BoxMesh.new() + barrel.size = Vector3(0.08, 0.08, 0.4) + barrel.material = mat + var barrel_inst = MeshInstance3D.new() + barrel_inst.mesh = barrel + barrel_inst.position = Vector3(0, 0, -0.2) + node.add_child(barrel_inst) + var grip = BoxMesh.new() + grip.size = Vector3(0.1, 0.2, 0.1) + grip.material = mat + var grip_inst = MeshInstance3D.new() + grip_inst.mesh = grip + grip_inst.position = Vector3(0, -0.15, -0.05) + grip_inst.rotation.x = -0.3 + node.add_child(grip_inst) + return node + +func _create_rifle_model() -> Node3D: + var node = Node3D.new() + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(0.3, 0.25, 0.2) + var barrel = BoxMesh.new() + barrel.size = Vector3(0.06, 0.06, 0.6) + barrel.material = mat + var barrel_inst = MeshInstance3D.new() + barrel_inst.mesh = barrel + barrel_inst.position = Vector3(0, 0, -0.3) + node.add_child(barrel_inst) + var stock = BoxMesh.new() + stock.size = Vector3(0.1, 0.15, 0.2) + stock.material = mat + var stock_inst = MeshInstance3D.new() + stock_inst.mesh = stock + stock_inst.position = Vector3(0, -0.05, 0.2) + node.add_child(stock_inst) + return node + +func _create_shotgun_model() -> Node3D: + var node = Node3D.new() + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(0.4, 0.3, 0.15) + var barrel = BoxMesh.new() + barrel.size = Vector3(0.1, 0.1, 0.5) + barrel.material = mat + var barrel_inst = MeshInstance3D.new() + barrel_inst.mesh = barrel + barrel_inst.position = Vector3(0, 0, -0.25) + node.add_child(barrel_inst) + return node + +func _create_sniper_model() -> Node3D: + var node = Node3D.new() + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(0.15, 0.15, 0.15) + var barrel = BoxMesh.new() + barrel.size = Vector3(0.05, 0.05, 0.8) + barrel.material = mat + var barrel_inst = MeshInstance3D.new() + barrel_inst.mesh = barrel + barrel_inst.position = Vector3(0, 0, -0.4) + node.add_child(barrel_inst) + var scope = CylinderMesh.new() + scope.top_radius = 0.04 + scope.bottom_radius = 0.04 + scope.height = 0.2 + scope.material = mat + var scope_inst = MeshInstance3D.new() + scope_inst.mesh = scope + scope_inst.position = Vector3(0, 0.1, -0.1) + scope_inst.rotation.x = PI / 2 + node.add_child(scope_inst) + return node + +func _create_launcher_model() -> Node3D: + var node = Node3D.new() + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(0.4, 0.4, 0.1) + var barrel = CylinderMesh.new() + barrel.top_radius = 0.08 + barrel.bottom_radius = 0.08 + barrel.height = 0.5 + barrel.material = mat + var barrel_inst = MeshInstance3D.new() + barrel_inst.mesh = barrel + barrel_inst.position = Vector3(0, 0, -0.25) + barrel_inst.rotation.x = PI / 2 + node.add_child(barrel_inst) + return node diff --git a/clients/godot/player/weapons.gd.uid b/clients/godot/player/weapons.gd.uid new file mode 100644 index 0000000..5a849fb --- /dev/null +++ b/clients/godot/player/weapons.gd.uid @@ -0,0 +1 @@ +uid://b88gnqtfmnefc diff --git a/clients/godot/shaders/terrain.gdshader b/clients/godot/shaders/terrain.gdshader new file mode 100644 index 0000000..c173b32 --- /dev/null +++ b/clients/godot/shaders/terrain.gdshader @@ -0,0 +1,28 @@ +shader_type spatial; + +uniform vec3 light_direction = vec3(0.3, 0.8, 0.5); +uniform float ambient_strength : hint_range(0.0, 1.0) = 0.1; +uniform vec3 fog_color : source_color = vec3(0.1, 0.2, 0.3); +uniform float fog_start : hint_range(0.0, 500.0) = 50.0; +uniform float fog_end : hint_range(0.0, 500.0) = 70.0; + +varying float height; +varying vec3 world_pos; + +void vertex() { + height = VERTEX.y; + world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + float h = clamp((height + 2.0) / 6.0, 0.0, 1.0); + vec3 green = vec3(0.3, 0.6, 0.2); + vec3 brown = vec3(0.5, 0.4, 0.2); + COLOR.rgb = mix(green, brown, h); +} + +void fragment() { + vec3 light_dir = normalize(light_direction); + float intensity = max(dot(NORMAL, light_dir), 0.0); + float ambient = ambient_strength * (0.2 + 0.8 * intensity); + ALBEDO = COLOR.rgb * (ambient + 0.7 * intensity); + float fog_factor = clamp((fog_end - world_pos.z) / (fog_end - fog_start), 0.0, 1.0); + ALBEDO = mix(fog_color, ALBEDO, fog_factor); +} diff --git a/clients/godot/shaders/terrain.gdshader.uid b/clients/godot/shaders/terrain.gdshader.uid new file mode 100644 index 0000000..981c034 --- /dev/null +++ b/clients/godot/shaders/terrain.gdshader.uid @@ -0,0 +1 @@ +uid://3l3sbscwkfvl diff --git a/clients/godot/ui/hud.gd b/clients/godot/ui/hud.gd new file mode 100644 index 0000000..ce38633 --- /dev/null +++ b/clients/godot/ui/hud.gd @@ -0,0 +1,164 @@ +extends Control + +class_name GameHUD + +signal chat_submitted(message: String) +signal inventory_toggled() + +@onready var health_bar = $MarginContainer/VBoxLeft/HealthBar +@onready var health_label = $MarginContainer/VBoxLeft/HealthBar/HealthLabel +@onready var mana_bar = $MarginContainer/VBoxLeft/ManaBar +@onready var mana_label = $MarginContainer/VBoxLeft/ManaBar/ManaLabel +@onready var xp_bar = $MarginContainer/VBoxLeft/XPBar +@onready var xp_label = $MarginContainer/VBoxLeft/XPBar/XPLabel +@onready var chat_panel = $MarginContainer/VBoxRight/ChatPanel +@onready var chat_display = $MarginContainer/VBoxRight/ChatPanel/ScrollContainer/ChatDisplay +@onready var chat_input = $MarginContainer/VBoxRight/ChatPanel/ChatInput +@onready var minimap = $Minimap +@onready var connection_status = $ConnectionStatus +@onready var debug_info = $DebugInfo +@onready var fps_label = $FPSLabel + +var chat_messages: Array = [] +var max_chat_messages: int = 50 +var is_chat_focused: bool = false + +func _ready(): + visible = false + chat_input.text_submitted.connect(_on_chat_submitted) + chat_input.focus_entered.connect(_on_chat_focus_entered) + chat_input.focus_exited.connect(_on_chat_focus_exited) + +func show_hud(): + visible = true + hide_all_popups() + +func hide_hud(): + visible = false + +func hide_all_popups(): + chat_panel.visible = false + minimap.visible = false + debug_info.visible = false + +func _process(_delta): + if fps_label: + fps_label.text = "FPS: %d" % Engine.get_frames_per_second() + +func _input(event): + if not visible: + return + + if event is InputEventKey: + if event.pressed: + match event.keycode: + KEY_ENTER: + if not is_chat_focused: + show_chat() + elif chat_input.text.strip_edges() != "": + _on_chat_submitted(chat_input.text) + hide_chat() + KEY_ESCAPE: + if is_chat_focused: + hide_chat() + else: + get_parent().get_parent().get_node("PauseMenu").toggle() + KEY_TAB: + toggle_minimap() + KEY_F1: + toggle_debug() + KEY_I: + emit_signal("inventory_toggled") + +func update_health(current: float, maximum: float): + if health_bar: + health_bar.value = current + health_bar.max_value = maximum + health_label.text = "%d / %d" % [int(current), int(maximum)] + +func update_mana(current: float, maximum: float): + if mana_bar: + mana_bar.value = current + mana_bar.max_value = maximum + mana_label.text = "%d / %d" % [int(current), int(maximum)] + +func update_xp(current: float, maximum: float): + if xp_bar: + xp_bar.value = current + xp_bar.max_value = maximum + xp_label.text = "%d / %d" % [int(current), int(maximum)] + +func add_chat_message(sender: String, message: String, channel: String = "global"): + var timestamp = Time.get_time_string_from_system() + var formatted = "[%s] [%s] %s: %s" % [timestamp, channel, sender, message] + chat_messages.append(formatted) + + if chat_messages.size() > max_chat_messages: + chat_messages.pop_front() + + _update_chat_display() + +func _update_chat_display(): + if chat_display: + chat_display.text = "\n".join(chat_messages) + +func show_chat(): + is_chat_focused = true + chat_panel.visible = true + chat_input.text = "" + chat_input.grab_focus() + +func hide_chat(): + is_chat_focused = false + chat_input.text = "" + chat_input.release_focus() + chat_panel.visible = false + +func _on_chat_submitted(text: String): + var message = text.strip_edges() + if message != "": + emit_signal("chat_submitted", message) + add_chat_message("You", message) + chat_input.text = "" + +func _on_chat_focus_entered(): + is_chat_focused = true + +func _on_chat_focus_exited(): + is_chat_focused = false + +func toggle_minimap(): + minimap.visible = not minimap.visible + +func toggle_debug(): + debug_info.visible = not debug_info.visible + +func update_connection_status(state: int): + match state: + 0: + connection_status.text = "Disconnected" + connection_status.modulate = Color.RED + 1: + connection_status.text = "Connecting..." + connection_status.modulate = Color.YELLOW + 2: + connection_status.text = "Handshake..." + connection_status.modulate = Color.YELLOW + 3: + connection_status.text = "Authenticating..." + connection_status.modulate = Color.YELLOW + 4: + connection_status.text = "Connected" + connection_status.modulate = Color.GREEN + 5: + connection_status.text = "Error" + connection_status.modulate = Color.RED + +func update_debug_info(info: Dictionary): + if not debug_info.visible: + return + var text = "=== DEBUG ===\n" + text += "FPS: %d\n" % info.get("fps", Engine.get_frames_per_second()) + text += "Ping: %dms\n" % info.get("latency", 0) + text += "Players: %d\n" % info.get("players", 0) + debug_info.text = text diff --git a/clients/godot/ui/hud.gd.uid b/clients/godot/ui/hud.gd.uid new file mode 100644 index 0000000..82954e7 --- /dev/null +++ b/clients/godot/ui/hud.gd.uid @@ -0,0 +1 @@ +uid://c5q4bgmk3caab diff --git a/clients/godot/ui/login_menu.gd b/clients/godot/ui/login_menu.gd new file mode 100644 index 0000000..f957274 --- /dev/null +++ b/clients/godot/ui/login_menu.gd @@ -0,0 +1,61 @@ +extends Control + +class_name LoginMenu + +signal login_submitted(username: String, password: String) +signal back_pressed() + +@onready var username_input = $VBoxContainer/UsernameInput +@onready var password_input = $VBoxContainer/PasswordInput +@onready var login_button = $VBoxContainer/LoginButton +@onready var back_button = $VBoxContainer/BackButton +@onready var status_label = $VBoxContainer/StatusLabel +@onready var server_address_input = $VBoxContainer/ServerAddressInput +@onready var server_port_input = $VBoxContainer/ServerPortInput + +func _ready(): + username_input.grab_focus() + login_button.pressed.connect(_on_login_pressed) + back_button.pressed.connect(_on_back_pressed) + password_input.text_submitted.connect(_on_login_pressed) + + server_address_input.text = "127.0.0.1" + server_port_input.text = "8080" + +func set_status(text: String, color: Color = Color.WHITE): + status_label.text = text + status_label.modulate = color + +func _on_login_pressed(_text: String = ""): + var username = username_input.text.strip_edges() + var password = password_input.text + + if username == "": + set_status("Username cannot be empty", Color.RED) + return + + emit_signal("login_submitted", username, password) + +func _on_back_pressed(): + emit_signal("back_pressed") + +func get_server_address() -> String: + return server_address_input.text.strip_edges() + +func get_server_port() -> int: + return server_port_input.text.to_int() + +func set_server(host: String, port: int): + server_address_input.text = host + server_port_input.text = str(port) + +func set_credentials(host: String, port: int, user: String, passw: String): + server_address_input.text = host + server_port_input.text = str(port) + username_input.text = user + password_input.text = passw + +func set_loading(loading: bool): + login_button.disabled = loading + if loading: + set_status("Connecting...", Color.YELLOW) diff --git a/clients/godot/ui/login_menu.gd.uid b/clients/godot/ui/login_menu.gd.uid new file mode 100644 index 0000000..3ef096f --- /dev/null +++ b/clients/godot/ui/login_menu.gd.uid @@ -0,0 +1 @@ +uid://6exijnvt3ax5 diff --git a/clients/godot/ui/menu.gd b/clients/godot/ui/menu.gd new file mode 100644 index 0000000..6f13fe0 --- /dev/null +++ b/clients/godot/ui/menu.gd @@ -0,0 +1,41 @@ +extends Control + +class_name MainMenu + +signal play_pressed() +signal local_play_pressed() +signal settings_pressed() +signal quit_pressed() + +@onready var title_label = $VBoxContainer/TitleLabel +@onready var play_button = $VBoxContainer/PlayButton +@onready var local_play_button = $VBoxContainer/LocalPlayButton +@onready var settings_button = $VBoxContainer/SettingsButton +@onready var quit_button = $VBoxContainer/QuitButton +@onready var version_label = $VersionLabel +@onready var status_label = $StatusLabel + +func _ready(): + play_button.grab_focus() + play_button.pressed.connect(_on_play_pressed) + local_play_button.pressed.connect(_on_local_play_pressed) + settings_button.pressed.connect(_on_settings_pressed) + quit_button.pressed.connect(_on_quit_pressed) + version_label.text = "v0.1.0" + +func set_status(text: String, color: Color = Color.WHITE): + status_label.text = text + status_label.modulate = color + +func _on_play_pressed(): + emit_signal("play_pressed") + +func _on_local_play_pressed(): + emit_signal("local_play_pressed") + +func _on_settings_pressed(): + emit_signal("settings_pressed") + +func _on_quit_pressed(): + emit_signal("quit_pressed") + get_tree().quit() diff --git a/clients/godot/ui/menu.gd.uid b/clients/godot/ui/menu.gd.uid new file mode 100644 index 0000000..a34d400 --- /dev/null +++ b/clients/godot/ui/menu.gd.uid @@ -0,0 +1 @@ +uid://ci6qruofgpdsc diff --git a/clients/godot/ui/pause_menu.gd b/clients/godot/ui/pause_menu.gd new file mode 100644 index 0000000..92e7b39 --- /dev/null +++ b/clients/godot/ui/pause_menu.gd @@ -0,0 +1,45 @@ +extends Control + +class_name PauseMenu + +signal resume_pressed() +signal settings_pressed() +signal disconnect_pressed() +signal quit_pressed() + +@onready var resume_button = $VBoxContainer/ResumeButton +@onready var settings_button = $VBoxContainer/SettingsButton +@onready var disconnect_button = $VBoxContainer/DisconnectButton +@onready var quit_button = $VBoxContainer/QuitButton + +func _ready(): + visible = false + resume_button.pressed.connect(_on_resume_pressed) + settings_button.pressed.connect(_on_settings_pressed) + disconnect_button.pressed.connect(_on_disconnect_pressed) + quit_button.pressed.connect(_on_quit_pressed) + +func toggle(): + visible = !visible + if visible: + resume_button.grab_focus() + get_tree().paused = true + else: + get_tree().paused = false + +func _on_resume_pressed(): + visible = false + get_tree().paused = false + emit_signal("resume_pressed") + +func _on_settings_pressed(): + emit_signal("settings_pressed") + +func _on_disconnect_pressed(): + visible = false + get_tree().paused = false + emit_signal("disconnect_pressed") + +func _on_quit_pressed(): + emit_signal("quit_pressed") + get_tree().quit() diff --git a/clients/godot/ui/pause_menu.gd.uid b/clients/godot/ui/pause_menu.gd.uid new file mode 100644 index 0000000..d03984b --- /dev/null +++ b/clients/godot/ui/pause_menu.gd.uid @@ -0,0 +1 @@ +uid://8ji7sj1tqt3l diff --git a/clients/godot/ui/settings_menu.gd b/clients/godot/ui/settings_menu.gd new file mode 100644 index 0000000..5d9240e --- /dev/null +++ b/clients/godot/ui/settings_menu.gd @@ -0,0 +1,130 @@ +extends Control + +class_name SettingsMenu + +signal back_pressed() +signal settings_saved(settings: Dictionary) + +# Network +@onready var host_input = $VBox/TabContainer/Network/VBox/HostInput +@onready var port_input = $VBox/TabContainer/Network/VBox/PortInput +@onready var protocol_option = $VBox/TabContainer/Network/VBox/ProtocolOption + +# Account +@onready var login_input = $VBox/TabContainer/Account/VBox/LoginInput +@onready var password_input = $VBox/TabContainer/Account/VBox/PasswordInput + +# Graphics +@onready var render_distance_slider = $VBox/TabContainer/Graphics/VBox/RenderDistance/Slider +@onready var render_distance_label = $VBox/TabContainer/Graphics/VBox/RenderDistance/Value +@onready var vsync_button = $VBox/TabContainer/Graphics/VBox/VSync/CheckBox +@onready var msaa_button = $VBox/TabContainer/Graphics/VBox/MSAA/CheckBox +@onready var fullscreen_button = $VBox/TabContainer/Graphics/VBox/Fullscreen/CheckBox +@onready var mouse_sensitivity_slider = $VBox/TabContainer/Graphics/VBox/MouseSensitivity/Slider +@onready var mouse_sensitivity_label = $VBox/TabContainer/Graphics/VBox/MouseSensitivity/Value +@onready var volume_slider = $VBox/TabContainer/Graphics/VBox/Volume/Slider +@onready var volume_label = $VBox/TabContainer/Graphics/VBox/Volume/Value + +@onready var save_button = $VBox/Buttons/SaveButton +@onready var back_button = $VBox/Buttons/BackButton + +func _ready(): + save_button.pressed.connect(_on_save_pressed) + back_button.pressed.connect(_on_back_pressed) + render_distance_slider.value_changed.connect(_on_render_distance_changed) + mouse_sensitivity_slider.value_changed.connect(_on_mouse_sensitivity_changed) + volume_slider.value_changed.connect(_on_volume_changed) + vsync_button.toggled.connect(_on_vsync_toggled) + fullscreen_button.toggled.connect(_on_fullscreen_toggled) + msaa_button.toggled.connect(_on_msaa_toggled) + load_defaults() + +func load_defaults(): + host_input.text = "127.0.0.1" + port_input.text = "8080" + protocol_option.clear() + protocol_option.add_item("Binary", 0) + protocol_option.add_item("WebSocket", 1) + protocol_option.selected = 0 + login_input.text = "" + password_input.text = "" + render_distance_slider.value = 3 + render_distance_label.text = "3" + mouse_sensitivity_slider.value = 0.002 + mouse_sensitivity_label.text = "0.002" + volume_slider.value = 80 + volume_label.text = "80%" + vsync_button.button_pressed = true + fullscreen_button.button_pressed = false + msaa_button.button_pressed = true + +func load_settings(settings: Dictionary): + if settings.has("host"): + host_input.text = settings.host + if settings.has("port"): + port_input.text = str(settings.port) + if settings.has("protocol"): + protocol_option.selected = settings.protocol + if settings.has("login"): + login_input.text = settings.login + if settings.has("password"): + password_input.text = settings.password + if settings.has("render_distance"): + render_distance_slider.value = settings.render_distance + render_distance_label.text = str(settings.render_distance) + if settings.has("mouse_sensitivity"): + mouse_sensitivity_slider.value = settings.mouse_sensitivity + mouse_sensitivity_label.text = str(settings.mouse_sensitivity) + if settings.has("volume"): + volume_slider.value = settings.volume + volume_label.text = str(int(settings.volume)) + "%" + if settings.has("vsync"): + vsync_button.button_pressed = settings.vsync + if settings.has("fullscreen"): + fullscreen_button.button_pressed = settings.fullscreen + if settings.has("msaa"): + msaa_button.button_pressed = settings.msaa + +func _on_render_distance_changed(value: float): + render_distance_label.text = str(int(value)) + +func _on_mouse_sensitivity_changed(value: float): + mouse_sensitivity_label.text = str(value) + +func _on_volume_changed(value: float): + volume_label.text = str(int(value)) + "%" + AudioServer.set_bus_volume_db(0, linear_to_db(value / 100.0)) + +func _on_vsync_toggled(pressed: bool): + DisplayServer.window_set_vsync_mode( + DisplayServer.VSYNC_ENABLED if pressed else DisplayServer.VSYNC_DISABLED + ) + +func _on_fullscreen_toggled(pressed: bool): + DisplayServer.window_set_mode( + DisplayServer.WINDOW_MODE_FULLSCREEN if pressed else DisplayServer.WINDOW_MODE_WINDOWED + ) + +func _on_msaa_toggled(pressed: bool): + get_viewport().msaa_3d = Viewport.MSAA_4X if pressed else Viewport.MSAA_DISABLED + +func _on_save_pressed(): + emit_signal("settings_saved", get_settings()) + +func _on_back_pressed(): + emit_signal("back_pressed") + +func get_settings() -> Dictionary: + return { + "host": host_input.text.strip_edges(), + "port": port_input.text.strip_edges().to_int(), + "protocol": protocol_option.selected, + "login": login_input.text.strip_edges(), + "password": password_input.text, + "render_distance": int(render_distance_slider.value), + "mouse_sensitivity": mouse_sensitivity_slider.value, + "volume": volume_slider.value, + "vsync": vsync_button.button_pressed, + "fullscreen": fullscreen_button.button_pressed, + "msaa": msaa_button.button_pressed, + } diff --git a/clients/godot/ui/settings_menu.gd.uid b/clients/godot/ui/settings_menu.gd.uid new file mode 100644 index 0000000..8dedc8b --- /dev/null +++ b/clients/godot/ui/settings_menu.gd.uid @@ -0,0 +1 @@ +uid://dbiserd16hdbx diff --git a/clients/godot/world/chunk.gd b/clients/godot/world/chunk.gd index 8535ecd..0b7ec31 100644 --- a/clients/godot/world/chunk.gd +++ b/clients/godot/world/chunk.gd @@ -2,206 +2,381 @@ extends Node3D class_name Chunk -# Chunk properties var chunk_x: int = 0 var chunk_z: int = 0 -var chunk_size: int = 16 +var chunk_size: int = 32 var chunk_height: int = 64 +var spacing: float = 1.0 var heightmap: Array = [] var biomes: Array = [] -var lod_level: int = 0 -# Mesh components var mesh_instance: MeshInstance3D var collision_shape: CollisionShape3D -var nav_region: NavigationRegion3D -# LOD meshes var lod_meshes: Array = [] var current_lod: int = 0 +var entities_container: Node3D + func initialize(x: int, z: int, size: int, height: int): chunk_x = x chunk_z = z chunk_size = size chunk_height = height - - # Initialize arrays heightmap.resize(size * size) biomes.resize(size * size) - - # Create mesh instance mesh_instance = MeshInstance3D.new() add_child(mesh_instance) + entities_container = Node3D.new() + entities_container.name = "Entities" + add_child(entities_container) + +func _pyf3d_height(wx: float, wz: float) -> float: + return (sin(wx * 0.1) * cos(wz * 0.1) + + 0.3 * sin(wx * 0.3 + 1.2) + + 0.3 * cos(wz * 0.3 + 2.4) + + 0.2 * sin((wx * 0.6 + wz * 0.4) * 0.8)) * 2.0 + 0.5 -func generate(height_data: Array = [], biome_data: Array = []): +func get_height_at(x: int, z: int) -> float: + if x >= 0 and x < chunk_size and z >= 0 and z < chunk_size: + return heightmap[z * chunk_size + x] + return 0.0 + +func generate(height_data: Array = [], _biome_data: Array = []): if not height_data.is_empty(): heightmap = height_data.duplicate() else: generate_heightmap() - - if not biome_data.is_empty(): - biomes = biome_data.duplicate() - else: - generate_biomes() - generate_mesh() func generate_heightmap(): - var noise = FastNoiseLite.new() - noise.noise_type = FastNoiseLite.TYPE_PERLIN - noise.frequency = 0.05 - noise.fractal_octaves = 4 - - for x in range(chunk_size): - for z in range(chunk_size): - var world_x = chunk_x * chunk_size + x - var world_z = chunk_z * chunk_size + z - - var height = noise.get_noise_2d(world_x, world_z) * 20 + 10 - - # Add some hills/mountains - var hill_noise = FastNoiseLite.new() - hill_noise.noise_type = FastNoiseLite.TYPE_CELLULAR - hill_noise.frequency = 0.02 - height += hill_noise.get_noise_2d(world_x, world_z) * 30 - - heightmap[z * chunk_size + x] = max(0, height) - -func generate_biomes(): - var temperature_noise = FastNoiseLite.new() - temperature_noise.noise_type = FastNoiseLite.TYPE_PERLIN - temperature_noise.frequency = 0.01 - - var moisture_noise = FastNoiseLite.new() - moisture_noise.noise_type = FastNoiseLite.TYPE_PERLIN - moisture_noise.frequency = 0.01 - for x in range(chunk_size): for z in range(chunk_size): - var world_x = chunk_x * chunk_size + x - var world_z = chunk_z * chunk_size + z - - var temperature = temperature_noise.get_noise_2d(world_x, world_z) - var moisture = moisture_noise.get_noise_2d(world_x, world_z) - - var biome = determine_biome(temperature, moisture, heightmap[z * chunk_size + x]) - biomes[z * chunk_size + x] = biome - -func determine_biome(temperature: float, moisture: float, height: float) -> int: - # Simple biome determination - if height < 5: - return 0 # Water - elif height < 10: - if moisture > 0.5: - return 1 # Beach/Swamp - else: - return 2 # Desert - elif height < 30: - if temperature > 0.3: - if moisture > 0.6: - return 3 # Forest - else: - return 4 # Plains - else: - return 5 # Tundra - else: - return 6 # Mountain + var wx = chunk_x * chunk_size + x * spacing + var wz = chunk_z * chunk_size + z * spacing + heightmap[z * chunk_size + x] = _pyf3d_height(wx, wz) func generate_mesh(): var surface_tool = SurfaceTool.new() surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES) - - for x in range(chunk_size - 1): - for z in range(chunk_size - 1): - # Create quad from two triangles - var v1 = Vector3(x, get_height(x, z), z) - var v2 = Vector3(x + 1, get_height(x + 1, z), z) - var v3 = Vector3(x, get_height(x, z + 1), z + 1) - var v4 = Vector3(x + 1, get_height(x + 1, z + 1), z + 1) - - # Calculate normals - var normal1 = (v2 - v1).cross(v3 - v1).normalized() - var normal2 = (v3 - v2).cross(v4 - v2).normalized() - - # First triangle - surface_tool.set_normal(normal1) - surface_tool.add_vertex(v1) - surface_tool.add_vertex(v2) - surface_tool.add_vertex(v3) - - # Second triangle - surface_tool.set_normal(normal2) - surface_tool.add_vertex(v2) - surface_tool.add_vertex(v4) - surface_tool.add_vertex(v3) - + for z in range(chunk_size + 1): + for x in range(chunk_size + 1): + var wx = chunk_x * chunk_size + x * spacing + var wz = chunk_z * chunk_size + z * spacing + var wy = _pyf3d_height(wx, wz) + var pos = Vector3(wx, wy, wz) + surface_tool.set_normal(Vector3.UP) + surface_tool.add_vertex(pos) + for z in range(chunk_size): + for x in range(chunk_size): + var i = z * (chunk_size + 1) + x + surface_tool.add_index(i) + surface_tool.add_index(i + 1) + surface_tool.add_index(i + chunk_size + 1) + surface_tool.add_index(i + 1) + surface_tool.add_index(i + chunk_size + 2) + surface_tool.add_index(i + chunk_size + 1) surface_tool.generate_normals() var mesh = surface_tool.commit() mesh_instance.mesh = mesh + var static_body = StaticBody3D.new() + static_body.position = Vector3.ZERO + add_child(static_body) + var trimesh_shape = mesh.create_trimesh_shape() + collision_shape = CollisionShape3D.new() + collision_shape.shape = trimesh_shape + static_body.add_child(collision_shape) -func get_height(x: int, z: int) -> float: - if x >= 0 and x < chunk_size and z >= 0 and z < chunk_size: - return heightmap[z * chunk_size + x] - return 0.0 +func _compute_normal(x: int, z: int) -> Vector3: + var hx1 = get_height_at(x + 1, z) if x + 1 < chunk_size else get_height_at(x, z) + var hx2 = get_height_at(x - 1, z) if x - 1 >= 0 else get_height_at(x, z) + var hz1 = get_height_at(x, z + 1) if z + 1 < chunk_size else get_height_at(x, z) + var hz2 = get_height_at(x, z - 1) if z - 1 >= 0 else get_height_at(x, z) + var dx = hx1 - hx2 + var dz = hz1 - hz2 + return Vector3(-dx, 2.0 * spacing, -dz).normalized() + +func spawn_entities(): + var rng = RandomNumberGenerator.new() + rng.seed = chunk_x * 1000003 + chunk_z * 1000033 + _spawn_trees(rng) + _spawn_stones(rng) + _spawn_houses(rng) + _spawn_water(rng) + _spawn_mobs(rng) + _spawn_health_boxes(rng) + +func _spawn_trees(rng: RandomNumberGenerator): + var count = rng.randi_range(5, 10) + var phys_size = (chunk_size - 1) * spacing + for _i in range(count): + var lx = rng.randf_range(1.5, phys_size - 1.5) + var lz = rng.randf_range(1.5, phys_size - 1.5) + var wx = chunk_x * chunk_size + lx + var wz = chunk_z * chunk_size + lz + var y = _pyf3d_height(wx, wz) + if y < 0.1: + continue + var trunk_height = rng.randf_range(1.8, 2.2) + var foliage_radius = rng.randf_range(1.0, 1.4) + var rot_y = rng.randf_range(0, TAU) + var tree = _create_tree(trunk_height, foliage_radius) + tree.position = Vector3(wx, y, wz) + tree.rotation.y = rot_y + entities_container.add_child(tree) + +func _create_tree(trunk_h: float, foliage_r: float) -> Node3D: + var node = Node3D.new() + var trunk_mesh = CylinderMesh.new() + trunk_mesh.top_radius = 0.3 + trunk_mesh.bottom_radius = 0.3 + trunk_mesh.height = trunk_h + var trunk_mat = StandardMaterial3D.new() + trunk_mat.albedo_color = Color(0.545, 0.271, 0.075) + trunk_mat.roughness = 0.9 + trunk_mesh.material = trunk_mat + var trunk_instance = MeshInstance3D.new() + trunk_instance.mesh = trunk_mesh + trunk_instance.position.y = trunk_h * 0.5 + node.add_child(trunk_instance) + var foliage_mesh = SphereMesh.new() + foliage_mesh.radius = foliage_r + foliage_mesh.height = foliage_r * 2.0 + var foliage_mat = StandardMaterial3D.new() + foliage_mat.albedo_color = Color(0.133, 0.545, 0.133) + foliage_mat.roughness = 0.8 + foliage_mesh.material = foliage_mat + var foliage_instance = MeshInstance3D.new() + foliage_instance.mesh = foliage_mesh + foliage_instance.position.y = trunk_h + foliage_r * 0.5 + node.add_child(foliage_instance) + return node + +func _spawn_stones(rng: RandomNumberGenerator): + var count = rng.randi_range(5, 10) + var phys_size = (chunk_size - 1) * spacing + for _i in range(count): + var lx = rng.randf_range(1.5, phys_size - 1.5) + var lz = rng.randf_range(1.5, phys_size - 1.5) + var wx = chunk_x * chunk_size + lx + var wz = chunk_z * chunk_size + lz + var y = _pyf3d_height(wx, wz) + var shade = rng.randf_range(0.4, 0.7) + var sx = rng.randf_range(0.7, 1.3) + var sy = rng.randf_range(0.5, 1.2) + var sz = rng.randf_range(0.7, 1.3) + var rot = rng.randf_range(0, TAU) + var stone = _create_stone(shade, Vector3(sx, sy, sz)) + stone.position = Vector3(wx, y, wz) + stone.rotation.y = rot + entities_container.add_child(stone) + +func _create_stone(shade: float, scl: Vector3) -> MeshInstance3D: + var mesh = BoxMesh.new() + mesh.size = Vector3(0.8, 0.8, 0.8) + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(shade, shade, shade) + mat.roughness = 0.9 + mesh.material = mat + var instance = MeshInstance3D.new() + instance.mesh = mesh + instance.scale = scl + return instance + +func _spawn_houses(rng: RandomNumberGenerator): + if rng.randf() > 0.2: + return + var phys_size = (chunk_size - 1) * spacing + var lx = rng.randf_range(3.0, phys_size - 3.0) + var lz = rng.randf_range(3.0, phys_size - 3.0) + var wx = chunk_x * chunk_size + lx + var wz = chunk_z * chunk_size + lz + var y = _pyf3d_height(wx, wz) + var s = rng.randf_range(0.8, 1.2) + var house = _create_house(s) + house.position = Vector3(wx, y, wz) + entities_container.add_child(house) + +func _create_house(s: float) -> Node3D: + var node = Node3D.new() + var wall_mat = StandardMaterial3D.new() + wall_mat.albedo_color = Color(0.8, 0.6, 0.4) + wall_mat.roughness = 0.8 + var roof_mat = StandardMaterial3D.new() + roof_mat.albedo_color = Color(0.6, 0.2, 0.1) + roof_mat.roughness = 0.7 + var door_mat = StandardMaterial3D.new() + door_mat.albedo_color = Color(0.5, 0.25, 0.0) + door_mat.roughness = 0.8 + var w = 1.5 + var d = 1.5 + var wh = 1.8 + var rh = 0.8 + var base = BoxMesh.new() + base.size = Vector3(w, wh, d) + base.material = wall_mat + var base_inst = MeshInstance3D.new() + base_inst.mesh = base + base_inst.position.y = wh * 0.5 + node.add_child(base_inst) + var roof = PrismMesh.new() + roof.size = Vector3(w + 0.2, rh, d + 0.2) + roof.material = roof_mat + var roof_inst = MeshInstance3D.new() + roof_inst.mesh = roof + roof_inst.position.y = wh + rh * 0.5 + node.add_child(roof_inst) + var door = BoxMesh.new() + door.size = Vector3(0.5, 1.5, 0.05) + door.material = door_mat + var door_inst = MeshInstance3D.new() + door_inst.mesh = door + door_inst.position = Vector3(0, 0.75, d * 0.5 + 0.03) + node.add_child(door_inst) + node.scale = Vector3(s, s, s) + return node + +func _spawn_water(_rng: RandomNumberGenerator): + var water_level = 0.3 + var has_water = false + for x in range(0, chunk_size, 4): + for z in range(0, chunk_size, 4): + if get_height_at(x, z) < water_level: + has_water = true + break + if has_water: + break + if not has_water: + return + var plane_mesh = PlaneMesh.new() + plane_mesh.size = Vector2(chunk_size * spacing, chunk_size * spacing) + var mat = StandardMaterial3D.new() + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.albedo_color = Color(0.2, 0.4, 0.8, 0.6) + mat.metallic = 0.3 + mat.roughness = 0.1 + plane_mesh.material = mat + var water_inst = MeshInstance3D.new() + water_inst.mesh = plane_mesh + water_inst.position = Vector3( + chunk_x * chunk_size + chunk_size * spacing * 0.5, + water_level, + chunk_z * chunk_size + chunk_size * spacing * 0.5 + ) + add_child(water_inst) + +func _spawn_mobs(rng: RandomNumberGenerator): + var count = rng.randi_range(0, 1) + var phys_size = (chunk_size - 1) * spacing + for _i in range(count): + var lx = rng.randf_range(2.0, phys_size - 2.0) + var lz = rng.randf_range(2.0, phys_size - 2.0) + var wx = chunk_x * chunk_size + lx + var wz = chunk_z * chunk_size + lz + var y = _pyf3d_height(wx, wz) + if y < 0.5: + continue + var mob = _create_mob(rng) + mob.position = Vector3(wx, y, wz) + mob.set_meta("health", rng.randi_range(30, 80)) + mob.set_meta("max_health", mob.get_meta("health")) + mob.set_meta("speed", 1.5) + mob.set_meta("damage", rng.randi_range(8, 20)) + mob.set_meta("collision_radius", 0.6) + entities_container.add_child(mob) + +func _create_mob(_rng: RandomNumberGenerator) -> Node3D: + var node = Node3D.new() + var mob_mat = StandardMaterial3D.new() + mob_mat.albedo_color = Color(0.8, 0.2, 0.2) + mob_mat.roughness = 0.7 + var body = SphereMesh.new() + body.radius = 0.4 + body.height = 0.6 + body.material = mob_mat + var body_inst = MeshInstance3D.new() + body_inst.mesh = body + body_inst.position.y = 0.5 + node.add_child(body_inst) + var head = SphereMesh.new() + head.radius = 0.25 + head.height = 0.5 + head.material = mob_mat + var head_inst = MeshInstance3D.new() + head_inst.mesh = head + head_inst.position = Vector3(0, 0.9, 0.25) + node.add_child(head_inst) + for i in range(4): + var leg = SphereMesh.new() + leg.radius = 0.15 + leg.height = 0.3 + leg.material = mob_mat + var leg_inst = MeshInstance3D.new() + leg_inst.mesh = leg + var lx = 0.2 if i % 2 == 0 else -0.2 + var lz = 0.25 if i < 2 else -0.25 + leg_inst.position = Vector3(lx, 0.15, lz) + node.add_child(leg_inst) + return node + +func _spawn_health_boxes(rng: RandomNumberGenerator): + var lx = rng.randf_range(2.0, (chunk_size - 2.0) * spacing) + var lz = rng.randf_range(2.0, (chunk_size - 2.0) * spacing) + var wx = chunk_x * chunk_size + lx + var wz = chunk_z * chunk_size + lz + var y = _pyf3d_height(wx, wz) + if y < 0.1: + return + var health_box = _create_health_box() + health_box.position = Vector3(wx, y + 0.8, wz) + health_box.set_meta("health_restore", 10) + entities_container.add_child(health_box) + +func _create_health_box() -> Node3D: + var node = Node3D.new() + var box_mesh = BoxMesh.new() + box_mesh.size = Vector3(0.8, 0.8, 0.8) + var mat = StandardMaterial3D.new() + mat.albedo_color = Color(1.0, 1.0, 1.0) + mat.emission_enabled = true + mat.emission = Color(1.0, 0.2, 0.2) + mat.emission_energy_multiplier = 0.5 + box_mesh.material = mat + var box_inst = MeshInstance3D.new() + box_inst.mesh = box_mesh + node.add_child(box_inst) + var cross_h = BoxMesh.new() + cross_h.size = Vector3(0.5, 0.15, 0.05) + var cross_mat = StandardMaterial3D.new() + cross_mat.albedo_color = Color(1.0, 0.0, 0.0) + cross_h.material = cross_mat + var cross_inst_h = MeshInstance3D.new() + cross_inst_h.mesh = cross_h + cross_inst_h.position.z = -0.41 + node.add_child(cross_inst_h) + var cross_v = BoxMesh.new() + cross_v.size = Vector3(0.15, 0.5, 0.05) + cross_v.material = cross_mat + var cross_inst_v = MeshInstance3D.new() + cross_inst_v.mesh = cross_v + cross_inst_v.position.z = -0.41 + node.add_child(cross_inst_v) + return node func setup_lod(distances: Array): - lod_meshes.resize(distances.size()) - lod_meshes[0] = mesh_instance.mesh - - # Generate lower LOD meshes - for i in range(1, distances.size()): - lod_meshes[i] = generate_lod_mesh(i + 1) - -func generate_lod_mesh(step: int) -> Mesh: - var surface_tool = SurfaceTool.new() - surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES) - - for x in range(0, chunk_size - step, step): - for z in range(0, chunk_size - step, step): - # Create quad with lower resolution - var v1 = Vector3(x, get_height(x, z), z) - var v2 = Vector3(x + step, get_height(x + step, z), z) - var v3 = Vector3(x, get_height(x, z + step), z + step) - var v4 = Vector3(x + step, get_height(x + step, z + step), z + step) - - var normal1 = (v2 - v1).cross(v3 - v1).normalized() - var normal2 = (v3 - v2).cross(v4 - v2).normalized() - - surface_tool.set_normal(normal1) - surface_tool.add_vertex(v1) - surface_tool.add_vertex(v2) - surface_tool.add_vertex(v3) - - surface_tool.set_normal(normal2) - surface_tool.add_vertex(v2) - surface_tool.add_vertex(v4) - surface_tool.add_vertex(v3) - - surface_tool.generate_normals() - return surface_tool.commit() + lod_meshes = distances func update_lod(distance: float): if lod_meshes.is_empty(): return - var new_lod = 0 - for i in range(lod_meshes.size() - 1, -1, -1): - if distance > 20 * (i + 1): # Simplified distance calculation - new_lod = i - break - - if new_lod != current_lod and new_lod < lod_meshes.size(): + for i in range(lod_meshes.size()): + if distance > lod_meshes[i]: + new_lod = i + 1 + if new_lod != current_lod: current_lod = new_lod - mesh_instance.mesh = lod_meshes[new_lod] - -func load_from_data(chunk_data: Dictionary): - if chunk_data.has("heightmap"): - heightmap = chunk_data.heightmap - if chunk_data.has("biomes"): - biomes = chunk_data.biomes - - generate_mesh() + if mesh_instance: + mesh_instance.visible = current_lod == 0 func apply_material(material: Material): if mesh_instance: @@ -219,4 +394,6 @@ func set_wireframe(enabled: bool): func _exit_tree(): if mesh_instance: - mesh_instance.queue_free() \ No newline at end of file + mesh_instance.queue_free() + if collision_shape: + collision_shape.queue_free() diff --git a/clients/godot/world/terrain.gd b/clients/godot/world/terrain.gd index bd4b0ea..7318c51 100644 --- a/clients/godot/world/terrain.gd +++ b/clients/godot/world/terrain.gd @@ -124,8 +124,8 @@ func calculate_height(x: float, z: float) -> float: # Ensure minimum height return max(0.0, height) -func add_terrain_features(x: float, z: float, base_height: float) -> float: - var height = base_height +func add_terrain_features(x: float, z: float, h: float) -> float: + var height = h # Add hills var hill_noise = FastNoiseLite.new() diff --git a/clients/godot/world/world.gd b/clients/godot/world/world.gd index 0faccea..405ce1c 100644 --- a/clients/godot/world/world.gd +++ b/clients/godot/world/world.gd @@ -1,42 +1,22 @@ extends Node3D -class ChunkLoader: - func load_chunk(chunk_x: int, chunk_z: int, chunk_size: int) -> Dictionary: - # This would load chunk data from disk or network - # For now, return empty data - return { - "chunk_x": chunk_x, - "chunk_z": chunk_z, - "heightmap": [], - "biomes": [] - } +class_name GameWorld -# World parameters -@export var chunk_size: int = 16 +@export var chunk_size: int = 32 @export var chunk_height: int = 64 @export var render_distance: int = 3 @export var lod_distances: Array = [20.0, 40.0, 80.0] -# Materials -@onready var terrain_material = preload("res://materials/terrain.tres") -@onready var water_material = preload("res://materials/water.tres") - -# World state +var terrain_material: ShaderMaterial +var water_material: StandardMaterial3D var chunks: Dictionary = {} var active_chunks: Array = [] -var chunk_loader: ChunkLoader var terrain_generator: TerrainGenerator - -# Performance var max_chunks_per_frame: int = 1 var wireframe_mode: bool = false -var show_debug: bool = false func _ready(): - chunk_loader = ChunkLoader.new() terrain_generator = TerrainGenerator.new() - - # Setup materials setup_materials() func initialize(size: int, height: int, distance: int, lod_array: Array): @@ -44,44 +24,37 @@ func initialize(size: int, height: int, distance: int, lod_array: Array): chunk_height = height render_distance = distance lod_distances = lod_array - print("World initialized: %dx%d chunks, render distance: %d" % [size, size, distance]) func create_chunk(chunk_x: int, chunk_z: int, chunk_data: Dictionary = {}): var chunk_key = Vector2(chunk_x, chunk_z) - if chunks.has(chunk_key): return chunks[chunk_key] - - # Create new chunk var chunk = Chunk.new() chunk.initialize(chunk_x, chunk_z, chunk_size, chunk_height) chunk.name = "Chunk_%d_%d" % [chunk_x, chunk_z] - chunk.position = Vector3(chunk_x * chunk_size, 0, chunk_z * chunk_size) + chunk.position = Vector3.ZERO add_child(chunk) - - # Generate or load terrain if chunk_data.is_empty(): - terrain_generator.generate_terrain(chunk) + chunk.generate() else: chunk.load_from_data(chunk_data) - - # Setup LOD - chunk.setup_lod(lod_distances) - - # Apply materials chunk.apply_material(terrain_material) - + chunk.spawn_entities() chunks[chunk_key] = chunk active_chunks.append(chunk_key) - return chunk func update_lod(viewer_position: Vector3): for chunk_key in active_chunks: var chunk = chunks[chunk_key] if chunk: - var distance = viewer_position.distance_to(chunk.global_transform.origin + Vector3(chunk_size/2, 0, chunk_size/2)) + var chunk_center = Vector3( + chunk.chunk_x * chunk_size + chunk_size / 2.0, + 0, + chunk.chunk_z * chunk_size + chunk_size / 2.0 + ) + var distance = viewer_position.distance_to(chunk_center) chunk.update_lod(distance) func clear_chunks(): @@ -89,64 +62,55 @@ func clear_chunks(): var chunk = chunks[chunk_key] if chunk: chunk.queue_free() - chunks.clear() active_chunks.clear() func get_height_at(x: float, z: float) -> float: - var chunk_x = int(x / chunk_size) - var chunk_z = int(z / chunk_size) + var chunk_x = int(floor(x / chunk_size)) + var chunk_z = int(floor(z / chunk_size)) var chunk_key = Vector2(chunk_x, chunk_z) - if chunks.has(chunk_key): var chunk = chunks[chunk_key] var local_x = int(x - chunk_x * chunk_size) var local_z = int(z - chunk_z * chunk_size) - return chunk.get_height(local_x, local_z) - - return 0.0 - -func add_water_chunk(chunk_x: int, chunk_z: int, water_level: float = 10.0): - var chunk_key = Vector2(chunk_x, chunk_z) - - var water_mesh = MeshInstance3D.new() - var plane_mesh = PlaneMesh.new() - plane_mesh.size = Vector2(chunk_size, chunk_size) - - water_mesh.mesh = plane_mesh - water_mesh.material_override = water_material - water_mesh.position = Vector3(chunk_x * chunk_size + chunk_size/2, water_level, chunk_z * chunk_size + chunk_size/2) - - add_child(water_mesh) - return water_mesh + return chunk.get_height_at(local_x, local_z) + return _pyf3d_height(x, z) -func toggle_wireframe(): - wireframe_mode = !wireframe_mode - for chunk in chunks.values(): - chunk.set_wireframe(wireframe_mode) +func _pyf3d_height(wx: float, wz: float) -> float: + return (sin(wx * 0.1) * cos(wz * 0.1) + + 0.3 * sin(wx * 0.3 + 1.2) + + 0.3 * cos(wz * 0.3 + 2.4) + + 0.2 * sin((wx * 0.6 + wz * 0.4) * 0.8)) * 2.0 + 0.5 func setup_materials(): - # Setup terrain material with noise - if terrain_material: - var noise = FastNoiseLite.new() - noise.noise_type = FastNoiseLite.TYPE_SIMPLEX - noise.frequency = 0.05 - terrain_material.set_shader_parameter("noise_texture", noise) - -func get_nearby_chunks(position: Vector3, radius: float) -> Array: + var shader = load("res://shaders/terrain.gdshader") + if shader: + terrain_material = ShaderMaterial.new() + terrain_material.shader = shader + else: + terrain_material = null + water_material = StandardMaterial3D.new() + water_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + water_material.albedo_color = Color(0.2, 0.4, 0.8, 0.7) + water_material.metallic = 0.3 + water_material.roughness = 0.1 + +func get_nearby_chunks(pos: Vector3, radius: float) -> Array: var nearby = [] - var chunk_x = int(position.x / chunk_size) - var chunk_z = int(position.z / chunk_size) - + var chunk_x = int(floor(pos.x / chunk_size)) + var chunk_z = int(floor(pos.z / chunk_size)) var radius_chunks = int(radius / chunk_size) + 1 - for x in range(chunk_x - radius_chunks, chunk_x + radius_chunks + 1): for z in range(chunk_z - radius_chunks, chunk_z + radius_chunks + 1): var chunk_key = Vector2(x, z) if chunks.has(chunk_key): nearby.append(chunks[chunk_key]) - return nearby +func toggle_wireframe(): + wireframe_mode = !wireframe_mode + for chunk in chunks.values(): + chunk.set_wireframe(wireframe_mode) + func _exit_tree(): clear_chunks()