Merge remote-tracking branch 'origin/client'
# Conflicts: # CHANGELOG.md
This commit is contained in:
@@ -8,6 +8,17 @@ Format based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
### Added
|
||||
- Sprint 5 "Live" team briefings — copy (6 tickets), client (2), CI (1), joint coordination for content-at-scale sprint targeting FRIEND packs, voice patterns, NPC style guide, PC-as-NPC authoring, UI microcopy, FactId validation
|
||||
- Live server mode (`make game`) — single command builds server, launches client with TCP connection, auto-kills server on exit; `make stop` helper for manual cleanup
|
||||
- `SR_LIVE=1` environment variable switches SimBridge from test mode to real TCP server connection
|
||||
- TileKind in wire protocol (#412) — server sends Floor/Wall/Door/Object per visible tile, client renders walls and floors in live mode
|
||||
- Input roundtrip integration test (#411) — spawns real server, connects via TCP, validates full movement and interact pipeline
|
||||
- Debug logging for received player inputs on server (visible with `RUST_LOG=debug`)
|
||||
|
||||
### Fixed
|
||||
- Interaction prompt target+verb data now attached to Interact action in game loop (#405) — was TODO stub, server receives `{target_entity_id, verb}` payload
|
||||
- Player entity detection uses `kind.variant == "Player"` instead of hardcoded `entity_id == 1` — fixes "player not found" warnings when connected to real server (which assigns different IDs)
|
||||
- Movement keys now work in live mode — client was sending millisecond timestamps as input tick, server only processes ticks <= current frame counter; now uses server tick from latest snapshot
|
||||
- Entity-to-tile alignment in live mode — server sends tile-center render coords (tile 16 → 16.5), entity renderer now floors to tile index before positioning
|
||||
|
||||
### Added
|
||||
- Content loader Phase 2 (#408) — 2-phase spawn pipeline loads real YAML content into ECS entities: enums, entity attributes, pools, templates, triangles, NPC profiles with Want/Tolerance/Contentment/Personality/Tells/Skills axes plus cross-reference resolution for Secrets, Relationships, Information, and DailyRoutine
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
GODOT := $(shell command -v godot4 2>/dev/null || command -v godot 2>/dev/null)
|
||||
|
||||
.PHONY: help setup build client server test lint ci ci-client ci-server clean \
|
||||
.PHONY: help setup build client server game stop test lint ci ci-client ci-server clean \
|
||||
decisions-sync decisions-coverage decisions-active decisions-orphan \
|
||||
db-backup db-install validate-content content-ron
|
||||
|
||||
@@ -14,7 +14,9 @@ help:
|
||||
@echo ""
|
||||
@echo " make setup Install dev dependencies (Rust, Godot, tooling)"
|
||||
@echo " make build Build client and server"
|
||||
@echo " make client Run the Godot client"
|
||||
@echo " make game Build and run the full game (server + client)"
|
||||
@echo " make stop Stop any running server instance"
|
||||
@echo " make client Run the Godot client (test mode)"
|
||||
@echo " make server Run the Rust simulation server"
|
||||
@echo " make test Run all tests"
|
||||
@echo " make lint Run all linters"
|
||||
@@ -72,6 +74,19 @@ client:
|
||||
@test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; }
|
||||
$(GODOT) --path client
|
||||
|
||||
game: stop build-server
|
||||
@test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; }
|
||||
@echo "Starting server..."
|
||||
@cd server && cargo run &
|
||||
@sleep 2
|
||||
@echo "Starting client..."
|
||||
@SR_LIVE=1 $(GODOT) --path client
|
||||
@$(MAKE) stop
|
||||
|
||||
stop:
|
||||
@lsof -ti :9876 | xargs -r kill 2>/dev/null || true
|
||||
@echo "Stopped any running server on port 9876"
|
||||
|
||||
# --- Test ---
|
||||
|
||||
test: test-server test-client
|
||||
|
||||
@@ -30,19 +30,29 @@ func apply_snapshot(snapshot: Dictionary) -> void:
|
||||
|
||||
if snapshot.has("entities"):
|
||||
visible_entities = snapshot.entities
|
||||
# Derive player position from the player entity
|
||||
# Derive player position from the entity with kind.variant == "Player"
|
||||
var found_player := false
|
||||
for entity in visible_entities:
|
||||
if entity.has("entity_id") and entity.entity_id == player_entity_id:
|
||||
if entity.has("kind") and entity.kind is Dictionary and entity.kind.get("variant") == "Player":
|
||||
player_position = Vector2(entity.x, entity.y)
|
||||
if entity.has("entity_id"):
|
||||
player_entity_id = entity.entity_id
|
||||
found_player = true
|
||||
break
|
||||
if not found_player and visible_entities.size() > 0:
|
||||
push_warning("GameState: player entity_id %d not found in %d entities" % [
|
||||
player_entity_id, visible_entities.size()])
|
||||
push_warning("GameState: no Player entity found in %d entities" % [
|
||||
visible_entities.size()])
|
||||
|
||||
# Tiles for rendering: test mode sends "tiles", live server sends tile data in "visible_tiles"
|
||||
if snapshot.has("tiles"):
|
||||
visible_tiles = snapshot.tiles
|
||||
elif snapshot.has("visible_tiles") and snapshot.visible_tiles is Array and snapshot.visible_tiles.size() > 0:
|
||||
# Live server: visible_tiles now includes type from tile_kind field
|
||||
var has_type := false
|
||||
if snapshot.visible_tiles.size() > 0 and snapshot.visible_tiles[0] is Dictionary:
|
||||
has_type = snapshot.visible_tiles[0].has("type")
|
||||
if has_type:
|
||||
visible_tiles = snapshot.visible_tiles
|
||||
|
||||
if snapshot.has("visible_positions"):
|
||||
visible_positions.clear()
|
||||
|
||||
@@ -4,7 +4,7 @@ extends Node
|
||||
enum ConnectionState { DISCONNECTED, CONNECTING, CONNECTED, ERROR }
|
||||
|
||||
var state: ConnectionState = ConnectionState.DISCONNECTED
|
||||
var test_mode: bool = true # Enable test mode for development without Rust server
|
||||
var test_mode: bool = OS.get_environment("SR_LIVE") != "1" # SR_LIVE=1 connects to real server
|
||||
var _test_tick: int = 0
|
||||
var _test_player_pos: Vector2i = Vector2i(10, 10)
|
||||
var _test_facing: String = "North"
|
||||
@@ -181,7 +181,9 @@ func send_input(player_input: Dictionary) -> Error:
|
||||
if action_name.is_empty():
|
||||
# _action_enum_to_wire already emits push_warning for invalid actions
|
||||
return ERR_INVALID_PARAMETER
|
||||
var tick: int = player_input.get("timestamp_msec", 0)
|
||||
# Use the server's current tick so drain_for_tick processes this input immediately.
|
||||
# The client-side timestamp_msec is only useful for ordering within a frame.
|
||||
var tick: int = GameState.current_tick
|
||||
var entry: Dictionary = { "tick": tick, "action_name": action_name }
|
||||
# Data variants (e.g. UsePerceptionMode) carry payload
|
||||
var action_data: Variant = player_input.get("action_data")
|
||||
|
||||
@@ -29,12 +29,11 @@ func _process(_delta: float) -> void:
|
||||
# Send queued input to simulation
|
||||
var inputs = InputMapper.flush_queue()
|
||||
for input in inputs:
|
||||
# TODO: Once server accepts Interact(InteractData), attach target + verb:
|
||||
# if input.action == InputMapper.Action.INTERACT:
|
||||
# var target_id: int = interaction_prompt.get_interaction_target()
|
||||
# if target_id >= 0:
|
||||
# input["action_data"] = {
|
||||
# "target_entity_id": target_id,
|
||||
# "verb": interaction_prompt.get_selected_verb(),
|
||||
# }
|
||||
if input.action == InputMapper.Action.INTERACT:
|
||||
var target_id: int = interaction_prompt.get_interaction_target()
|
||||
if target_id >= 0:
|
||||
input["action_data"] = {
|
||||
"target_entity_id": target_id,
|
||||
"verb": interaction_prompt.get_selected_verb(),
|
||||
}
|
||||
SimBridge.send_input(input)
|
||||
|
||||
@@ -63,7 +63,7 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_facing is String:
|
||||
player_facing = raw_facing
|
||||
|
||||
# visible_tiles: Array of {x, y, z, visibility}
|
||||
# visible_tiles: Array of {x, y, z, visibility, tile_kind}
|
||||
var visible_tiles: Array = []
|
||||
var raw_vtiles: Variant = raw.get("visible_tiles")
|
||||
if raw_vtiles is Array:
|
||||
@@ -77,6 +77,12 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
var vis: Variant = raw_tile.get("visibility")
|
||||
if vis is String:
|
||||
tile_entry["visibility"] = vis
|
||||
# tile_kind: Floor/Wall/Door/Object — map to client tile type strings
|
||||
var kind: Variant = raw_tile.get("tile_kind")
|
||||
if kind is String:
|
||||
tile_entry["type"] = kind.to_lower()
|
||||
else:
|
||||
tile_entry["type"] = "floor"
|
||||
visible_tiles.append(tile_entry)
|
||||
|
||||
# v4: nearby_interactions (#404/#405)
|
||||
|
||||
@@ -71,11 +71,12 @@ func _update_entity_node(entity_id: int, entity_data: Dictionary) -> void:
|
||||
|
||||
var entity_node = entity_nodes[entity_id]
|
||||
|
||||
# Update position from x, y fields (Protocol format), centered within tile
|
||||
# Update position from x, y fields (Protocol format), centered within tile.
|
||||
# Server sends tile-center coords (tile 16 → 16.5), floor to get tile index.
|
||||
if entity_data.has("x") and entity_data.has("y"):
|
||||
entity_node.position = Vector2(
|
||||
entity_data.x * TILE_SIZE + ENTITY_OFFSET,
|
||||
entity_data.y * TILE_SIZE + ENTITY_OFFSET
|
||||
floorf(entity_data.x) * TILE_SIZE + ENTITY_OFFSET,
|
||||
floorf(entity_data.y) * TILE_SIZE + ENTITY_OFFSET
|
||||
)
|
||||
|
||||
# v2: Peripheral vision dimming (D-015)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,198 @@
|
||||
## #411: Integration test — full input roundtrip through live server.
|
||||
## Validates the complete path: encode inputs → TCP wire → server deserialize →
|
||||
## simulation systems → ObserverSnapshot → client decode.
|
||||
## Catches integration seams that unit tests miss by exercising both movement
|
||||
## and interaction actions against the real server binary.
|
||||
## Requires: server binary built (cargo build in server/)
|
||||
class_name TestInputRoundtrip
|
||||
extends GdUnitTestSuite
|
||||
|
||||
const CONNECT_TIMEOUT: float = 3.0
|
||||
const RESPONSE_TIMEOUT: float = 5.0
|
||||
const MAX_PORT_ATTEMPTS: int = 5
|
||||
|
||||
var _server_pid: int = -1
|
||||
var _bridge: LocalBridge = null
|
||||
var _test_port: int = 0
|
||||
|
||||
|
||||
func _server_binary_path() -> String:
|
||||
var project_dir := ProjectSettings.globalize_path("res://")
|
||||
return project_dir.path_join("../server/target/debug/settled-reach-server")
|
||||
|
||||
|
||||
static func _random_test_port() -> int:
|
||||
return 49152 + (randi() % (65535 - 49152 + 1))
|
||||
|
||||
|
||||
func _spawn_server(server_path: String) -> bool:
|
||||
for attempt in range(MAX_PORT_ATTEMPTS):
|
||||
_test_port = _random_test_port()
|
||||
var addr := "127.0.0.1:%d" % _test_port
|
||||
_server_pid = OS.create_process(server_path, [addr])
|
||||
if _server_pid <= 0:
|
||||
continue
|
||||
await get_tree().create_timer(0.15).timeout
|
||||
if OS.is_process_running(_server_pid):
|
||||
return true
|
||||
_server_pid = -1
|
||||
return false
|
||||
|
||||
|
||||
func after_test() -> void:
|
||||
if _bridge != null:
|
||||
_bridge.disconnect_from_server()
|
||||
_bridge = null
|
||||
if _server_pid > 0 and OS.is_process_running(_server_pid):
|
||||
OS.kill(_server_pid)
|
||||
_server_pid = -1
|
||||
|
||||
|
||||
func _connect_to_server() -> bool:
|
||||
var server_path := _server_binary_path()
|
||||
if not FileAccess.file_exists(server_path):
|
||||
push_warning("Input roundtrip test skipped: server binary not found at %s" % server_path)
|
||||
return false
|
||||
|
||||
var spawned := await _spawn_server(server_path)
|
||||
if not spawned:
|
||||
return false
|
||||
|
||||
_bridge = LocalBridge.new()
|
||||
var elapsed := 0.0
|
||||
while elapsed < CONNECT_TIMEOUT:
|
||||
if _server_pid > 0 and not OS.is_process_running(_server_pid):
|
||||
push_warning("Server process died during connection")
|
||||
return false
|
||||
if _bridge.get_status() == StreamPeerTCP.STATUS_NONE:
|
||||
_bridge.connect_to_server("127.0.0.1", _test_port)
|
||||
_bridge.poll()
|
||||
if _bridge.get_status() == StreamPeerTCP.STATUS_CONNECTED:
|
||||
return true
|
||||
if _bridge.get_status() == StreamPeerTCP.STATUS_ERROR:
|
||||
_bridge.disconnect_from_server()
|
||||
_bridge.reset()
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
elapsed += 0.1
|
||||
|
||||
return false
|
||||
|
||||
|
||||
## Send a batch input and receive the snapshot response.
|
||||
func _send_and_receive(action_name: String, tick: int = 0, action_data: Variant = null) -> Variant:
|
||||
var input_entry := {"tick": tick, "action_name": action_name}
|
||||
if action_data != null:
|
||||
input_entry["action_data"] = action_data
|
||||
var inputs: Array = [input_entry]
|
||||
var encoded := Protocol.encode_player_inputs(inputs)
|
||||
assert_that(encoded.size()).is_greater(0)
|
||||
var send_err := _bridge.send_message(encoded)
|
||||
assert_that(send_err).is_equal(OK)
|
||||
|
||||
var snapshot_bytes := PackedByteArray()
|
||||
var elapsed := 0.0
|
||||
while elapsed < RESPONSE_TIMEOUT:
|
||||
_bridge.poll()
|
||||
snapshot_bytes = _bridge.poll_message()
|
||||
if snapshot_bytes.size() > 0:
|
||||
break
|
||||
await get_tree().create_timer(0.05).timeout
|
||||
elapsed += 0.05
|
||||
|
||||
assert_that(snapshot_bytes.size()).is_greater(0)
|
||||
var snapshot: Variant = Protocol.decode_snapshot(snapshot_bytes)
|
||||
assert_that(snapshot).is_not_null()
|
||||
return snapshot
|
||||
|
||||
|
||||
## Find the player entity in a snapshot by kind.
|
||||
static func _find_player(snapshot: Dictionary) -> Dictionary:
|
||||
for entity in snapshot.entities:
|
||||
if entity.kind.variant == "Player":
|
||||
return entity
|
||||
return {}
|
||||
|
||||
|
||||
# -- Movement roundtrip: send movement, verify position changes ----------------
|
||||
|
||||
func test_movement_roundtrip() -> void:
|
||||
var ok := await _connect_to_server()
|
||||
if not ok:
|
||||
return
|
||||
|
||||
# Use Interact (server no-op) to get a baseline snapshot without moving.
|
||||
var baseline: Dictionary = await _send_and_receive("Interact", 0)
|
||||
var player := _find_player(baseline)
|
||||
assert_that(player.size()).is_greater(0)
|
||||
|
||||
# Player starts at (16,16) → render coords (16.5, 16.5)
|
||||
var start_x: float = player.x
|
||||
var start_y: float = player.y
|
||||
assert_float(start_x).is_equal_approx(16.5, 0.001)
|
||||
assert_float(start_y).is_equal_approx(16.5, 0.001)
|
||||
|
||||
# Send MoveNorth — player should move to (16, 15) → (16.5, 15.5)
|
||||
var snap1: Dictionary = await _send_and_receive("MoveNorth", 1)
|
||||
var p1 := _find_player(snap1)
|
||||
assert_that(p1.size()).is_greater(0)
|
||||
assert_float(p1.x).is_equal_approx(16.5, 0.001)
|
||||
assert_float(p1.y).is_equal_approx(15.5, 0.001)
|
||||
|
||||
# Send MoveEast — player should move to (17, 15) → (17.5, 15.5)
|
||||
var snap2: Dictionary = await _send_and_receive("MoveEast", 2)
|
||||
var p2 := _find_player(snap2)
|
||||
assert_that(p2.size()).is_greater(0)
|
||||
assert_float(p2.x).is_equal_approx(17.5, 0.001)
|
||||
assert_float(p2.y).is_equal_approx(15.5, 0.001)
|
||||
|
||||
# Verify position actually changed from baseline
|
||||
assert_that(p2.x != start_x or p2.y != start_y).is_true()
|
||||
|
||||
|
||||
# -- Interact roundtrip: server accepts without crashing -----------------------
|
||||
|
||||
func test_interact_roundtrip() -> void:
|
||||
var ok := await _connect_to_server()
|
||||
if not ok:
|
||||
return
|
||||
|
||||
# Send Interact action — unit variant, no target data.
|
||||
# Server accepts it (currently a no-op) and responds with a valid snapshot.
|
||||
var snapshot: Dictionary = await _send_and_receive("Interact", 0)
|
||||
|
||||
# Snapshot should be valid with correct protocol version
|
||||
assert_that(snapshot.version).is_equal(Protocol.PROTOCOL_VERSION)
|
||||
assert_that(snapshot.entities.size()).is_greater(0)
|
||||
|
||||
# Player should be at start position (Interact doesn't move)
|
||||
var player := _find_player(snapshot)
|
||||
assert_that(player.size()).is_greater(0)
|
||||
assert_float(player.x).is_equal_approx(16.5, 0.001)
|
||||
assert_float(player.y).is_equal_approx(16.5, 0.001)
|
||||
|
||||
# Send Interact again at next tick — server should still accept it
|
||||
var snap2: Dictionary = await _send_and_receive("Interact", 1)
|
||||
assert_that(snap2).is_not_null()
|
||||
assert_that(snap2.version).is_equal(Protocol.PROTOCOL_VERSION)
|
||||
|
||||
|
||||
# -- Mixed sequence: movement then interact in one session ---------------------
|
||||
|
||||
func test_move_then_interact() -> void:
|
||||
var ok := await _connect_to_server()
|
||||
if not ok:
|
||||
return
|
||||
|
||||
# Move player first
|
||||
var snap1: Dictionary = await _send_and_receive("MoveNorth", 0)
|
||||
var p1 := _find_player(snap1)
|
||||
assert_that(p1.size()).is_greater(0)
|
||||
var moved_x: float = p1.x
|
||||
var moved_y: float = p1.y
|
||||
|
||||
# Now send Interact — player should stay where they moved to
|
||||
var snap2: Dictionary = await _send_and_receive("Interact", 1)
|
||||
var p2 := _find_player(snap2)
|
||||
assert_that(p2.size()).is_greater(0)
|
||||
assert_float(p2.x).is_equal_approx(moved_x, 0.001)
|
||||
assert_float(p2.y).is_equal_approx(moved_y, 0.001)
|
||||
@@ -7,13 +7,12 @@ extends GdUnitTestSuite
|
||||
var _valid_snapshot: Dictionary = {
|
||||
"tick": 1,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 10.0, "y": 15.0, "z": 0, "kind": {"variant": "Npc", "data": null}},
|
||||
{"entity_id": 1, "x": 10.0, "y": 15.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
{"entity_id": 2, "x": 5.0, "y": 20.0, "z": 0, "kind": {"variant": "Npc", "data": null}},
|
||||
],
|
||||
}
|
||||
|
||||
func test_apply_valid_snapshot() -> void:
|
||||
GameState.player_entity_id = 1
|
||||
GameState.apply_snapshot(_valid_snapshot)
|
||||
|
||||
assert_that(GameState.current_tick).is_equal(1)
|
||||
@@ -32,17 +31,22 @@ func test_empty_snapshot_no_crash() -> void:
|
||||
assert_that(GameState.visible_entities.size()).is_equal(0)
|
||||
|
||||
func test_no_player_entity_position_unchanged() -> void:
|
||||
GameState.player_entity_id = 999 # No entity with this ID
|
||||
GameState.player_position = Vector2(5, 5)
|
||||
|
||||
GameState.apply_snapshot(_valid_snapshot)
|
||||
# Snapshot with only NPCs — no Player entity, so position should not change
|
||||
var npc_only_snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 10.0, "y": 15.0, "z": 0, "kind": {"variant": "Npc", "data": null}},
|
||||
],
|
||||
}
|
||||
GameState.apply_snapshot(npc_only_snapshot)
|
||||
|
||||
# Position stays at previous value since no matching entity
|
||||
# Position stays at previous value since no Player entity found
|
||||
assert_that(GameState.player_position).is_equal(Vector2(5, 5))
|
||||
|
||||
func test_missing_fields_partial_update() -> void:
|
||||
# First apply valid snapshot
|
||||
GameState.player_entity_id = 1
|
||||
# First apply valid snapshot (contains Player entity at 10,15)
|
||||
GameState.apply_snapshot(_valid_snapshot)
|
||||
assert_that(GameState.player_position).is_equal(Vector2(10, 15))
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@ pub fn receive_bridge_inputs(
|
||||
let Some(bridge) = bridge else { return };
|
||||
match bridge.receive_inputs() {
|
||||
Ok(inputs) => {
|
||||
for input in &inputs {
|
||||
tracing::debug!("Received input: tick={} action={:?}", input.tick, input.action);
|
||||
}
|
||||
for input in inputs {
|
||||
input_queue.push(input);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ pub enum FacingDirection {
|
||||
Northwest,
|
||||
}
|
||||
|
||||
/// A tile visible to the observer with its visibility quality
|
||||
/// A tile visible to the observer with its visibility quality and type
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VisibleTile {
|
||||
pub x: i32,
|
||||
@@ -81,6 +81,20 @@ pub struct VisibleTile {
|
||||
pub z: i32,
|
||||
/// Which vision cone sector this tile falls in (D-015)
|
||||
pub visibility: VisibilitySector,
|
||||
/// Tile type for client rendering (floor, wall, door, object)
|
||||
#[serde(default)]
|
||||
pub tile_kind: TileKind,
|
||||
}
|
||||
|
||||
/// Tile type for rendering. Derived from WalkabilityMap on the server side.
|
||||
/// v0.1: Floor (walkable) and Wall (blocked). Door and Object for future use.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
|
||||
pub enum TileKind {
|
||||
#[default]
|
||||
Floor,
|
||||
Wall,
|
||||
Door,
|
||||
Object,
|
||||
}
|
||||
|
||||
/// Vision cone sectors per D-015.
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::collections::{HashMap, HashSet};
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::bridge::types::{FacingDirection, VisibilitySector, VisibleTile};
|
||||
use crate::bridge::types::{FacingDirection, TileKind, VisibilitySector, VisibleTile};
|
||||
use crate::perception::shadowcast::compute_fov;
|
||||
use crate::perception::vision_cone::{apply_vision_cone, VisionConeConfig};
|
||||
use crate::simulation::movement::{TilePosition, WalkabilityMap};
|
||||
@@ -67,11 +67,19 @@ impl PerceptionQuery for NaturalVision {
|
||||
|
||||
let visible_tiles = cone_tiles
|
||||
.iter()
|
||||
.map(|&(x, y, sector)| VisibleTile {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
visibility: sector,
|
||||
.map(|&(x, y, sector)| {
|
||||
let tile_kind = if walkability.can_move_to(&TilePosition::new(x, y, z)) {
|
||||
TileKind::Floor
|
||||
} else {
|
||||
TileKind::Wall
|
||||
};
|
||||
VisibleTile {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
visibility: sector,
|
||||
tile_kind,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -176,18 +176,21 @@ fn generate_msgpack_fixtures() {
|
||||
y: 10,
|
||||
z: 0,
|
||||
visibility: VisibilitySector::Forward,
|
||||
tile_kind: TileKind::Floor,
|
||||
},
|
||||
VisibleTile {
|
||||
x: 11,
|
||||
y: 10,
|
||||
z: 0,
|
||||
visibility: VisibilitySector::Peripheral,
|
||||
tile_kind: TileKind::Floor,
|
||||
},
|
||||
VisibleTile {
|
||||
x: 10,
|
||||
y: 9,
|
||||
z: 0,
|
||||
visibility: VisibilitySector::Forward,
|
||||
tile_kind: TileKind::Floor,
|
||||
},
|
||||
],
|
||||
nearby_interactions: vec![],
|
||||
|
||||
@@ -205,12 +205,14 @@ fn snapshot_v2_fields_roundtrip() {
|
||||
y: 10,
|
||||
z: 0,
|
||||
visibility: VisibilitySector::Forward,
|
||||
tile_kind: TileKind::Floor,
|
||||
},
|
||||
VisibleTile {
|
||||
x: 6,
|
||||
y: 10,
|
||||
z: 0,
|
||||
visibility: VisibilitySector::Peripheral,
|
||||
tile_kind: TileKind::Wall,
|
||||
},
|
||||
],
|
||||
nearby_interactions: vec![],
|
||||
|
||||
Reference in New Issue
Block a user