diff --git a/Net/net.gd b/Net/net.gd index 69f3095..fed671e 100644 --- a/Net/net.gd +++ b/Net/net.gd @@ -27,12 +27,10 @@ var connected := false var current_map := "world" var map_ready := false -# Камень-преследователь: его считает один клиент (владелец), остальные повторяют -var rock: Node3D = null # Камень этой сцены (регистрирует сам) +# Преследователи (камень, Верити…): их все считает один клиент (владелец), остальные повторяют +var chasers := {} # id -> Node3D; преследователи этой сцены, регистрируют себя сами +var chaser_states := {} # id -> {"pos": Vector3, "yaw": float} — что прислал владелец var rock_owner := 0 # peer_id владельца (0 — ещё не назначен) -var rock_pos := Vector3.ZERO -var rock_yaw := 0.0 -var rock_has_state := false var dead_peers := {} # peer_id -> true, пока игрока поймали var hooked_player: Node = null @@ -142,9 +140,9 @@ func submit_state(pos: Vector3, yaw: float, pitch: float, flags: int) -> void: for other in _room(peer_maps.get(sender, ""), sender): recv_state.rpc_id(other, sender, pos, yaw, pitch, flags) -# Владелец камня -> сервер: позиция камня. Сервер пересылает остальным на той же карте. +# Владелец -> сервер: позиции всех преследователей. Сервер пересылает остальным на той же карте. @rpc("any_peer", "unreliable_ordered") -func submit_rock(pos: Vector3, yaw: float) -> void: +func submit_chasers(ids: PackedStringArray, positions: PackedVector3Array, yaws: PackedFloat32Array) -> void: if not is_server: return var sender := multiplayer.get_remote_sender_id() @@ -153,7 +151,7 @@ func submit_rock(pos: Vector3, yaw: float) -> void: return rock_seen[map] = Time.get_ticks_msec() / 1000.0 for other in _room(map, sender): - recv_rock.rpc_id(other, pos, yaw) + recv_chasers.rpc_id(other, ids, positions, yaws) # Клиент -> сервер: его поймали (dead) или он возродился в точке pos. @rpc("any_peer", "reliable") @@ -210,7 +208,7 @@ func _reset_room_state() -> void: ghosts.clear() dead_peers.clear() rock_owner = 0 - rock_has_state = false + chaser_states.clear() hooked_player = null map_ready = false @@ -245,12 +243,11 @@ func recv_rock_owner(id: int) -> void: rock_owner = id @rpc("authority", "unreliable_ordered") -func recv_rock(pos: Vector3, yaw: float) -> void: +func recv_chasers(ids: PackedStringArray, positions: PackedVector3Array, yaws: PackedFloat32Array) -> void: if not map_ready: return - rock_pos = pos - rock_yaw = yaw - rock_has_state = true + for i in ids.size(): + chaser_states[ids[i]] = {"pos": positions[i], "yaw": yaws[i]} @rpc("authority", "reliable") func recv_life(id: int, dead: bool, pos: Vector3) -> void: @@ -309,5 +306,15 @@ func _process(delta: float) -> void: return _hook_player(player) submit_state.rpc_id(1, player.global_position, player.rotation.y, player.head.rotation.x, player.get_net_flags()) - if rock != null and rock_is_local(): - submit_rock.rpc_id(1, rock.global_position, rock.rotation.y) + if not chasers.is_empty() and rock_is_local(): + var ids := PackedStringArray() + var positions := PackedVector3Array() + var yaws := PackedFloat32Array() + for id in chasers: + var c: Node3D = chasers[id] + if is_instance_valid(c): + ids.append(id) + positions.append(c.global_position) + yaws.append(c.rotation.y) + if not ids.is_empty(): + submit_chasers.rpc_id(1, ids, positions, yaws) diff --git a/World/Backrooms/backrooms.tscn b/World/Backrooms/backrooms.tscn index 6fd5631..089656d 100644 --- a/World/Backrooms/backrooms.tscn +++ b/World/Backrooms/backrooms.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://gmpkjl6m3415" path="res://World/Backrooms/backrooms.gd" id="1_level"] [ext_resource type="PackedScene" uid="uid://b8ef3wy1pthgm" path="res://models/backrooms/backrooms_vr.glb" id="2_map"] [ext_resource type="PackedScene" uid="uid://c3efk6k8xnf57" path="res://Player/Player.tscn" id="3_player"] -[ext_resource type="PackedScene" path="res://models/Bochi-Rock/rock.tscn" id="4_rock"] +[ext_resource type="PackedScene" path="res://models/verity/verity.tscn" id="4_rock"] [ext_resource type="Script" uid="uid://bu8v0ivhmnykt" path="res://World/portal.gd" id="5_portal"] [ext_resource type="NavigationMesh" path="res://World/Backrooms/backrooms_nav.res" id="6_nav"] @@ -16,11 +16,12 @@ transform = Transform3D(1.4, 0, 0, 0, 1.4, 0, 0, 0, 1.4, 0, 0, 0) [node name="Player" parent="." unique_id=143512730 instance=ExtResource("3_player")] transform = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -0.4838078, 1.2, 2.0468445) -[node name="Rock" parent="." unique_id=311663607 instance=ExtResource("4_rock")] +[node name="Verity" parent="." unique_id=311663607 instance=ExtResource("4_rock")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -48, 0, 3) speed = 10.0 catch_distance = 2.0 catch_height = 3.0 +model_scale = 0.5 sight_limited = true [node name="Exit" type="Area3D" parent="." unique_id=1540431614] diff --git a/export_presets.cfg b/export_presets.cfg index fa5ed5b..7c058d7 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -10,7 +10,7 @@ dedicated_server=false custom_features="" export_filter="all_resources" include_filter="" -exclude_filter="models/backrooms/*, World/Backrooms/*, main.tscn, World/Floor/*, World/Sky/*, World/tron_01.ogg, World/forest.gd, World/birds.gd, World/levitate.gd, World/nav-bake.gd, models/building_02*, models/tower_01/*, models/house_02/*, models/cyber-office/*, models/obelisk_01/*, models/Tree_01/*, models/empire-state/*" +exclude_filter="models/verity/*, models/backrooms/*, World/Backrooms/*, main.tscn, World/Floor/*, World/Sky/*, World/tron_01.ogg, World/forest.gd, World/birds.gd, World/levitate.gd, World/nav-bake.gd, models/building_02*, models/tower_01/*, models/house_02/*, models/cyber-office/*, models/obelisk_01/*, models/Tree_01/*, models/empire-state/*" export_path="../grid-world/index.html" patches=PackedStringArray() patch_delta_encoding=false diff --git a/main.tscn b/main.tscn index 5aa7a2c..12ee0ac 100644 --- a/main.tscn +++ b/main.tscn @@ -19,6 +19,7 @@ [ext_resource type="PackedScene" uid="uid://bmprcwbt78ghq" path="res://models/empire-state/Empire-State.glb" id="17_yaehf"] [ext_resource type="Script" uid="uid://c2k1kj4a2s2su" path="res://World/nav-bake.gd" id="18_navbake"] [ext_resource type="Script" uid="uid://bu8v0ivhmnykt" path="res://World/portal.gd" id="19_portal"] +[ext_resource type="PackedScene" path="res://models/verity/verity.tscn" id="20_verity"] [sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_6opy2"] panorama = ExtResource("2_5vw27") @@ -142,3 +143,13 @@ target_map = "hub" target_scene = "res://World/Hub/hub.tscn" title = "HUB" color = Color(0.85, 0.9, 1, 1) + +[node name="Verity" parent="." instance=ExtResource("20_verity")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -60, 0, 40) +speed = 12.0 +sight_limited = true +sight_range = 55.0 +sight_fov = 110.0 +hearing_radius = 10.0 +lose_time = 6.0 +wander_radius = 50.0 diff --git a/models/Bochi-Rock/rock-follow.gd b/models/Bochi-Rock/rock-follow.gd index 2ff5ac5..16d2f47 100644 --- a/models/Bochi-Rock/rock-follow.gd +++ b/models/Bochi-Rock/rock-follow.gd @@ -1,10 +1,13 @@ extends Node3D -# Камень преследует ближайшего живого игрока, обходя препятствия по навмешу, и ловит его. +# Преследователь (камень, Верити…): гонится за ближайшим живым игроком, обходя препятствия по навмешу, и ловит его. # С sight_limited он замечает игрока лишь вблизи или в поле зрения без стен между ними; # потеряв из виду, идёт к последнему месту, где видел, и дальше бродит по карте. # В сети камень считает один клиент (владелец, его назначает сервер), остальные # плавно повторяют его позицию — поэтому у всех игроков камень один и тот же. +@export var chaser_id := "rock" # Имя в сети: по нему владелец и остальные клиенты сопоставляют преследователей +@export var model_scale := 1.0 # Масштаб узла Model (если он есть): уменьшаем в тесных картах +@export var emission_energy := -1.0 # Модель светится сама (чёрная основа, цвет = свечение × это число); меньше 0 — как в модели @export var speed := 15.0 # Скорость движения @export var turn_speed := 6.0 # Как быстро разворачивается к игроку @export var catch_distance := 3.0 # С какого расстояния (по горизонтали) ловит игрока @@ -43,14 +46,31 @@ func _ready() -> void: agent.target_desired_distance = 1.0 agent.path_desired_distance = 1.5 agent.path_height_offset = 0.25 # Навмеш лежит чуть выше пола + var model := get_node_or_null("Model") + if model != null and model_scale != 1.0: + model.scale = Vector3.ONE * model_scale + if emission_energy >= 0.0: + _set_emission(emission_energy) for body in find_children("*", "CollisionObject3D", true, false): _own_rids.append(body.get_rid()) # Луч зрения не должен упираться в сам камень - Net.rock = self + Net.chasers[chaser_id] = self Net.peer_respawned.connect(_on_respawned) +# Копируем материалы модели (они общие для всех её экземпляров) и делаем их чисто светящимися: +# без белой основы лицо не выгорает на ярком свету и не пропадает при слабом свечении +func _set_emission(energy: float) -> void: + for mi in find_children("*", "MeshInstance3D", true, false): + for s in mi.mesh.get_surface_count(): + var mat := mi.get_active_material(s) as BaseMaterial3D + if mat != null and mat.emission_enabled: + var copy := mat.duplicate() as BaseMaterial3D + copy.albedo_color = Color.BLACK + copy.emission_energy_multiplier = energy + mi.set_surface_override_material(s, copy) + func _exit_tree() -> void: - if Net.rock == self: - Net.rock = null + if Net.chasers.get(chaser_id) == self: + Net.chasers.erase(chaser_id) func _physics_process(delta: float) -> void: if player == null: @@ -223,13 +243,14 @@ func _move_to(dest: Vector3, spd: float, stop_dist: float, delta: float) -> bool # Камень считает другой клиент — плавно идём к тому, что он прислал func _follow_owner(delta: float) -> void: - if not Net.rock_has_state: + var state = Net.chaser_states.get(chaser_id) + if state == null: return - if global_position.distance_to(Net.rock_pos) > snap_distance: - global_position = Net.rock_pos + if global_position.distance_to(state.pos) > snap_distance: + global_position = state.pos else: - global_position = global_position.lerp(Net.rock_pos, 1.0 - exp(-sync_smoothing * delta)) - rotation.y = lerp_angle(rotation.y, Net.rock_yaw, 1.0 - exp(-sync_smoothing * delta)) + global_position = global_position.lerp(state.pos, 1.0 - exp(-sync_smoothing * delta)) + rotation.y = lerp_angle(rotation.y, state.yaw, 1.0 - exp(-sync_smoothing * delta)) # Кто-то (мы или другой игрок) возродился. Если камень рядом с точкой возрождения — # уводим его на старт, чтобы не поймал снова сразу. Решает только владелец камня. diff --git a/models/verity/verity.glb b/models/verity/verity.glb new file mode 100644 index 0000000..15281d4 Binary files /dev/null and b/models/verity/verity.glb differ diff --git a/models/verity/verity.glb.import b/models/verity/verity.glb.import new file mode 100644 index 0000000..f712602 --- /dev/null +++ b/models/verity/verity.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cn50f1trssdya" +path="res://.godot/imported/verity.glb-aae30371d8b76c9d120dd8fbd4a369c2.scn" + +[deps] + +source_file="res://models/verity/verity.glb" +dest_files=["res://.godot/imported/verity.glb-aae30371d8b76c9d120dd8fbd4a369c2.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/models/verity/verity.ogg b/models/verity/verity.ogg new file mode 100644 index 0000000..087486d Binary files /dev/null and b/models/verity/verity.ogg differ diff --git a/models/verity/verity.ogg.import b/models/verity/verity.ogg.import new file mode 100644 index 0000000..b2426bc --- /dev/null +++ b/models/verity/verity.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://b7qmenkjh1wdk" +path="res://.godot/imported/verity.ogg-c8d59770635466ff43367f93a436eb90.oggvorbisstr" + +[deps] + +source_file="res://models/verity/verity.ogg" +dest_files=["res://.godot/imported/verity.ogg-c8d59770635466ff43367f93a436eb90.oggvorbisstr"] + +[params] + +loop=true +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/models/verity/verity.tscn b/models/verity/verity.tscn new file mode 100644 index 0000000..f65e4b0 --- /dev/null +++ b/models/verity/verity.tscn @@ -0,0 +1,25 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://models/Bochi-Rock/rock-follow.gd" id="1_script"] +[ext_resource type="PackedScene" path="res://models/verity/verity2.glb" id="2_model"] +[ext_resource type="AudioStream" path="res://models/verity/verity.ogg" id="3_audio"] + +[node name="Verity" type="Node3D"] +script = ExtResource("1_script") +chaser_id = "verity" +emission_energy = 1.0 + +[node name="Model" type="Node3D" parent="."] + +[node name="Mesh" parent="Model" instance=ExtResource("2_model")] +transform = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 1.59, 0) + +[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] +height = 5.0 +radius = 2.0 + +[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +stream = ExtResource("3_audio") +autoplay = true +max_distance = 35.0 diff --git a/models/verity/verity2.glb b/models/verity/verity2.glb new file mode 100644 index 0000000..19d3169 Binary files /dev/null and b/models/verity/verity2.glb differ diff --git a/models/verity/verity2.glb.import b/models/verity/verity2.glb.import new file mode 100644 index 0000000..c338da7 --- /dev/null +++ b/models/verity/verity2.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://wup5p3waumce" +path="res://.godot/imported/verity2.glb-74448d25644050663fdf5f7cb5b2b561.scn" + +[deps] + +source_file="res://models/verity/verity2.glb" +dest_files=["res://.godot/imported/verity2.glb-74448d25644050663fdf5f7cb5b2b561.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/models/verity/verity2_unnamed.webp b/models/verity/verity2_unnamed.webp new file mode 100644 index 0000000..7fe9047 Binary files /dev/null and b/models/verity/verity2_unnamed.webp differ diff --git a/models/verity/verity2_unnamed.webp.import b/models/verity/verity2_unnamed.webp.import new file mode 100644 index 0000000..b881613 --- /dev/null +++ b/models/verity/verity2_unnamed.webp.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4fe3eo5c2w6g" +path="res://.godot/imported/verity2_unnamed.webp-5857f5fcb0e2a440b744a4c083bfb79c.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "5d3e31c7af9c06dc059d9fa5104b94dd" +} + +[deps] + +source_file="res://models/verity/verity2_unnamed.webp" +dest_files=["res://.godot/imported/verity2_unnamed.webp-5857f5fcb0e2a440b744a4c083bfb79c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/models/verity/verity_unnamed.webp b/models/verity/verity_unnamed.webp new file mode 100644 index 0000000..7fe9047 Binary files /dev/null and b/models/verity/verity_unnamed.webp differ diff --git a/models/verity/verity_unnamed.webp.import b/models/verity/verity_unnamed.webp.import new file mode 100644 index 0000000..8f0abb0 --- /dev/null +++ b/models/verity/verity_unnamed.webp.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxiwv7ayauag5" +path.s3tc="res://.godot/imported/verity_unnamed.webp-57398071db19959d3dcf9d9f175390fc.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} +generator_parameters={ +"md5": "5d3e31c7af9c06dc059d9fa5104b94dd" +} + +[deps] + +source_file="res://models/verity/verity_unnamed.webp" +dest_files=["res://.godot/imported/verity_unnamed.webp-57398071db19959d3dcf9d9f175390fc.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0