Compare commits
No commits in common. "24b957046365e2be867cb2da4b24ebfd0dafbb1f" and "d944367301c936042c6e59dbdebc864619b9093d" have entirely different histories.
24b9570463
...
d944367301
25 changed files with 123 additions and 969 deletions
|
|
@ -46,14 +46,6 @@ derives trusted context from registered peers, authoritative regions, and
|
||||||
server-owned state before mutating inventory, wallet, progression, or shared
|
server-owned state before mutating inventory, wallet, progression, or shared
|
||||||
world state. See [`decisions/0001-host-authority.md`](decisions/0001-host-authority.md).
|
world state. See [`decisions/0001-host-authority.md`](decisions/0001-host-authority.md).
|
||||||
|
|
||||||
Moderation follows the same boundary. Player hosts may grant session-scoped
|
|
||||||
operator status to an authenticated identity. Dedicated servers derive
|
|
||||||
operators from their configured fingerprint allowlist. Operator status is
|
|
||||||
replicated for presentation, but kick, ban, unban, and artwork-reset requests
|
|
||||||
are always reauthorized against the authenticated sender by the host. Only a
|
|
||||||
player host can grant or revoke operators; operators cannot moderate the host
|
|
||||||
or another operator.
|
|
||||||
|
|
||||||
Protocol compatibility is defined in `network/network_protocol.gd`. A visible
|
Protocol compatibility is defined in `network/network_protocol.gd`. A visible
|
||||||
release version is not a reason to change the protocol number.
|
release version is not a reason to change the protocol number.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,16 @@ const MINNOW_ROLL_WEIGHTS: Array[float] = [34.0, 27.0, 20.0, 13.0, 6.0]
|
||||||
const SARDINE_ROLL_WEIGHTS: Array[float] = [25.0, 28.0, 23.0, 16.0, 8.0]
|
const SARDINE_ROLL_WEIGHTS: Array[float] = [25.0, 28.0, 23.0, 16.0, 8.0]
|
||||||
const LUMINOUS_ROE_ROLL_WEIGHTS: Array[float] = [16.0, 26.0, 26.0, 20.0, 12.0]
|
const LUMINOUS_ROE_ROLL_WEIGHTS: Array[float] = [16.0, 26.0, 26.0, 20.0, 12.0]
|
||||||
const SALE_MULTIPLIERS: Array[float] = [1.0, 1.1, 1.25, 1.5, 2.0]
|
const SALE_MULTIPLIERS: Array[float] = [1.0, 1.1, 1.25, 1.5, 2.0]
|
||||||
# Legacy multiplier helpers remain available for compatibility. New encounters
|
# Legacy profile multiplier retained for serialized/profile compatibility.
|
||||||
# roll directly within the authored quality bands below.
|
# New encounters use the weighted quality/rarity/weight bands below.
|
||||||
const BARRIER_HEALTH_MULTIPLIERS: Array[float] = [1.0, 1.25, 1.6, 2.2, 3.25]
|
const BARRIER_HEALTH_MULTIPLIERS: Array[float] = [1.0, 1.25, 1.6, 2.2, 3.25]
|
||||||
# Inclusive per-barrier health bands authored for each quality tier. Encounter
|
# Barrier health is weighted 70% by quality, 20% by rarity, and 10% by the
|
||||||
# generation rolls uniformly across the selected tier's complete band.
|
# catch's position in its authored weight range. The upper bounds define the
|
||||||
|
# intended per-barrier challenge bands; a small seeded variance keeps barriers
|
||||||
|
# from feeling identical without crossing those bands.
|
||||||
|
const BARRIER_QUALITY_WEIGHT: float = 0.70
|
||||||
|
const BARRIER_RARITY_WEIGHT: float = 0.20
|
||||||
|
const BARRIER_WEIGHT_WEIGHT: float = 0.10
|
||||||
const BARRIER_HEALTH_MINIMUMS: Array[int] = [1, 10, 50, 100, 200]
|
const BARRIER_HEALTH_MINIMUMS: Array[int] = [1, 10, 50, 100, 200]
|
||||||
const BARRIER_HEALTH_MAXIMUMS: Array[int] = [9, 50, 100, 200, 400]
|
const BARRIER_HEALTH_MAXIMUMS: Array[int] = [9, 50, 100, 200, 400]
|
||||||
const DISPLAY_NAMES: PackedStringArray = [
|
const DISPLAY_NAMES: PackedStringArray = [
|
||||||
|
|
@ -84,26 +89,32 @@ static func apply_barrier_health(base_health: int, quality: int) -> int:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
static func barrier_health_range(quality: int) -> Vector2i:
|
static func barrier_health_for_catch(
|
||||||
|
quality: int,
|
||||||
|
rarity: int,
|
||||||
|
weight_percentile: float,
|
||||||
|
variance: float = 1.0,
|
||||||
|
) -> int:
|
||||||
var safe_quality: int = (
|
var safe_quality: int = (
|
||||||
quality if is_valid(quality) else Tier.BORING
|
quality if is_valid(quality) else Tier.BORING
|
||||||
)
|
)
|
||||||
return Vector2i(
|
var safe_rarity: float = clampf(float(rarity) / 4.0, 0.0, 1.0)
|
||||||
|
var safe_weight: float = clampf(weight_percentile, 0.0, 1.0)
|
||||||
|
var weighted_health: float = float(BARRIER_HEALTH_MAXIMUMS[safe_quality]) * (
|
||||||
|
BARRIER_QUALITY_WEIGHT
|
||||||
|
+ BARRIER_RARITY_WEIGHT * safe_rarity
|
||||||
|
+ BARRIER_WEIGHT_WEIGHT * safe_weight
|
||||||
|
)
|
||||||
|
var varied_health: int = roundi(
|
||||||
|
weighted_health * clampf(variance, 0.8, 1.2)
|
||||||
|
)
|
||||||
|
return clampi(
|
||||||
|
varied_health,
|
||||||
BARRIER_HEALTH_MINIMUMS[safe_quality],
|
BARRIER_HEALTH_MINIMUMS[safe_quality],
|
||||||
BARRIER_HEALTH_MAXIMUMS[safe_quality],
|
BARRIER_HEALTH_MAXIMUMS[safe_quality],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
static func roll_barrier_health(
|
|
||||||
rng: RandomNumberGenerator,
|
|
||||||
quality: int,
|
|
||||||
) -> int:
|
|
||||||
var health_range: Vector2i = barrier_health_range(quality)
|
|
||||||
if rng == null:
|
|
||||||
return health_range.x
|
|
||||||
return rng.randi_range(health_range.x, health_range.y)
|
|
||||||
|
|
||||||
|
|
||||||
static func roll(
|
static func roll(
|
||||||
rng: RandomNumberGenerator,
|
rng: RandomNumberGenerator,
|
||||||
weight_multipliers: Array[float] = [],
|
weight_multipliers: Array[float] = [],
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
script = ExtResource("1_profile")
|
script = ExtResource("1_profile")
|
||||||
barrier_count_min = 3
|
barrier_count_min = 3
|
||||||
barrier_count_max = 4
|
barrier_count_max = 4
|
||||||
|
barrier_health_min = 4
|
||||||
|
barrier_health_max = 7
|
||||||
first_barrier_margin = 0.14
|
first_barrier_margin = 0.14
|
||||||
final_barrier_margin = 0.1
|
final_barrier_margin = 0.1
|
||||||
minimum_barrier_spacing = 0.14
|
minimum_barrier_spacing = 0.14
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
script = ExtResource("1_profile")
|
script = ExtResource("1_profile")
|
||||||
barrier_count_min = 3
|
barrier_count_min = 3
|
||||||
barrier_count_max = 4
|
barrier_count_max = 4
|
||||||
|
barrier_health_min = 5
|
||||||
|
barrier_health_max = 8
|
||||||
first_barrier_margin = 0.13
|
first_barrier_margin = 0.13
|
||||||
final_barrier_margin = 0.09
|
final_barrier_margin = 0.09
|
||||||
minimum_barrier_spacing = 0.14
|
minimum_barrier_spacing = 0.14
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ var _reel_input_held: bool = false
|
||||||
var _reel_speed: float = 0.0
|
var _reel_speed: float = 0.0
|
||||||
var _click_power: int = 1
|
var _click_power: int = 1
|
||||||
var _fish_quality: int = FishQualityType.Tier.BORING
|
var _fish_quality: int = FishQualityType.Tier.BORING
|
||||||
|
var _fish_rarity: int = 0
|
||||||
|
var _fish_weight_percentile: float = 0.0
|
||||||
var _chase_delay_remaining: float = 0.0
|
var _chase_delay_remaining: float = 0.0
|
||||||
var _failure_epsilon: float = 0.0001
|
var _failure_epsilon: float = 0.0001
|
||||||
var _auto_click_accumulator: float = 0.0
|
var _auto_click_accumulator: float = 0.0
|
||||||
|
|
@ -111,6 +113,8 @@ func start_encounter(
|
||||||
reel_speed: float,
|
reel_speed: float,
|
||||||
click_power: int,
|
click_power: int,
|
||||||
fish_quality: int = FishQualityType.Tier.BORING,
|
fish_quality: int = FishQualityType.Tier.BORING,
|
||||||
|
fish_rarity: int = 0,
|
||||||
|
fish_weight_percentile: float = 0.0,
|
||||||
) -> void:
|
) -> void:
|
||||||
reset()
|
reset()
|
||||||
if profile == null:
|
if profile == null:
|
||||||
|
|
@ -124,6 +128,8 @@ func start_encounter(
|
||||||
if FishQualityType.is_valid(fish_quality)
|
if FishQualityType.is_valid(fish_quality)
|
||||||
else FishQualityType.Tier.BORING
|
else FishQualityType.Tier.BORING
|
||||||
)
|
)
|
||||||
|
_fish_rarity = clampi(fish_rarity, 0, 4)
|
||||||
|
_fish_weight_percentile = clampf(fish_weight_percentile, 0.0, 1.0)
|
||||||
_chase_delay_remaining = CHASE_START_DELAY
|
_chase_delay_remaining = CHASE_START_DELAY
|
||||||
chase_progress = -CHASE_START_OFFSET
|
chase_progress = -CHASE_START_OFFSET
|
||||||
_seed_encounter_rng()
|
_seed_encounter_rng()
|
||||||
|
|
@ -139,6 +145,8 @@ func start_authoritative_encounter(
|
||||||
click_power: int,
|
click_power: int,
|
||||||
encounter_seed_value: int,
|
encounter_seed_value: int,
|
||||||
fish_quality: int = FishQualityType.Tier.BORING,
|
fish_quality: int = FishQualityType.Tier.BORING,
|
||||||
|
fish_rarity: int = 0,
|
||||||
|
fish_weight_percentile: float = 0.0,
|
||||||
) -> void:
|
) -> void:
|
||||||
var previous_test_mode: bool = use_deterministic_test_seed
|
var previous_test_mode: bool = use_deterministic_test_seed
|
||||||
var previous_seed: int = deterministic_test_seed
|
var previous_seed: int = deterministic_test_seed
|
||||||
|
|
@ -149,6 +157,8 @@ func start_authoritative_encounter(
|
||||||
reel_speed,
|
reel_speed,
|
||||||
click_power,
|
click_power,
|
||||||
fish_quality,
|
fish_quality,
|
||||||
|
fish_rarity,
|
||||||
|
fish_weight_percentile,
|
||||||
)
|
)
|
||||||
use_deterministic_test_seed = previous_test_mode
|
use_deterministic_test_seed = previous_test_mode
|
||||||
deterministic_test_seed = previous_seed
|
deterministic_test_seed = previous_seed
|
||||||
|
|
@ -317,8 +327,11 @@ func _generate_barriers(profile: CatchDifficultyProfileType) -> void:
|
||||||
+ spacing * float(barrier_index)
|
+ spacing * float(barrier_index)
|
||||||
+ random_offsets[barrier_index] * random_slack
|
+ random_offsets[barrier_index] * random_slack
|
||||||
)
|
)
|
||||||
var health: int = FishQualityType.roll_barrier_health(
|
var health: int = FishQualityType.barrier_health_for_catch(
|
||||||
_rng, _fish_quality
|
_fish_quality,
|
||||||
|
_fish_rarity,
|
||||||
|
_fish_weight_percentile,
|
||||||
|
_rng.randf_range(0.9, 1.1),
|
||||||
)
|
)
|
||||||
_barriers.append(Barrier.new(position, health))
|
_barriers.append(Barrier.new(position, health))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ extends Resource
|
||||||
|
|
||||||
@export_range(0, 12, 1) var barrier_count_min: int = 2
|
@export_range(0, 12, 1) var barrier_count_min: int = 2
|
||||||
@export_range(0, 12, 1) var barrier_count_max: int = 4
|
@export_range(0, 12, 1) var barrier_count_max: int = 4
|
||||||
|
@export_range(1, 100, 1) var barrier_health_min: int = 3
|
||||||
|
@export_range(1, 100, 1) var barrier_health_max: int = 7
|
||||||
@export_range(0.0, 0.45, 0.01) var first_barrier_margin: float = 0.15
|
@export_range(0.0, 0.45, 0.01) var first_barrier_margin: float = 0.15
|
||||||
@export_range(0.0, 0.45, 0.01) var final_barrier_margin: float = 0.10
|
@export_range(0.0, 0.45, 0.01) var final_barrier_margin: float = 0.10
|
||||||
@export_range(0.01, 1.0, 0.01) var minimum_barrier_spacing: float = 0.15
|
@export_range(0.01, 1.0, 0.01) var minimum_barrier_spacing: float = 0.15
|
||||||
|
|
@ -13,6 +15,11 @@ func get_barrier_count_range() -> Vector2i:
|
||||||
return Vector2i(minimum, maxi(minimum, barrier_count_max))
|
return Vector2i(minimum, maxi(minimum, barrier_count_max))
|
||||||
|
|
||||||
|
|
||||||
|
func get_barrier_health_range() -> Vector2i:
|
||||||
|
var minimum: int = maxi(1, barrier_health_min)
|
||||||
|
return Vector2i(minimum, maxi(minimum, barrier_health_max))
|
||||||
|
|
||||||
|
|
||||||
func get_generation_interval() -> Vector2:
|
func get_generation_interval() -> Vector2:
|
||||||
var start: float = clampf(first_barrier_margin, 0.0, 0.95)
|
var start: float = clampf(first_barrier_margin, 0.0, 0.95)
|
||||||
var finish: float = clampf(1.0 - final_barrier_margin, start, 1.0)
|
var finish: float = clampf(1.0 - final_barrier_margin, start, 1.0)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
script = ExtResource("1_profile")
|
script = ExtResource("1_profile")
|
||||||
barrier_count_min = 2
|
barrier_count_min = 2
|
||||||
barrier_count_max = 4
|
barrier_count_max = 4
|
||||||
|
barrier_health_min = 3
|
||||||
|
barrier_health_max = 7
|
||||||
first_barrier_margin = 0.15
|
first_barrier_margin = 0.15
|
||||||
final_barrier_margin = 0.1
|
final_barrier_margin = 0.1
|
||||||
minimum_barrier_spacing = 0.15
|
minimum_barrier_spacing = 0.15
|
||||||
|
|
|
||||||
|
|
@ -1045,6 +1045,8 @@ func _activate_bite(confirmation_override: bool = false) -> void:
|
||||||
_get_effective_reel_speed(),
|
_get_effective_reel_speed(),
|
||||||
_get_effective_barrier_damage(),
|
_get_effective_barrier_damage(),
|
||||||
_pending_catch.quality,
|
_pending_catch.quality,
|
||||||
|
int(_pending_catch.fish.rarity),
|
||||||
|
_pending_catch.fish.get_weight_percentile(_pending_catch.weight_lb),
|
||||||
)
|
)
|
||||||
_catch_controller.set_reel_input(
|
_catch_controller.set_reel_input(
|
||||||
Input.is_action_pressed("fish_primary")
|
Input.is_action_pressed("fish_primary")
|
||||||
|
|
|
||||||
|
|
@ -308,15 +308,7 @@ func _start_dedicated_server() -> void:
|
||||||
if not _discovery.set_base_url_override(config.discovery_url):
|
if not _discovery.set_base_url_override(config.discovery_url):
|
||||||
_fail_dedicated_server("The discovery URL is invalid.")
|
_fail_dedicated_server("The discovery URL is invalid.")
|
||||||
return
|
return
|
||||||
if not _network_session.configure_dedicated_operators(
|
|
||||||
config.operator_fingerprints
|
|
||||||
):
|
|
||||||
_fail_dedicated_server("The server operator list is invalid.")
|
|
||||||
return
|
|
||||||
_configure_portable_stores()
|
_configure_portable_stores()
|
||||||
if not _discovery.configure_dedicated_runtime(config.server_name):
|
|
||||||
_fail_dedicated_server("The server room name is invalid.")
|
|
||||||
return
|
|
||||||
_initialize_application(true)
|
_initialize_application(true)
|
||||||
_player.set_local_control(false)
|
_player.set_local_control(false)
|
||||||
_player.visible = false
|
_player.visible = false
|
||||||
|
|
@ -331,6 +323,7 @@ func _start_dedicated_server() -> void:
|
||||||
_fail_dedicated_server("Could not start the dedicated server.")
|
_fail_dedicated_server("Could not start the dedicated server.")
|
||||||
return
|
return
|
||||||
_network_session.set_session_display_name(config.server_name)
|
_network_session.set_session_display_name(config.server_name)
|
||||||
|
_discovery.set_room_name(config.server_name)
|
||||||
if not _network_session.set_host_open(true):
|
if not _network_session.set_host_open(true):
|
||||||
_fail_dedicated_server("Could not open the dedicated server.")
|
_fail_dedicated_server("Could not open the dedicated server.")
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ var _session: NetworkSession
|
||||||
var _base_url: String = ""
|
var _base_url: String = ""
|
||||||
var _room_name: String = DEFAULT_ROOM_NAME
|
var _room_name: String = DEFAULT_ROOM_NAME
|
||||||
var _room_name_uses_default: bool = true
|
var _room_name_uses_default: bool = true
|
||||||
var _host_settings_persistence_enabled: bool = true
|
|
||||||
var _discoverable: bool = false
|
var _discoverable: bool = false
|
||||||
var _host_status_message: String = ""
|
var _host_status_message: String = ""
|
||||||
var _host_status_is_error: bool = false
|
var _host_status_is_error: bool = false
|
||||||
|
|
@ -219,11 +218,6 @@ func get_public_join_state() -> PublicJoinState:
|
||||||
return _public_join_state
|
return _public_join_state
|
||||||
|
|
||||||
|
|
||||||
func configure_dedicated_runtime(room_name: String) -> bool:
|
|
||||||
_host_settings_persistence_enabled = false
|
|
||||||
return set_room_name(room_name)
|
|
||||||
|
|
||||||
|
|
||||||
func set_room_name(value: String) -> bool:
|
func set_room_name(value: String) -> bool:
|
||||||
var cleaned: String = _sanitize_room_name(value)
|
var cleaned: String = _sanitize_room_name(value)
|
||||||
if cleaned.is_empty():
|
if cleaned.is_empty():
|
||||||
|
|
@ -1053,8 +1047,6 @@ func _load_settings() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _save_settings() -> void:
|
func _save_settings() -> void:
|
||||||
if not _host_settings_persistence_enabled:
|
|
||||||
return
|
|
||||||
var config := ConfigFile.new()
|
var config := ConfigFile.new()
|
||||||
config.set_value("host", "room_name", _room_name)
|
config.set_value("host", "room_name", _room_name)
|
||||||
config.set_value(
|
config.set_value(
|
||||||
|
|
|
||||||
|
|
@ -548,6 +548,8 @@ func _start_bite(attempt: NetworkFishingAttempt) -> void:
|
||||||
attempt.barrier_damage,
|
attempt.barrier_damage,
|
||||||
attempt.encounter_seed,
|
attempt.encounter_seed,
|
||||||
fish_catch.quality,
|
fish_catch.quality,
|
||||||
|
int(fish.rarity),
|
||||||
|
fish.get_weight_percentile(fish_catch.weight_lb),
|
||||||
)
|
)
|
||||||
var data: Dictionary = {
|
var data: Dictionary = {
|
||||||
"attempt_id": attempt.attempt_id,
|
"attempt_id": attempt.attempt_id,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@ var _surface_drawing: NetworkSurfaceDrawingService
|
||||||
var _revision := 0
|
var _revision := 0
|
||||||
var _host_block_pairs: Dictionary[String, bool] = {}
|
var _host_block_pairs: Dictionary[String, bool] = {}
|
||||||
var _peer_fingerprints: Dictionary[int, String] = {}
|
var _peer_fingerprints: Dictionary[int, String] = {}
|
||||||
var _remote_bans: Array[Dictionary] = []
|
|
||||||
var _ban_snapshot_requested: bool = false
|
|
||||||
var _ban_snapshot_loaded: bool = false
|
|
||||||
|
|
||||||
|
|
||||||
func setup(
|
func setup(
|
||||||
|
|
@ -43,7 +40,6 @@ func setup(
|
||||||
func(_count: int, _maximum: int) -> void: _sync_authenticated_peers()
|
func(_count: int, _maximum: int) -> void: _sync_authenticated_peers()
|
||||||
)
|
)
|
||||||
_session.state_changed.connect(_on_session_state_changed)
|
_session.state_changed.connect(_on_session_state_changed)
|
||||||
_session.operator_status_changed.connect(_on_operator_status_changed)
|
|
||||||
_relationships.relationship_changed.connect(_on_relationship_changed)
|
_relationships.relationship_changed.connect(_on_relationship_changed)
|
||||||
_bans.bans_changed.connect(_changed)
|
_bans.bans_changed.connect(_changed)
|
||||||
_chat.set_relationship_store(_relationships)
|
_chat.set_relationship_store(_relationships)
|
||||||
|
|
@ -68,24 +64,13 @@ func get_entries() -> Array[PlayerListEntry]:
|
||||||
entry.compact_fingerprint = NetworkIdentityCrypto.compact_suffix(record.identity_fingerprint)
|
entry.compact_fingerprint = NetworkIdentityCrypto.compact_suffix(record.identity_fingerprint)
|
||||||
entry.display_name = record.display_name
|
entry.display_name = record.display_name
|
||||||
entry.is_host = peer_id == 1
|
entry.is_host = peer_id == 1
|
||||||
entry.is_operator = _session.is_peer_operator(peer_id)
|
|
||||||
entry.is_local_player = peer_id == local_id
|
entry.is_local_player = peer_id == local_id
|
||||||
entry.continuity_state = _known.identity_status(record.identity_fingerprint, record.display_name)
|
entry.continuity_state = _known.identity_status(record.identity_fingerprint, record.display_name)
|
||||||
entry.ping_to_host_ms = _session.get_peer_rtt_ms(peer_id)
|
entry.ping_to_host_ms = _session.get_peer_rtt_ms(peer_id)
|
||||||
entry.muted = _relationships.is_muted(record.identity_fingerprint)
|
entry.muted = _relationships.is_muted(record.identity_fingerprint)
|
||||||
entry.blocked = blocked
|
entry.blocked = blocked
|
||||||
entry.can_kick = (
|
entry.can_kick = _session.is_host() and peer_id != local_id and peer_id != 1
|
||||||
_session.can_local_moderate()
|
|
||||||
and peer_id != local_id
|
|
||||||
and peer_id != 1
|
|
||||||
and (_session.is_host() or not entry.is_operator)
|
|
||||||
)
|
|
||||||
entry.can_ban = entry.can_kick
|
entry.can_ban = entry.can_kick
|
||||||
entry.can_manage_operator = (
|
|
||||||
_session.can_manage_operators()
|
|
||||||
and peer_id != local_id
|
|
||||||
and peer_id != 1
|
|
||||||
)
|
|
||||||
entry.revision = _revision
|
entry.revision = _revision
|
||||||
result.append(entry)
|
result.append(entry)
|
||||||
result.sort_custom(_entry_before)
|
result.sort_custom(_entry_before)
|
||||||
|
|
@ -104,14 +89,6 @@ func is_local_host() -> bool:
|
||||||
return _session.is_host()
|
return _session.is_host()
|
||||||
|
|
||||||
|
|
||||||
func is_local_moderator() -> bool:
|
|
||||||
return _session.can_local_moderate()
|
|
||||||
|
|
||||||
|
|
||||||
func can_manage_operators() -> bool:
|
|
||||||
return _session.can_manage_operators()
|
|
||||||
|
|
||||||
|
|
||||||
func is_open_host() -> bool:
|
func is_open_host() -> bool:
|
||||||
return _session.is_open_host()
|
return _session.is_open_host()
|
||||||
|
|
||||||
|
|
@ -125,12 +102,7 @@ func get_relationships() -> Array[Dictionary]:
|
||||||
|
|
||||||
|
|
||||||
func get_bans() -> Array[Dictionary]:
|
func get_bans() -> Array[Dictionary]:
|
||||||
if _session.is_host():
|
return _bans.get_bans(_session.get_host_identity_fingerprint()) if _session.is_host() else []
|
||||||
return _bans.get_bans(_session.get_host_identity_fingerprint())
|
|
||||||
if not _session.is_local_operator():
|
|
||||||
return []
|
|
||||||
_request_ban_snapshot()
|
|
||||||
return _remote_bans.duplicate(true)
|
|
||||||
|
|
||||||
|
|
||||||
func set_surface_drawing_service(
|
func set_surface_drawing_service(
|
||||||
|
|
@ -159,12 +131,14 @@ func get_session_artwork_counts() -> Vector2i:
|
||||||
|
|
||||||
|
|
||||||
func reset_session_artwork() -> bool:
|
func reset_session_artwork() -> bool:
|
||||||
if _session.is_host():
|
if not _session.is_host() or _surface_drawing == null:
|
||||||
return _reset_session_artwork_on_host()
|
|
||||||
if not _session.is_local_operator():
|
|
||||||
return false
|
return false
|
||||||
request_reset_session_artwork.rpc_id(1)
|
var ok: bool = _surface_drawing.clear_session_artwork()
|
||||||
return true
|
moderation_finished.emit(
|
||||||
|
ok,
|
||||||
|
"Session artwork cleared." if ok else "Session artwork could not be cleared.",
|
||||||
|
)
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
func set_muted(fingerprint: String, display_name: String, value: bool) -> bool:
|
func set_muted(fingerprint: String, display_name: String, value: bool) -> bool:
|
||||||
|
|
@ -180,13 +154,12 @@ func set_blocked(fingerprint: String, display_name: String, value: bool) -> bool
|
||||||
|
|
||||||
|
|
||||||
func kick(peer_id: int, fingerprint: String, revision: int) -> bool:
|
func kick(peer_id: int, fingerprint: String, revision: int) -> bool:
|
||||||
if _session.is_host():
|
if not _valid_moderation_target(peer_id, fingerprint, revision):
|
||||||
return _kick_on_host(peer_id, fingerprint, revision, true)
|
moderation_finished.emit(false, "That player is no longer connected.")
|
||||||
if not _session.is_local_operator():
|
|
||||||
moderation_finished.emit(false, "Only the host or an operator can remove players.")
|
|
||||||
return false
|
return false
|
||||||
request_kick.rpc_id(1, peer_id, fingerprint)
|
var ok := _session.kick_authenticated_peer(peer_id, fingerprint)
|
||||||
return true
|
moderation_finished.emit(ok, "Player removed." if ok else "Player could not be removed.")
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
func ban(
|
func ban(
|
||||||
|
|
@ -195,158 +168,22 @@ func ban(
|
||||||
display_name: String,
|
display_name: String,
|
||||||
revision: int,
|
revision: int,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if _session.is_host():
|
if not _valid_moderation_target(peer_id, fingerprint, revision):
|
||||||
return _ban_on_host(
|
moderation_finished.emit(false, "That player is no longer connected.")
|
||||||
peer_id, fingerprint, display_name, revision, true
|
|
||||||
)
|
|
||||||
if not _session.is_local_operator():
|
|
||||||
moderation_finished.emit(false, "Only the host or an operator can ban players.")
|
|
||||||
return false
|
return false
|
||||||
request_ban.rpc_id(1, peer_id, fingerprint)
|
var host_fingerprint := _session.get_host_identity_fingerprint()
|
||||||
|
if not _bans.ban(host_fingerprint, fingerprint, display_name):
|
||||||
|
moderation_finished.emit(false, "Ban could not be saved.")
|
||||||
|
return false
|
||||||
|
var ok := _session.kick_authenticated_peer(peer_id, fingerprint, true)
|
||||||
|
moderation_finished.emit(ok, "Player banned." if ok else "Ban saved.")
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
func unban(fingerprint: String) -> bool:
|
func unban(fingerprint: String) -> bool:
|
||||||
if _session.is_host():
|
if not _session.is_host():
|
||||||
var ok: bool = _bans.unban(
|
|
||||||
_session.get_host_identity_fingerprint(), fingerprint
|
|
||||||
)
|
|
||||||
moderation_finished.emit(
|
|
||||||
ok, "Player unbanned." if ok else "Ban could not be removed."
|
|
||||||
)
|
|
||||||
return ok
|
|
||||||
if not _session.is_local_operator():
|
|
||||||
moderation_finished.emit(false, "Only the host or an operator can remove bans.")
|
|
||||||
return false
|
return false
|
||||||
request_unban.rpc_id(1, fingerprint)
|
return _bans.unban(_session.get_host_identity_fingerprint(), fingerprint)
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func set_operator(
|
|
||||||
peer_id: int,
|
|
||||||
fingerprint: String,
|
|
||||||
enabled: bool,
|
|
||||||
revision: int,
|
|
||||||
) -> bool:
|
|
||||||
if (
|
|
||||||
not _session.can_manage_operators()
|
|
||||||
or not _valid_moderation_target(
|
|
||||||
peer_id, fingerprint, revision, true, true
|
|
||||||
)
|
|
||||||
):
|
|
||||||
moderation_finished.emit(false, "That player is no longer connected.")
|
|
||||||
return false
|
|
||||||
var ok: bool = _session.set_peer_operator(peer_id, fingerprint, enabled)
|
|
||||||
moderation_finished.emit(
|
|
||||||
ok,
|
|
||||||
("Player is now an operator." if enabled else "Operator access removed.")
|
|
||||||
if ok
|
|
||||||
else "Operator access could not be changed.",
|
|
||||||
)
|
|
||||||
return ok
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
|
||||||
func request_kick(peer_id: int, fingerprint: String) -> void:
|
|
||||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
|
||||||
if not _valid_operator_sender(sender_id):
|
|
||||||
return
|
|
||||||
var ok: bool = _kick_on_host(peer_id, fingerprint, -1, false)
|
|
||||||
_send_moderation_result(
|
|
||||||
sender_id, ok, "Player removed." if ok else "Player could not be removed."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
|
||||||
func request_ban(peer_id: int, fingerprint: String) -> void:
|
|
||||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
|
||||||
if not _valid_operator_sender(sender_id):
|
|
||||||
return
|
|
||||||
var record: PeerRegistry.PeerRecord = _session.get_peer_record(peer_id)
|
|
||||||
var display_name: String = record.display_name if record != null else "Player"
|
|
||||||
var ok: bool = _ban_on_host(
|
|
||||||
peer_id, fingerprint, display_name, -1, false
|
|
||||||
)
|
|
||||||
_send_moderation_result(
|
|
||||||
sender_id, ok, "Player banned." if ok else "Player could not be banned."
|
|
||||||
)
|
|
||||||
_send_ban_snapshot(sender_id)
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
|
||||||
func request_unban(fingerprint: String) -> void:
|
|
||||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
|
||||||
if (
|
|
||||||
not _valid_operator_sender(sender_id)
|
|
||||||
or not NetworkIdentityCrypto.valid_fingerprint(fingerprint)
|
|
||||||
):
|
|
||||||
return
|
|
||||||
var ok: bool = _bans.unban(
|
|
||||||
_session.get_host_identity_fingerprint(), fingerprint
|
|
||||||
)
|
|
||||||
_send_moderation_result(
|
|
||||||
sender_id, ok, "Player unbanned." if ok else "Ban could not be removed."
|
|
||||||
)
|
|
||||||
_send_ban_snapshot(sender_id)
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
|
||||||
func request_reset_session_artwork() -> void:
|
|
||||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
|
||||||
if not _valid_operator_sender(sender_id):
|
|
||||||
return
|
|
||||||
var ok: bool = _reset_session_artwork_on_host(false)
|
|
||||||
_send_moderation_result(
|
|
||||||
sender_id,
|
|
||||||
ok,
|
|
||||||
"Session artwork cleared."
|
|
||||||
if ok
|
|
||||||
else "Session artwork could not be cleared.",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
|
||||||
func request_ban_snapshot() -> void:
|
|
||||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
|
||||||
if _valid_operator_sender(sender_id):
|
|
||||||
_send_ban_snapshot(sender_id)
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("authority", "call_remote", "reliable", 0)
|
|
||||||
func receive_moderation_result(success: bool, message: String) -> void:
|
|
||||||
if not _session.is_joined_client():
|
|
||||||
return
|
|
||||||
moderation_finished.emit(success, message.left(120))
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("authority", "call_remote", "reliable", 0)
|
|
||||||
func receive_ban_snapshot(records: Array) -> void:
|
|
||||||
if not _session.is_local_operator() or records.size() > HostBanStore.MAX_BANS:
|
|
||||||
return
|
|
||||||
var sanitized: Array[Dictionary] = []
|
|
||||||
for value: Variant in records:
|
|
||||||
if typeof(value) != TYPE_DICTIONARY:
|
|
||||||
return
|
|
||||||
var record: Dictionary = value
|
|
||||||
var fingerprint: String = str(record.get("target_fingerprint", ""))
|
|
||||||
var display_name: String = str(
|
|
||||||
record.get("last_known_display_name", "Player")
|
|
||||||
).strip_edges().left(NetworkProtocol.MAX_DISPLAY_NAME_LENGTH)
|
|
||||||
if (
|
|
||||||
not NetworkIdentityCrypto.valid_fingerprint(fingerprint)
|
|
||||||
or display_name.is_empty()
|
|
||||||
or typeof(record.get("banned_unix")) != TYPE_INT
|
|
||||||
):
|
|
||||||
return
|
|
||||||
sanitized.append({
|
|
||||||
"target_fingerprint": fingerprint,
|
|
||||||
"last_known_display_name": display_name,
|
|
||||||
"banned_unix": int(record["banned_unix"]),
|
|
||||||
})
|
|
||||||
_remote_bans = sanitized
|
|
||||||
_ban_snapshot_requested = false
|
|
||||||
_ban_snapshot_loaded = true
|
|
||||||
_changed()
|
|
||||||
|
|
||||||
|
|
||||||
func is_locally_blocked(fingerprint: String) -> bool:
|
func is_locally_blocked(fingerprint: String) -> bool:
|
||||||
|
|
@ -434,22 +271,6 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||||
]:
|
]:
|
||||||
_host_block_pairs.clear()
|
_host_block_pairs.clear()
|
||||||
_peer_fingerprints.clear()
|
_peer_fingerprints.clear()
|
||||||
_remote_bans.clear()
|
|
||||||
_ban_snapshot_requested = false
|
|
||||||
_ban_snapshot_loaded = false
|
|
||||||
elif state == NetworkSession.State.JOINED_CLIENT:
|
|
||||||
_request_ban_snapshot()
|
|
||||||
_changed()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_operator_status_changed(peer_id: int, enabled: bool) -> void:
|
|
||||||
if peer_id == _session.get_local_peer_id():
|
|
||||||
if enabled:
|
|
||||||
_request_ban_snapshot()
|
|
||||||
else:
|
|
||||||
_remote_bans.clear()
|
|
||||||
_ban_snapshot_requested = false
|
|
||||||
_ban_snapshot_loaded = false
|
|
||||||
_changed()
|
_changed()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -474,137 +295,16 @@ func _peer_for_fingerprint(fingerprint: String) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
func _valid_moderation_target(
|
func _valid_moderation_target(peer_id: int, fingerprint: String, revision: int) -> bool:
|
||||||
peer_id: int,
|
if not _session.is_host() or revision != _revision or peer_id == 1:
|
||||||
fingerprint: String,
|
|
||||||
revision: int,
|
|
||||||
require_revision: bool,
|
|
||||||
can_target_operator: bool,
|
|
||||||
) -> bool:
|
|
||||||
if (
|
|
||||||
not _session.is_host()
|
|
||||||
or (require_revision and revision != _revision)
|
|
||||||
or peer_id == 1
|
|
||||||
or (not can_target_operator and _session.is_peer_operator(peer_id))
|
|
||||||
):
|
|
||||||
return false
|
return false
|
||||||
var record := _session.get_peer_record(peer_id)
|
var record := _session.get_peer_record(peer_id)
|
||||||
return record != null and record.identity_fingerprint == fingerprint
|
return record != null and record.identity_fingerprint == fingerprint
|
||||||
|
|
||||||
|
|
||||||
func _valid_operator_sender(peer_id: int) -> bool:
|
|
||||||
return (
|
|
||||||
_session.is_host()
|
|
||||||
and peer_id > 1
|
|
||||||
and _session.is_authenticated_peer(peer_id)
|
|
||||||
and _session.is_peer_operator(peer_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _kick_on_host(
|
|
||||||
peer_id: int,
|
|
||||||
fingerprint: String,
|
|
||||||
revision: int,
|
|
||||||
local_host_request: bool,
|
|
||||||
) -> bool:
|
|
||||||
if not _valid_moderation_target(
|
|
||||||
peer_id,
|
|
||||||
fingerprint,
|
|
||||||
revision,
|
|
||||||
local_host_request,
|
|
||||||
local_host_request,
|
|
||||||
):
|
|
||||||
if local_host_request:
|
|
||||||
moderation_finished.emit(false, "That player is no longer connected.")
|
|
||||||
return false
|
|
||||||
var ok: bool = _session.kick_authenticated_peer(peer_id, fingerprint)
|
|
||||||
if local_host_request:
|
|
||||||
moderation_finished.emit(
|
|
||||||
ok, "Player removed." if ok else "Player could not be removed."
|
|
||||||
)
|
|
||||||
return ok
|
|
||||||
|
|
||||||
|
|
||||||
func _ban_on_host(
|
|
||||||
peer_id: int,
|
|
||||||
fingerprint: String,
|
|
||||||
display_name: String,
|
|
||||||
revision: int,
|
|
||||||
local_host_request: bool,
|
|
||||||
) -> bool:
|
|
||||||
if not _valid_moderation_target(
|
|
||||||
peer_id,
|
|
||||||
fingerprint,
|
|
||||||
revision,
|
|
||||||
local_host_request,
|
|
||||||
local_host_request,
|
|
||||||
):
|
|
||||||
if local_host_request:
|
|
||||||
moderation_finished.emit(false, "That player is no longer connected.")
|
|
||||||
return false
|
|
||||||
var record: PeerRegistry.PeerRecord = _session.get_peer_record(peer_id)
|
|
||||||
var trusted_display_name: String = (
|
|
||||||
record.display_name if record != null else display_name
|
|
||||||
)
|
|
||||||
var host_fingerprint: String = _session.get_host_identity_fingerprint()
|
|
||||||
if not _bans.ban(host_fingerprint, fingerprint, trusted_display_name):
|
|
||||||
if local_host_request:
|
|
||||||
moderation_finished.emit(false, "Ban could not be saved.")
|
|
||||||
return false
|
|
||||||
var ok: bool = _session.kick_authenticated_peer(peer_id, fingerprint, true)
|
|
||||||
if local_host_request:
|
|
||||||
moderation_finished.emit(ok, "Player banned." if ok else "Ban saved.")
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func _reset_session_artwork_on_host(
|
|
||||||
emit_local_result: bool = true,
|
|
||||||
) -> bool:
|
|
||||||
if not _session.is_host() or _surface_drawing == null:
|
|
||||||
return false
|
|
||||||
var ok: bool = _surface_drawing.clear_session_artwork()
|
|
||||||
if emit_local_result:
|
|
||||||
moderation_finished.emit(
|
|
||||||
ok,
|
|
||||||
"Session artwork cleared."
|
|
||||||
if ok
|
|
||||||
else "Session artwork could not be cleared.",
|
|
||||||
)
|
|
||||||
return ok
|
|
||||||
|
|
||||||
|
|
||||||
func _send_moderation_result(
|
|
||||||
peer_id: int,
|
|
||||||
success: bool,
|
|
||||||
message: String,
|
|
||||||
) -> void:
|
|
||||||
receive_moderation_result.rpc_id(peer_id, success, message.left(120))
|
|
||||||
|
|
||||||
|
|
||||||
func _send_ban_snapshot(peer_id: int) -> void:
|
|
||||||
if not _valid_operator_sender(peer_id):
|
|
||||||
return
|
|
||||||
receive_ban_snapshot.rpc_id(
|
|
||||||
peer_id,
|
|
||||||
_bans.get_bans(_session.get_host_identity_fingerprint()),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _request_ban_snapshot() -> void:
|
|
||||||
if (
|
|
||||||
_session.is_local_operator()
|
|
||||||
and not _ban_snapshot_requested
|
|
||||||
and not _ban_snapshot_loaded
|
|
||||||
):
|
|
||||||
_ban_snapshot_requested = true
|
|
||||||
request_ban_snapshot.rpc_id(1)
|
|
||||||
|
|
||||||
|
|
||||||
func _entry_before(a: PlayerListEntry, b: PlayerListEntry) -> bool:
|
func _entry_before(a: PlayerListEntry, b: PlayerListEntry) -> bool:
|
||||||
if a.is_host != b.is_host:
|
if a.is_host != b.is_host:
|
||||||
return a.is_host
|
return a.is_host
|
||||||
if a.is_operator != b.is_operator:
|
|
||||||
return a.is_operator
|
|
||||||
if a.is_local_player != b.is_local_player:
|
if a.is_local_player != b.is_local_player:
|
||||||
return a.is_local_player
|
return a.is_local_player
|
||||||
var compared := a.display_name.naturalnocasecmp_to(b.display_name)
|
var compared := a.display_name.naturalnocasecmp_to(b.display_name)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ signal server_trust_required(
|
||||||
is_changed: bool,
|
is_changed: bool,
|
||||||
)
|
)
|
||||||
signal peer_identity_observed(peer_id: int, status: String)
|
signal peer_identity_observed(peer_id: int, status: String)
|
||||||
signal operator_status_changed(peer_id: int, is_operator: bool)
|
|
||||||
signal server_lost
|
signal server_lost
|
||||||
signal remote_recovery_requested(peer_id: int, entry_position: Vector3)
|
signal remote_recovery_requested(peer_id: int, entry_position: Vector3)
|
||||||
signal remote_recovery_presentation_changed(
|
signal remote_recovery_presentation_changed(
|
||||||
|
|
@ -103,10 +102,6 @@ var _moderation_disconnect_message := ""
|
||||||
var _host_port: int = 0
|
var _host_port: int = 0
|
||||||
var _session_display_name: String = "NETfishing Room"
|
var _session_display_name: String = "NETfishing Room"
|
||||||
var _dedicated_host: bool = false
|
var _dedicated_host: bool = false
|
||||||
var _configured_operator_fingerprints: Dictionary[String, bool] = {}
|
|
||||||
var _session_operator_fingerprints: Dictionary[String, bool] = {}
|
|
||||||
var _operator_peer_ids: Dictionary[int, bool] = {}
|
|
||||||
var _local_operator: bool = false
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -142,20 +137,6 @@ func setup(
|
||||||
_profile_ready = _profile_ready and _player_identity.load_or_create()
|
_profile_ready = _profile_ready and _player_identity.load_or_create()
|
||||||
|
|
||||||
|
|
||||||
func configure_dedicated_operators(
|
|
||||||
fingerprints: PackedStringArray,
|
|
||||||
) -> bool:
|
|
||||||
if state != State.INACTIVE:
|
|
||||||
return false
|
|
||||||
var configured: Dictionary[String, bool] = {}
|
|
||||||
for fingerprint: String in fingerprints:
|
|
||||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
|
||||||
return false
|
|
||||||
configured[fingerprint] = true
|
|
||||||
_configured_operator_fingerprints = configured
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func start_private_host(
|
func start_private_host(
|
||||||
port: int = DEFAULT_PORT,
|
port: int = DEFAULT_PORT,
|
||||||
port_attempts: int = 1,
|
port_attempts: int = 1,
|
||||||
|
|
@ -234,9 +215,6 @@ func _start_host(
|
||||||
multiplayer.multiplayer_peer = peer
|
multiplayer.multiplayer_peer = peer
|
||||||
_session_id = Crypto.new().generate_random_bytes(16).hex_encode()
|
_session_id = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||||
_registry.clear()
|
_registry.clear()
|
||||||
_session_operator_fingerprints.clear()
|
|
||||||
_operator_peer_ids.clear()
|
|
||||||
_local_operator = false
|
|
||||||
_spawn_service.clear_remote_players()
|
_spawn_service.clear_remote_players()
|
||||||
if not dedicated:
|
if not dedicated:
|
||||||
_register_player_host()
|
_register_player_host()
|
||||||
|
|
@ -323,44 +301,6 @@ func is_dedicated_host() -> bool:
|
||||||
return is_host() and _dedicated_host
|
return is_host() and _dedicated_host
|
||||||
|
|
||||||
|
|
||||||
func is_local_operator() -> bool:
|
|
||||||
return state == State.JOINED_CLIENT and _local_operator
|
|
||||||
|
|
||||||
|
|
||||||
func can_local_moderate() -> bool:
|
|
||||||
return is_host() or is_local_operator()
|
|
||||||
|
|
||||||
|
|
||||||
func can_manage_operators() -> bool:
|
|
||||||
return is_host() and not _dedicated_host
|
|
||||||
|
|
||||||
|
|
||||||
func is_peer_operator(peer_id: int) -> bool:
|
|
||||||
return bool(_operator_peer_ids.get(peer_id, false))
|
|
||||||
|
|
||||||
|
|
||||||
func set_peer_operator(
|
|
||||||
peer_id: int,
|
|
||||||
fingerprint: String,
|
|
||||||
enabled: bool,
|
|
||||||
) -> bool:
|
|
||||||
if not can_manage_operators() or peer_id <= 1:
|
|
||||||
return false
|
|
||||||
var record: PeerRegistry.PeerRecord = _registry.get_peer(peer_id)
|
|
||||||
if (
|
|
||||||
record == null
|
|
||||||
or not record.identity_authenticated
|
|
||||||
or record.identity_fingerprint != fingerprint
|
|
||||||
):
|
|
||||||
return false
|
|
||||||
if enabled:
|
|
||||||
_session_operator_fingerprints[fingerprint] = true
|
|
||||||
else:
|
|
||||||
_session_operator_fingerprints.erase(fingerprint)
|
|
||||||
_set_operator_status(peer_id, enabled)
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func set_session_display_name(value: String) -> void:
|
func set_session_display_name(value: String) -> void:
|
||||||
var cleaned: String = value.strip_edges().left(48)
|
var cleaned: String = value.strip_edges().left(48)
|
||||||
if cleaned.is_empty():
|
if cleaned.is_empty():
|
||||||
|
|
@ -882,7 +822,6 @@ func _on_peer_disconnected(peer_id: int) -> void:
|
||||||
_pending_authentication.erase(peer_id)
|
_pending_authentication.erase(peer_id)
|
||||||
_pending_identity_challenges.erase(peer_id)
|
_pending_identity_challenges.erase(peer_id)
|
||||||
_authenticated_identity_cache.erase(peer_id)
|
_authenticated_identity_cache.erase(peer_id)
|
||||||
_operator_peer_ids.erase(peer_id)
|
|
||||||
var recovery_attempt: String = _recovery_attempts.get(peer_id, "")
|
var recovery_attempt: String = _recovery_attempts.get(peer_id, "")
|
||||||
if not recovery_attempt.is_empty():
|
if not recovery_attempt.is_empty():
|
||||||
_recovery_attempts.erase(peer_id)
|
_recovery_attempts.erase(peer_id)
|
||||||
|
|
@ -1197,11 +1136,6 @@ func submit_client_hello(data: Dictionary) -> void:
|
||||||
NetworkProtocol.RejectionCode.MALFORMED_HANDSHAKE
|
NetworkProtocol.RejectionCode.MALFORMED_HANDSHAKE
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
var operator_enabled: bool = _operator_for_fingerprint(
|
|
||||||
str(identity["fingerprint"])
|
|
||||||
)
|
|
||||||
if operator_enabled:
|
|
||||||
_operator_peer_ids[sender_id] = true
|
|
||||||
var submitted_appearance := CharacterCustomizationCatalog.sanitized_snapshot(
|
var submitted_appearance := CharacterCustomizationCatalog.sanitized_snapshot(
|
||||||
data["cosmetic_snapshot"]
|
data["cosmetic_snapshot"]
|
||||||
)
|
)
|
||||||
|
|
@ -1246,12 +1180,9 @@ func submit_client_hello(data: Dictionary) -> void:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
receive_spawn_list.rpc_id(sender_id, _build_spawn_list())
|
receive_spawn_list.rpc_id(sender_id, _build_spawn_list())
|
||||||
receive_operator_snapshot.rpc_id(sender_id, _operator_peer_id_snapshot())
|
|
||||||
receive_peer_spawn.rpc(
|
receive_peer_spawn.rpc(
|
||||||
_make_spawn_entry(sender_id, display_name, spawn_transform)
|
_make_spawn_entry(sender_id, display_name, spawn_transform)
|
||||||
)
|
)
|
||||||
if operator_enabled:
|
|
||||||
receive_operator_status.rpc(sender_id, true)
|
|
||||||
peer_authenticated.emit(sender_id, display_name)
|
peer_authenticated.emit(sender_id, display_name)
|
||||||
_emit_peer_count()
|
_emit_peer_count()
|
||||||
|
|
||||||
|
|
@ -1403,41 +1334,12 @@ func receive_peer_spawn(entry: Dictionary) -> void:
|
||||||
func receive_peer_despawn(peer_id: int) -> void:
|
func receive_peer_despawn(peer_id: int) -> void:
|
||||||
if state != State.JOINED_CLIENT:
|
if state != State.JOINED_CLIENT:
|
||||||
return
|
return
|
||||||
_operator_peer_ids.erase(peer_id)
|
|
||||||
_registry.remove_peer(peer_id)
|
_registry.remove_peer(peer_id)
|
||||||
_spawn_service.remove_peer(peer_id)
|
_spawn_service.remove_peer(peer_id)
|
||||||
peer_removed.emit(peer_id)
|
peer_removed.emit(peer_id)
|
||||||
_emit_peer_count()
|
_emit_peer_count()
|
||||||
|
|
||||||
|
|
||||||
@rpc("authority", "call_remote", "reliable", 0)
|
|
||||||
func receive_operator_snapshot(peer_ids: PackedInt32Array) -> void:
|
|
||||||
if (
|
|
||||||
state != State.JOINED_CLIENT
|
|
||||||
or peer_ids.size() > get_session_max_players()
|
|
||||||
):
|
|
||||||
return
|
|
||||||
_operator_peer_ids.clear()
|
|
||||||
for peer_id: int in peer_ids:
|
|
||||||
if peer_id > 1:
|
|
||||||
_operator_peer_ids[peer_id] = true
|
|
||||||
_update_local_operator()
|
|
||||||
for peer_id: int in _operator_peer_ids:
|
|
||||||
operator_status_changed.emit(peer_id, true)
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("authority", "call_remote", "reliable", 0)
|
|
||||||
func receive_operator_status(peer_id: int, enabled: bool) -> void:
|
|
||||||
if state != State.JOINED_CLIENT or peer_id <= 1:
|
|
||||||
return
|
|
||||||
if enabled:
|
|
||||||
_operator_peer_ids[peer_id] = true
|
|
||||||
else:
|
|
||||||
_operator_peer_ids.erase(peer_id)
|
|
||||||
_update_local_operator()
|
|
||||||
operator_status_changed.emit(peer_id, enabled)
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_spawn_entry(entry: Dictionary) -> void:
|
func _apply_spawn_entry(entry: Dictionary) -> void:
|
||||||
if (
|
if (
|
||||||
typeof(entry.get("peer_id")) != TYPE_INT
|
typeof(entry.get("peer_id")) != TYPE_INT
|
||||||
|
|
@ -1568,36 +1470,6 @@ func _sanitized_capabilities(value: Variant) -> PackedStringArray:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
func _operator_for_fingerprint(fingerprint: String) -> bool:
|
|
||||||
return bool((
|
|
||||||
_configured_operator_fingerprints
|
|
||||||
if _dedicated_host
|
|
||||||
else _session_operator_fingerprints
|
|
||||||
).get(fingerprint, false))
|
|
||||||
|
|
||||||
|
|
||||||
func _operator_peer_id_snapshot() -> PackedInt32Array:
|
|
||||||
var result: PackedInt32Array = PackedInt32Array()
|
|
||||||
for peer_id: int in _operator_peer_ids:
|
|
||||||
result.append(peer_id)
|
|
||||||
result.sort()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
func _set_operator_status(peer_id: int, enabled: bool) -> void:
|
|
||||||
if enabled:
|
|
||||||
_operator_peer_ids[peer_id] = true
|
|
||||||
else:
|
|
||||||
_operator_peer_ids.erase(peer_id)
|
|
||||||
operator_status_changed.emit(peer_id, enabled)
|
|
||||||
receive_operator_status.rpc(peer_id, enabled)
|
|
||||||
|
|
||||||
|
|
||||||
func _update_local_operator() -> void:
|
|
||||||
var local_peer_id: int = multiplayer.get_unique_id()
|
|
||||||
_local_operator = bool(_operator_peer_ids.get(local_peer_id, false))
|
|
||||||
|
|
||||||
|
|
||||||
func _verify_spawn_identity(entry: Dictionary) -> bool:
|
func _verify_spawn_identity(entry: Dictionary) -> bool:
|
||||||
var fingerprint := str(entry.get("identity_fingerprint", ""))
|
var fingerprint := str(entry.get("identity_fingerprint", ""))
|
||||||
var public_pem := NetworkIdentityCrypto.normalize_public_pem(
|
var public_pem := NetworkIdentityCrypto.normalize_public_pem(
|
||||||
|
|
@ -2057,8 +1929,5 @@ func _teardown_peer() -> void:
|
||||||
_server_identity_fingerprint = ""
|
_server_identity_fingerprint = ""
|
||||||
_server_identity_public_key = ""
|
_server_identity_public_key = ""
|
||||||
_session_identity_keys.clear()
|
_session_identity_keys.clear()
|
||||||
_session_operator_fingerprints.clear()
|
|
||||||
_operator_peer_ids.clear()
|
|
||||||
_local_operator = false
|
|
||||||
_host_port = 0
|
_host_port = 0
|
||||||
_dedicated_host = false
|
_dedicated_host = false
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ var full_fingerprint := ""
|
||||||
var compact_fingerprint := ""
|
var compact_fingerprint := ""
|
||||||
var display_name := ""
|
var display_name := ""
|
||||||
var is_host := false
|
var is_host := false
|
||||||
var is_operator := false
|
|
||||||
var is_local_player := false
|
var is_local_player := false
|
||||||
var continuity_state := ""
|
var continuity_state := ""
|
||||||
var ping_to_host_ms := -1
|
var ping_to_host_ms := -1
|
||||||
|
|
@ -14,5 +13,4 @@ var muted := false
|
||||||
var blocked := false
|
var blocked := false
|
||||||
var can_kick := false
|
var can_kick := false
|
||||||
var can_ban := false
|
var can_ban := false
|
||||||
var can_manage_operator := false
|
|
||||||
var revision := 0
|
var revision := 0
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ readonly RUN_ROOT="$(mktemp -d -t netfishing-validations.XXXXXX)"
|
||||||
|
|
||||||
readonly -a QUICK_TESTS=(
|
readonly -a QUICK_TESTS=(
|
||||||
"tests/android_readiness_validation.gd"
|
"tests/android_readiness_validation.gd"
|
||||||
"tests/dedicated_server_config_validation.gd"
|
|
||||||
"tests/fish_catalog_content_validation.gd"
|
"tests/fish_catalog_content_validation.gd"
|
||||||
"tests/fish_quality_validation.gd"
|
"tests/fish_quality_validation.gd"
|
||||||
"tests/fishing_audio_validation.gd"
|
"tests/fishing_audio_validation.gd"
|
||||||
|
|
@ -33,7 +32,6 @@ readonly -a RUNTIME_TESTS=(
|
||||||
|
|
||||||
readonly -a HOST_TESTS=(
|
readonly -a HOST_TESTS=(
|
||||||
"tests/art_tools_validation.gd"
|
"tests/art_tools_validation.gd"
|
||||||
"tests/dedicated_host_session_validation.gd"
|
|
||||||
"tests/economy_regression_validation.gd"
|
"tests/economy_regression_validation.gd"
|
||||||
"tests/fish_hotbar_showcase_validation.gd"
|
"tests/fish_hotbar_showcase_validation.gd"
|
||||||
"tests/fishing_authority_validation.gd"
|
"tests/fishing_authority_validation.gd"
|
||||||
|
|
@ -46,7 +44,6 @@ readonly -a NETWORK_TESTS=(
|
||||||
"tests/fish_showcase_multiplayer_validation.gd"
|
"tests/fish_showcase_multiplayer_validation.gd"
|
||||||
"tests/fishing_multiplayer_validation.gd"
|
"tests/fishing_multiplayer_validation.gd"
|
||||||
"tests/job_multiplayer_validation.gd"
|
"tests/job_multiplayer_validation.gd"
|
||||||
"tests/operator_multiplayer_validation.gd"
|
|
||||||
"tests/surface_drawing_multiplayer_validation.gd"
|
"tests/surface_drawing_multiplayer_validation.gd"
|
||||||
"tests/world_time_multiplayer_validation.gd"
|
"tests/world_time_multiplayer_validation.gd"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ const DEFAULT_NAME: String = "NETfishing Dedicated Server"
|
||||||
const DEFAULT_BIND_ADDRESS: String = "*"
|
const DEFAULT_BIND_ADDRESS: String = "*"
|
||||||
const DEFAULT_PORT: int = 7777
|
const DEFAULT_PORT: int = 7777
|
||||||
const DEFAULT_MAX_PLAYERS: int = 8
|
const DEFAULT_MAX_PLAYERS: int = 8
|
||||||
const MAX_OPERATORS: int = 64
|
|
||||||
|
|
||||||
var server_name: String = DEFAULT_NAME
|
var server_name: String = DEFAULT_NAME
|
||||||
var bind_address: String = DEFAULT_BIND_ADDRESS
|
var bind_address: String = DEFAULT_BIND_ADDRESS
|
||||||
|
|
@ -14,7 +13,6 @@ var max_players: int = DEFAULT_MAX_PLAYERS
|
||||||
var public_listing: bool = false
|
var public_listing: bool = false
|
||||||
var discovery_url: String = ""
|
var discovery_url: String = ""
|
||||||
var data_directory: String = ""
|
var data_directory: String = ""
|
||||||
var operator_fingerprints: PackedStringArray = PackedStringArray()
|
|
||||||
var error_message: String = ""
|
var error_message: String = ""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,9 +65,6 @@ func _load_file(path: String) -> bool:
|
||||||
discovery_url = str(file.get_value(
|
discovery_url = str(file.get_value(
|
||||||
"discovery", "url", discovery_url
|
"discovery", "url", discovery_url
|
||||||
))
|
))
|
||||||
operator_fingerprints = _parse_fingerprint_list(file.get_value(
|
|
||||||
"moderation", "operators", operator_fingerprints
|
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -93,10 +88,6 @@ func _apply_environment() -> void:
|
||||||
data_directory = _environment_string(
|
data_directory = _environment_string(
|
||||||
"NETFISHING_DATA_DIR", data_directory
|
"NETFISHING_DATA_DIR", data_directory
|
||||||
)
|
)
|
||||||
if OS.has_environment("NETFISHING_SERVER_OPERATORS"):
|
|
||||||
operator_fingerprints = _parse_fingerprint_list(
|
|
||||||
OS.get_environment("NETFISHING_SERVER_OPERATORS")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_arguments(arguments: PackedStringArray) -> void:
|
func _apply_arguments(arguments: PackedStringArray) -> void:
|
||||||
|
|
@ -115,10 +106,6 @@ func _apply_arguments(arguments: PackedStringArray) -> void:
|
||||||
data_directory = argument.trim_prefix("--data-dir=")
|
data_directory = argument.trim_prefix("--data-dir=")
|
||||||
elif argument.begins_with("--discovery-url="):
|
elif argument.begins_with("--discovery-url="):
|
||||||
discovery_url = argument.trim_prefix("--discovery-url=")
|
discovery_url = argument.trim_prefix("--discovery-url=")
|
||||||
elif argument.begins_with("--operators="):
|
|
||||||
operator_fingerprints = _parse_fingerprint_list(
|
|
||||||
argument.trim_prefix("--operators=")
|
|
||||||
)
|
|
||||||
elif argument == "--public":
|
elif argument == "--public":
|
||||||
public_listing = true
|
public_listing = true
|
||||||
elif argument == "--private":
|
elif argument == "--private":
|
||||||
|
|
@ -130,7 +117,6 @@ func _validate() -> void:
|
||||||
bind_address = bind_address.strip_edges()
|
bind_address = bind_address.strip_edges()
|
||||||
discovery_url = discovery_url.strip_edges().trim_suffix("/")
|
discovery_url = discovery_url.strip_edges().trim_suffix("/")
|
||||||
data_directory = data_directory.strip_edges()
|
data_directory = data_directory.strip_edges()
|
||||||
operator_fingerprints = _normalized_fingerprints(operator_fingerprints)
|
|
||||||
if server_name.is_empty():
|
if server_name.is_empty():
|
||||||
error_message = "Server name cannot be empty."
|
error_message = "Server name cannot be empty."
|
||||||
elif not _safe_text(server_name):
|
elif not _safe_text(server_name):
|
||||||
|
|
@ -148,17 +134,6 @@ func _validate() -> void:
|
||||||
or discovery_url.begins_with("http://")
|
or discovery_url.begins_with("http://")
|
||||||
):
|
):
|
||||||
error_message = "A public server requires a discovery URL."
|
error_message = "A public server requires a discovery URL."
|
||||||
elif operator_fingerprints.size() > MAX_OPERATORS:
|
|
||||||
error_message = "A server may configure at most %d operators." % (
|
|
||||||
MAX_OPERATORS
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for fingerprint: String in operator_fingerprints:
|
|
||||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
|
||||||
error_message = (
|
|
||||||
"Server operator fingerprints must be 64 lowercase hex characters."
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
static func _safe_text(value: String) -> bool:
|
static func _safe_text(value: String) -> bool:
|
||||||
|
|
@ -189,29 +164,3 @@ static func _environment_bool(name: String, fallback: bool) -> bool:
|
||||||
|
|
||||||
static func _parse_int(value: String, fallback: int) -> int:
|
static func _parse_int(value: String, fallback: int) -> int:
|
||||||
return int(value) if value.strip_edges().is_valid_int() else fallback
|
return int(value) if value.strip_edges().is_valid_int() else fallback
|
||||||
|
|
||||||
|
|
||||||
static func _parse_fingerprint_list(value: Variant) -> PackedStringArray:
|
|
||||||
var values: PackedStringArray = PackedStringArray()
|
|
||||||
if typeof(value) == TYPE_STRING:
|
|
||||||
var text: String = str(value)
|
|
||||||
for separator: String in [";", "\n", "\r", "\t", " "]:
|
|
||||||
text = text.replace(separator, ",")
|
|
||||||
values = text.split(",", false)
|
|
||||||
elif typeof(value) in [TYPE_ARRAY, TYPE_PACKED_STRING_ARRAY]:
|
|
||||||
for entry: Variant in value:
|
|
||||||
if typeof(entry) in [TYPE_STRING, TYPE_STRING_NAME]:
|
|
||||||
values.append(str(entry))
|
|
||||||
return _normalized_fingerprints(values)
|
|
||||||
|
|
||||||
|
|
||||||
static func _normalized_fingerprints(
|
|
||||||
values: PackedStringArray,
|
|
||||||
) -> PackedStringArray:
|
|
||||||
var result: PackedStringArray = PackedStringArray()
|
|
||||||
for value: String in values:
|
|
||||||
var fingerprint: String = value.strip_edges().to_lower()
|
|
||||||
if not fingerprint.is_empty() and fingerprint not in result:
|
|
||||||
result.append(fingerprint)
|
|
||||||
result.sort()
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
extends SceneTree
|
extends SceneTree
|
||||||
|
|
||||||
const TEST_PORT: int = 35777
|
const TEST_PORT: int = 35777
|
||||||
const OPERATOR_FINGERPRINT: String = (
|
|
||||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
|
|
@ -38,18 +35,12 @@ func _run() -> void:
|
||||||
host_bans,
|
host_bans,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
assert(session.configure_dedicated_operators(
|
|
||||||
PackedStringArray([OPERATOR_FINGERPRINT])
|
|
||||||
))
|
|
||||||
assert(session.start_dedicated_host(TEST_PORT, 5, "127.0.0.1"))
|
assert(session.start_dedicated_host(TEST_PORT, 5, "127.0.0.1"))
|
||||||
assert(session.is_dedicated_host())
|
assert(session.is_dedicated_host())
|
||||||
assert(session.get_local_peer_id() == 0)
|
assert(session.get_local_peer_id() == 0)
|
||||||
assert(session.get_player_count() == 0)
|
assert(session.get_player_count() == 0)
|
||||||
assert(session.get_session_max_players() == 5)
|
assert(session.get_session_max_players() == 5)
|
||||||
assert(session.get_host_port() == TEST_PORT)
|
assert(session.get_host_port() == TEST_PORT)
|
||||||
assert(bool(session.call(
|
|
||||||
"_operator_for_fingerprint", OPERATOR_FINGERPRINT
|
|
||||||
)))
|
|
||||||
assert(session.set_host_open(true))
|
assert(session.set_host_open(true))
|
||||||
assert(session.is_open_host())
|
assert(session.is_open_host())
|
||||||
session.disconnect_session("Dedicated host validation complete.")
|
session.disconnect_session("Dedicated host validation complete.")
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
extends SceneTree
|
extends SceneTree
|
||||||
|
|
||||||
const ConfigType = preload("res://server/dedicated_server_config.gd")
|
const ConfigType = preload("res://server/dedicated_server_config.gd")
|
||||||
const OPERATOR_A: String = (
|
|
||||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
||||||
)
|
|
||||||
const OPERATOR_B: String = (
|
|
||||||
"fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
func _initialize() -> void:
|
||||||
|
|
@ -31,9 +25,6 @@ func _run() -> void:
|
||||||
file.set_value("server", "public", true)
|
file.set_value("server", "public", true)
|
||||||
file.set_value("server", "data_directory", "/tmp/configured-server")
|
file.set_value("server", "data_directory", "/tmp/configured-server")
|
||||||
file.set_value("discovery", "url", "https://discovery.netfishing.org/")
|
file.set_value("discovery", "url", "https://discovery.netfishing.org/")
|
||||||
file.set_value(
|
|
||||||
"moderation", "operators", PackedStringArray([OPERATOR_B, OPERATOR_A])
|
|
||||||
)
|
|
||||||
assert(file.save(path) == OK)
|
assert(file.save(path) == OK)
|
||||||
|
|
||||||
var configured := ConfigType.new()
|
var configured := ConfigType.new()
|
||||||
|
|
@ -46,35 +37,8 @@ func _run() -> void:
|
||||||
assert(int(configured.get("max_players")) == 12)
|
assert(int(configured.get("max_players")) == 12)
|
||||||
assert(bool(configured.get("public_listing")))
|
assert(bool(configured.get("public_listing")))
|
||||||
assert(str(configured.get("discovery_url")) == "https://discovery.netfishing.org")
|
assert(str(configured.get("discovery_url")) == "https://discovery.netfishing.org")
|
||||||
assert(
|
|
||||||
configured.get("operator_fingerprints")
|
|
||||||
== PackedStringArray([OPERATOR_A, OPERATOR_B])
|
|
||||||
)
|
|
||||||
assert(DirAccess.remove_absolute(path) == OK)
|
assert(DirAccess.remove_absolute(path) == OK)
|
||||||
|
|
||||||
var parsed := ConfigType.new()
|
|
||||||
parsed.set(
|
|
||||||
"operator_fingerprints",
|
|
||||||
ConfigType._parse_fingerprint_list(
|
|
||||||
"%s, %s;%s" % [OPERATOR_B.to_upper(), OPERATOR_A, OPERATOR_A]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parsed.set("data_directory", "/tmp/parsed-server")
|
|
||||||
parsed.call("_validate")
|
|
||||||
assert(parsed.is_valid())
|
|
||||||
assert(
|
|
||||||
parsed.get("operator_fingerprints")
|
|
||||||
== PackedStringArray([OPERATOR_A, OPERATOR_B])
|
|
||||||
)
|
|
||||||
|
|
||||||
var invalid_operator := ConfigType.new()
|
|
||||||
invalid_operator.set(
|
|
||||||
"operator_fingerprints", PackedStringArray(["not-a-fingerprint"])
|
|
||||||
)
|
|
||||||
invalid_operator.set("data_directory", "/tmp/invalid-operator-server")
|
|
||||||
invalid_operator.call("_validate")
|
|
||||||
assert(not invalid_operator.is_valid())
|
|
||||||
|
|
||||||
var invalid := ConfigType.new()
|
var invalid := ConfigType.new()
|
||||||
invalid.set("public_listing", true)
|
invalid.set("public_listing", true)
|
||||||
invalid.set("data_directory", "/tmp/invalid-server")
|
invalid.set("data_directory", "/tmp/invalid-server")
|
||||||
|
|
@ -94,27 +58,6 @@ func _run() -> void:
|
||||||
DiscoveryClient.UPNP_RETRY_INTERVAL_SECONDS
|
DiscoveryClient.UPNP_RETRY_INTERVAL_SECONDS
|
||||||
< DiscoveryClient.UPNP_RENEW_INTERVAL_SECONDS
|
< DiscoveryClient.UPNP_RENEW_INTERVAL_SECONDS
|
||||||
)
|
)
|
||||||
var discovery_settings_path: String = ProjectSettings.globalize_path(
|
|
||||||
DiscoveryClient.SETTINGS_PATH
|
|
||||||
)
|
|
||||||
assert(not FileAccess.file_exists(discovery_settings_path))
|
|
||||||
assert(discovery.set_room_name("Client Room"))
|
|
||||||
var client_settings: PackedByteArray = FileAccess.get_file_as_bytes(
|
|
||||||
discovery_settings_path
|
|
||||||
)
|
|
||||||
assert(not client_settings.is_empty())
|
|
||||||
var dedicated_discovery := DiscoveryClient.new()
|
|
||||||
dedicated_discovery.call("_load_settings")
|
|
||||||
assert(
|
|
||||||
dedicated_discovery.configure_dedicated_runtime("Headless Room")
|
|
||||||
)
|
|
||||||
assert(dedicated_discovery.get_room_name() == "Headless Room")
|
|
||||||
assert(
|
|
||||||
FileAccess.get_file_as_bytes(discovery_settings_path)
|
|
||||||
== client_settings
|
|
||||||
)
|
|
||||||
dedicated_discovery.free()
|
|
||||||
assert(DirAccess.remove_absolute(discovery_settings_path) == OK)
|
|
||||||
assert(
|
assert(
|
||||||
discovery.get_host_state()
|
discovery.get_host_state()
|
||||||
== DiscoveryClient.HostState.CLOSED
|
== DiscoveryClient.HostState.CLOSED
|
||||||
|
|
|
||||||
|
|
@ -83,68 +83,40 @@ func _validate_barrier_challenge_curve() -> void:
|
||||||
var expected_maximums: Array[int] = [9, 50, 100, 200, 400]
|
var expected_maximums: Array[int] = [9, 50, 100, 200, 400]
|
||||||
var previous_health: int = 0
|
var previous_health: int = 0
|
||||||
for quality: int in FishQualityType.TIER_COUNT:
|
for quality: int in FishQualityType.TIER_COUNT:
|
||||||
var expected_range := Vector2i(
|
|
||||||
expected_minimums[quality], expected_maximums[quality]
|
|
||||||
)
|
|
||||||
assert(
|
|
||||||
FishQualityType.barrier_health_range(quality)
|
|
||||||
== expected_range
|
|
||||||
)
|
|
||||||
assert(
|
|
||||||
FishQualityType.roll_barrier_health(null, quality)
|
|
||||||
== expected_range.x
|
|
||||||
)
|
|
||||||
var rng := RandomNumberGenerator.new()
|
|
||||||
rng.seed = 73013 + quality
|
|
||||||
var observed_counts: Dictionary[int, int] = {}
|
|
||||||
var observed_minimum: int = expected_range.y
|
|
||||||
var observed_maximum: int = expected_range.x
|
|
||||||
var observed_total: int = 0
|
|
||||||
var band_size: int = expected_range.y - expected_range.x + 1
|
|
||||||
var sample_count: int = maxi(10000, band_size * 250)
|
|
||||||
for _sample: int in sample_count:
|
|
||||||
var rolled_health: int = FishQualityType.roll_barrier_health(
|
|
||||||
rng, quality
|
|
||||||
)
|
|
||||||
assert(
|
|
||||||
rolled_health >= expected_range.x
|
|
||||||
and rolled_health <= expected_range.y
|
|
||||||
)
|
|
||||||
observed_counts[rolled_health] = (
|
|
||||||
int(observed_counts.get(rolled_health, 0)) + 1
|
|
||||||
)
|
|
||||||
observed_minimum = mini(observed_minimum, rolled_health)
|
|
||||||
observed_maximum = maxi(observed_maximum, rolled_health)
|
|
||||||
observed_total += rolled_health
|
|
||||||
assert(observed_minimum == expected_range.x)
|
|
||||||
assert(observed_maximum == expected_range.y)
|
|
||||||
for authored_health: int in range(
|
|
||||||
expected_range.x, expected_range.y + 1
|
|
||||||
):
|
|
||||||
assert(observed_counts.has(authored_health))
|
|
||||||
var observed_average: float = (
|
|
||||||
float(observed_total) / float(sample_count)
|
|
||||||
)
|
|
||||||
var authored_midpoint: float = (
|
|
||||||
float(expected_range.x + expected_range.y) / 2.0
|
|
||||||
)
|
|
||||||
assert(
|
|
||||||
absf(observed_average - authored_midpoint)
|
|
||||||
< maxf(0.15, float(band_size) * 0.01)
|
|
||||||
)
|
|
||||||
var health: int = FishQualityType.apply_barrier_health(8, quality)
|
var health: int = FishQualityType.apply_barrier_health(8, quality)
|
||||||
assert(health > previous_health)
|
assert(health > previous_health)
|
||||||
previous_health = health
|
previous_health = health
|
||||||
assert(FishQualityType.apply_barrier_health(8, -1) == 8)
|
assert(FishQualityType.apply_barrier_health(8, -1) == 8)
|
||||||
assert(FishQualityType.apply_barrier_health(0, FishQualityType.Tier.SHINY) == 4)
|
assert(FishQualityType.apply_barrier_health(0, FishQualityType.Tier.SHINY) == 4)
|
||||||
|
var impressive_low_input: int = FishQualityType.barrier_health_for_catch(
|
||||||
|
FishQualityType.Tier.IMPRESSIVE,
|
||||||
|
0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
var impressive_high_input: int = FishQualityType.barrier_health_for_catch(
|
||||||
|
FishQualityType.Tier.IMPRESSIVE,
|
||||||
|
4,
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
assert(impressive_low_input >= 50 and impressive_low_input <= 100)
|
||||||
|
assert(impressive_high_input > impressive_low_input)
|
||||||
|
assert(impressive_high_input <= 100)
|
||||||
assert(
|
assert(
|
||||||
FishQualityType.barrier_health_range(-1)
|
FishQualityType.barrier_health_for_catch(
|
||||||
== Vector2i(1, 9)
|
FishQualityType.Tier.BORING,
|
||||||
|
4,
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
) <= 9
|
||||||
)
|
)
|
||||||
|
|
||||||
var profile := CatchDifficultyProfile.new()
|
var profile := CatchDifficultyProfile.new()
|
||||||
profile.barrier_count_min = 1
|
profile.barrier_count_min = 1
|
||||||
profile.barrier_count_max = 1
|
profile.barrier_count_max = 1
|
||||||
|
profile.barrier_health_min = 8
|
||||||
|
profile.barrier_health_max = 8
|
||||||
profile.first_barrier_margin = 0.2
|
profile.first_barrier_margin = 0.2
|
||||||
profile.final_barrier_margin = 0.2
|
profile.final_barrier_margin = 0.2
|
||||||
profile.minimum_barrier_spacing = 0.1
|
profile.minimum_barrier_spacing = 0.1
|
||||||
|
|
@ -169,9 +141,12 @@ func _validate_barrier_challenge_curve() -> void:
|
||||||
assert(health <= expected_maximums[quality])
|
assert(health <= expected_maximums[quality])
|
||||||
controller.queue_free()
|
controller.queue_free()
|
||||||
|
|
||||||
var shiny_health: int = FishQualityType.barrier_health_range(
|
var shiny_health: int = FishQualityType.barrier_health_for_catch(
|
||||||
FishQualityType.Tier.SHINY
|
FishQualityType.Tier.SHINY,
|
||||||
).y
|
4,
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
var base_power_clicks: int = ceili(float(shiny_health) / 1.0)
|
var base_power_clicks: int = ceili(float(shiny_health) / 1.0)
|
||||||
var max_power_clicks: int = ceili(
|
var max_power_clicks: int = ceili(
|
||||||
float(shiny_health)
|
float(shiny_health)
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ func _run() -> void:
|
||||||
assert(not bobber.visible)
|
assert(not bobber.visible)
|
||||||
|
|
||||||
# A private host rolls and retains the authoritative catch before the fight
|
# A private host rolls and retains the authoritative catch before the fight
|
||||||
# so its quality selects the barrier band clients receive in snapshots.
|
# so its quality can scale the same barriers clients receive in snapshots.
|
||||||
fishing_spot.call("_begin_aiming", player)
|
fishing_spot.call("_begin_aiming", player)
|
||||||
fishing_spot.set("_cast_charge", 0.32)
|
fishing_spot.set("_cast_charge", 0.32)
|
||||||
fishing_spot.call("_update_cast_charge", 0.0)
|
fishing_spot.call("_update_cast_charge", 0.0)
|
||||||
|
|
@ -138,6 +138,8 @@ func _run() -> void:
|
||||||
attempt.barrier_damage,
|
attempt.barrier_damage,
|
||||||
attempt.encounter_seed,
|
attempt.encounter_seed,
|
||||||
fish_catch.quality,
|
fish_catch.quality,
|
||||||
|
int(fish.rarity),
|
||||||
|
fish.get_weight_percentile(fish_catch.weight_lb),
|
||||||
)
|
)
|
||||||
var quality_barriers: Array = attempt.controller.get("_barriers")
|
var quality_barriers: Array = attempt.controller.get("_barriers")
|
||||||
var reference_barriers: Array = reference_controller.get("_barriers")
|
var reference_barriers: Array = reference_controller.get("_barriers")
|
||||||
|
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
||||||
extends SceneTree
|
|
||||||
|
|
||||||
const MainScene = preload("res://main/main.tscn")
|
|
||||||
const TEST_PORT: int = 18140
|
|
||||||
const FIRST_BANNED_FINGERPRINT: String = (
|
|
||||||
"1111111111111111111111111111111111111111111111111111111111111111"
|
|
||||||
)
|
|
||||||
const SECOND_BANNED_FINGERPRINT: String = (
|
|
||||||
"2222222222222222222222222222222222222222222222222222222222222222"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _moderation_results: Array[Dictionary] = []
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
|
||||||
call_deferred("_run")
|
|
||||||
|
|
||||||
|
|
||||||
func _run() -> void:
|
|
||||||
var arguments: PackedStringArray = OS.get_cmdline_user_args()
|
|
||||||
if arguments.has("host"):
|
|
||||||
await _run_host()
|
|
||||||
return
|
|
||||||
if arguments.has("client"):
|
|
||||||
await _run_client()
|
|
||||||
return
|
|
||||||
push_error("Operator multiplayer validation needs host or client mode.")
|
|
||||||
quit(1)
|
|
||||||
|
|
||||||
|
|
||||||
func _run_host() -> void:
|
|
||||||
var main: Node = await _create_initialized_main()
|
|
||||||
var session := main.get_node("%NetworkSession") as NetworkSession
|
|
||||||
var service := (
|
|
||||||
main.get_node("%NetworkPlayerListService")
|
|
||||||
as NetworkPlayerListService
|
|
||||||
)
|
|
||||||
var bans := main.get_node("%HostBanStore") as HostBanStore
|
|
||||||
assert(session.start_private_host(TEST_PORT))
|
|
||||||
assert(session.set_host_open(true))
|
|
||||||
|
|
||||||
var remote_peer_id: int = await _wait_for_remote_peer(session)
|
|
||||||
assert(remote_peer_id > 1)
|
|
||||||
var remote_record: PeerRegistry.PeerRecord = session.get_peer_record(
|
|
||||||
remote_peer_id
|
|
||||||
)
|
|
||||||
assert(remote_record != null and remote_record.identity_authenticated)
|
|
||||||
var host_fingerprint: String = session.get_host_identity_fingerprint()
|
|
||||||
assert(bans.ban(
|
|
||||||
host_fingerprint, FIRST_BANNED_FINGERPRINT, "First Banned Player"
|
|
||||||
))
|
|
||||||
|
|
||||||
var entry: PlayerListEntry = _entry_for_peer(service, remote_peer_id)
|
|
||||||
assert(entry != null and entry.can_manage_operator)
|
|
||||||
assert(service.set_operator(
|
|
||||||
remote_peer_id,
|
|
||||||
remote_record.identity_fingerprint,
|
|
||||||
true,
|
|
||||||
entry.revision,
|
|
||||||
))
|
|
||||||
assert(session.is_peer_operator(remote_peer_id))
|
|
||||||
var players_page := main.find_child(
|
|
||||||
"PlayersPage", true, false
|
|
||||||
) as PlayersPage
|
|
||||||
assert(players_page != null)
|
|
||||||
players_page.call("_refresh")
|
|
||||||
assert(_has_button_text(players_page, "deop"))
|
|
||||||
|
|
||||||
var unban_deadline: int = Time.get_ticks_msec() + 12000
|
|
||||||
while (
|
|
||||||
Time.get_ticks_msec() < unban_deadline
|
|
||||||
and bans.is_banned(host_fingerprint, FIRST_BANNED_FINGERPRINT)
|
|
||||||
):
|
|
||||||
await process_frame
|
|
||||||
assert(not bans.is_banned(
|
|
||||||
host_fingerprint, FIRST_BANNED_FINGERPRINT
|
|
||||||
))
|
|
||||||
assert(bans.ban(
|
|
||||||
host_fingerprint, SECOND_BANNED_FINGERPRINT, "Second Banned Player"
|
|
||||||
))
|
|
||||||
|
|
||||||
entry = _entry_for_peer(service, remote_peer_id)
|
|
||||||
assert(entry != null and entry.is_operator)
|
|
||||||
assert(service.set_operator(
|
|
||||||
remote_peer_id,
|
|
||||||
remote_record.identity_fingerprint,
|
|
||||||
false,
|
|
||||||
entry.revision,
|
|
||||||
))
|
|
||||||
assert(not session.is_peer_operator(remote_peer_id))
|
|
||||||
|
|
||||||
var disconnect_deadline: int = Time.get_ticks_msec() + 8000
|
|
||||||
while (
|
|
||||||
Time.get_ticks_msec() < disconnect_deadline
|
|
||||||
and session.is_authenticated_peer(remote_peer_id)
|
|
||||||
):
|
|
||||||
await process_frame
|
|
||||||
assert(not session.is_authenticated_peer(remote_peer_id))
|
|
||||||
assert(bans.is_banned(host_fingerprint, SECOND_BANNED_FINGERPRINT))
|
|
||||||
print("Operator multiplayer host validation: PASS")
|
|
||||||
session.disconnect_session("")
|
|
||||||
main.queue_free()
|
|
||||||
for _frame: int in 4:
|
|
||||||
await process_frame
|
|
||||||
await create_timer(0.1).timeout
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
func _run_client() -> void:
|
|
||||||
var main: Node = await _create_initialized_main()
|
|
||||||
main.call(
|
|
||||||
"_on_title_join_game_requested", "127.0.0.1:%d" % TEST_PORT
|
|
||||||
)
|
|
||||||
var session := main.get_node("%NetworkSession") as NetworkSession
|
|
||||||
var service := (
|
|
||||||
main.get_node("%NetworkPlayerListService")
|
|
||||||
as NetworkPlayerListService
|
|
||||||
)
|
|
||||||
service.moderation_finished.connect(
|
|
||||||
func(success: bool, message: String) -> void:
|
|
||||||
_moderation_results.append({
|
|
||||||
"success": success,
|
|
||||||
"message": message,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
var join_deadline: int = Time.get_ticks_msec() + 20000
|
|
||||||
while Time.get_ticks_msec() < join_deadline:
|
|
||||||
await process_frame
|
|
||||||
if session.state == NetworkSession.State.VERIFYING_SERVER_IDENTITY:
|
|
||||||
main.call("_confirm_server_trust")
|
|
||||||
if session.is_joined_client():
|
|
||||||
break
|
|
||||||
assert(session.is_joined_client())
|
|
||||||
|
|
||||||
var operator_deadline: int = Time.get_ticks_msec() + 10000
|
|
||||||
while Time.get_ticks_msec() < operator_deadline and not session.is_local_operator():
|
|
||||||
await process_frame
|
|
||||||
assert(session.is_local_operator())
|
|
||||||
assert(service.is_local_moderator())
|
|
||||||
var players_page := main.find_child(
|
|
||||||
"PlayersPage", true, false
|
|
||||||
) as PlayersPage
|
|
||||||
assert(players_page != null)
|
|
||||||
players_page.call("_refresh")
|
|
||||||
var tabs := players_page.get("_tabs") as HBoxContainer
|
|
||||||
assert((tabs.get_child(2) as Button).visible)
|
|
||||||
players_page.call("_select_tab", 2)
|
|
||||||
assert(int(players_page.get("_current_tab")) == 2)
|
|
||||||
var local_entry: PlayerListEntry = _entry_for_peer(
|
|
||||||
service, session.get_local_peer_id()
|
|
||||||
)
|
|
||||||
assert(local_entry != null and local_entry.is_operator)
|
|
||||||
|
|
||||||
var ban_deadline: int = Time.get_ticks_msec() + 10000
|
|
||||||
while (
|
|
||||||
Time.get_ticks_msec() < ban_deadline
|
|
||||||
and not _has_ban(service.get_bans(), FIRST_BANNED_FINGERPRINT)
|
|
||||||
):
|
|
||||||
await process_frame
|
|
||||||
assert(_has_ban(service.get_bans(), FIRST_BANNED_FINGERPRINT))
|
|
||||||
assert(service.unban(FIRST_BANNED_FINGERPRINT))
|
|
||||||
var result_deadline: int = Time.get_ticks_msec() + 8000
|
|
||||||
while Time.get_ticks_msec() < result_deadline and _moderation_results.is_empty():
|
|
||||||
await process_frame
|
|
||||||
assert(not _moderation_results.is_empty())
|
|
||||||
assert(bool(_moderation_results.back().get("success", false)))
|
|
||||||
|
|
||||||
var deop_deadline: int = Time.get_ticks_msec() + 10000
|
|
||||||
while Time.get_ticks_msec() < deop_deadline and session.is_local_operator():
|
|
||||||
await process_frame
|
|
||||||
assert(not session.is_local_operator())
|
|
||||||
assert(not service.is_local_moderator())
|
|
||||||
assert(not (tabs.get_child(2) as Button).visible)
|
|
||||||
assert(int(players_page.get("_current_tab")) == 0)
|
|
||||||
service.request_unban.rpc_id(1, SECOND_BANNED_FINGERPRINT)
|
|
||||||
await create_timer(1.0).timeout
|
|
||||||
print("Operator multiplayer client validation: PASS")
|
|
||||||
session.disconnect_session("")
|
|
||||||
main.queue_free()
|
|
||||||
for _frame: int in 4:
|
|
||||||
await process_frame
|
|
||||||
await create_timer(0.1).timeout
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
func _create_initialized_main() -> Node:
|
|
||||||
root.size = Vector2i(1280, 720)
|
|
||||||
var main: Node = MainScene.instantiate()
|
|
||||||
root.add_child(main)
|
|
||||||
for _frame: int in 4:
|
|
||||||
await process_frame
|
|
||||||
if not bool(main.get("_application_initialized")):
|
|
||||||
main.call("_activate_selected_data_path", "", true)
|
|
||||||
for _frame: int in 8:
|
|
||||||
await process_frame
|
|
||||||
assert(bool(main.get("_application_initialized")))
|
|
||||||
return main
|
|
||||||
|
|
||||||
|
|
||||||
func _wait_for_remote_peer(session: NetworkSession) -> int:
|
|
||||||
var deadline: int = Time.get_ticks_msec() + 20000
|
|
||||||
while Time.get_ticks_msec() < deadline:
|
|
||||||
await process_frame
|
|
||||||
for peer_id: int in session.get_authenticated_peer_ids():
|
|
||||||
if peer_id != session.get_local_peer_id():
|
|
||||||
return peer_id
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
func _entry_for_peer(
|
|
||||||
service: NetworkPlayerListService,
|
|
||||||
peer_id: int,
|
|
||||||
) -> PlayerListEntry:
|
|
||||||
for entry: PlayerListEntry in service.get_entries():
|
|
||||||
if entry.peer_id == peer_id:
|
|
||||||
return entry
|
|
||||||
return null
|
|
||||||
|
|
||||||
|
|
||||||
func _has_ban(records: Array[Dictionary], fingerprint: String) -> bool:
|
|
||||||
for record: Dictionary in records:
|
|
||||||
if str(record.get("target_fingerprint", "")) == fingerprint:
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
func _has_button_text(root_node: Node, text: String) -> bool:
|
|
||||||
for node: Node in root_node.find_children("*", "Button", true, false):
|
|
||||||
var button := node as Button
|
|
||||||
if button != null and button.text == text:
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://cv17xsf20ue5t
|
|
||||||
|
|
@ -194,7 +194,7 @@ func _set_host_toggle_state(
|
||||||
|
|
||||||
|
|
||||||
func _select_tab(index: int) -> void:
|
func _select_tab(index: int) -> void:
|
||||||
if index == 2 and (_service == null or not _service.is_local_moderator()):
|
if index == 2 and (_service == null or not _service.is_local_host()):
|
||||||
return
|
return
|
||||||
_current_tab = index
|
_current_tab = index
|
||||||
_refresh()
|
_refresh()
|
||||||
|
|
@ -207,12 +207,10 @@ func _refresh() -> void:
|
||||||
_count_label.text = "%d / %d connected" % [
|
_count_label.text = "%d / %d connected" % [
|
||||||
_service.get_connected_count(), _service.get_max_players(),
|
_service.get_connected_count(), _service.get_max_players(),
|
||||||
]
|
]
|
||||||
if _current_tab == 2 and not _service.is_local_moderator():
|
|
||||||
_current_tab = 0
|
|
||||||
for index: int in _tabs.get_child_count():
|
for index: int in _tabs.get_child_count():
|
||||||
var button := _tabs.get_child(index) as Button
|
var button := _tabs.get_child(index) as Button
|
||||||
button.button_pressed = index == _current_tab
|
button.button_pressed = index == _current_tab
|
||||||
button.visible = index != 2 or _service.is_local_moderator()
|
button.visible = index != 2 or _service.is_local_host()
|
||||||
_refresh_host_settings()
|
_refresh_host_settings()
|
||||||
for child: Node in _list.get_children():
|
for child: Node in _list.get_children():
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
|
|
@ -332,7 +330,7 @@ func _on_host_status_changed(message: String, is_error: bool) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _build_active_rows() -> void:
|
func _build_active_rows() -> void:
|
||||||
if _service.is_local_moderator():
|
if _service.is_local_host():
|
||||||
_build_session_artwork_controls()
|
_build_session_artwork_controls()
|
||||||
var entries := _service.get_entries()
|
var entries := _service.get_entries()
|
||||||
if entries.is_empty():
|
if entries.is_empty():
|
||||||
|
|
@ -341,12 +339,10 @@ func _build_active_rows() -> void:
|
||||||
for entry: PlayerListEntry in entries:
|
for entry: PlayerListEntry in entries:
|
||||||
var row := _make_row()
|
var row := _make_row()
|
||||||
var identity := Label.new()
|
var identity := Label.new()
|
||||||
identity.custom_minimum_size.x = 430
|
identity.custom_minimum_size.x = 515
|
||||||
var markers: Array[String] = []
|
var markers: Array[String] = []
|
||||||
if entry.is_host:
|
if entry.is_host:
|
||||||
markers.append("host")
|
markers.append("host")
|
||||||
if entry.is_operator:
|
|
||||||
markers.append("operator")
|
|
||||||
if entry.is_local_player:
|
if entry.is_local_player:
|
||||||
markers.append("You")
|
markers.append("You")
|
||||||
identity.text = "%s%s · %s\n%s" % [
|
identity.text = "%s%s · %s\n%s" % [
|
||||||
|
|
@ -355,7 +351,7 @@ func _build_active_rows() -> void:
|
||||||
entry.compact_fingerprint,
|
entry.compact_fingerprint,
|
||||||
entry.continuity_state,
|
entry.continuity_state,
|
||||||
]
|
]
|
||||||
identity.tooltip_text = "Full identity fingerprint:\n%s" % (
|
identity.tooltip_text = NetworkIdentityCrypto.format_fingerprint(
|
||||||
entry.full_fingerprint
|
entry.full_fingerprint
|
||||||
)
|
)
|
||||||
identity.add_theme_color_override(
|
identity.add_theme_color_override(
|
||||||
|
|
@ -385,12 +381,6 @@ func _build_active_rows() -> void:
|
||||||
block.pressed.connect(_confirm_block.bind(entry))
|
block.pressed.connect(_confirm_block.bind(entry))
|
||||||
UtilityPageStyle.apply_ocean_button(block)
|
UtilityPageStyle.apply_ocean_button(block)
|
||||||
row.add_child(block)
|
row.add_child(block)
|
||||||
if entry.can_manage_operator:
|
|
||||||
var operator := Button.new()
|
|
||||||
operator.text = "deop" if entry.is_operator else "op"
|
|
||||||
operator.pressed.connect(_confirm_operator.bind(entry))
|
|
||||||
UtilityPageStyle.apply_ocean_button(operator)
|
|
||||||
row.add_child(operator)
|
|
||||||
var kick := Button.new()
|
var kick := Button.new()
|
||||||
kick.text = "kick"
|
kick.text = "kick"
|
||||||
kick.disabled = not entry.can_kick
|
kick.disabled = not entry.can_kick
|
||||||
|
|
@ -549,24 +539,6 @@ func _confirm_ban(entry: PlayerListEntry) -> void:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func _confirm_operator(entry: PlayerListEntry) -> void:
|
|
||||||
var enabled: bool = not entry.is_operator
|
|
||||||
_confirm(
|
|
||||||
(
|
|
||||||
"Grant operator access to %s for this room session?"
|
|
||||||
if enabled
|
|
||||||
else "Remove operator access from %s?"
|
|
||||||
) % entry.display_name,
|
|
||||||
func() -> void:
|
|
||||||
_service.set_operator(
|
|
||||||
entry.peer_id,
|
|
||||||
entry.full_fingerprint,
|
|
||||||
enabled,
|
|
||||||
entry.revision,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _confirm_unban(fingerprint: String) -> void:
|
func _confirm_unban(fingerprint: String) -> void:
|
||||||
_confirm("Unban %s?" % NetworkIdentityCrypto.compact_suffix(fingerprint), func() -> void:
|
_confirm("Unban %s?" % NetworkIdentityCrypto.compact_suffix(fingerprint), func() -> void:
|
||||||
_service.unban(fingerprint)
|
_service.unban(fingerprint)
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,6 @@ func _ready() -> void:
|
||||||
%ChangeDataFolder.pressed.connect(_choose_data_folder)
|
%ChangeDataFolder.pressed.connect(_choose_data_folder)
|
||||||
%ExportPlayerIdentity.pressed.connect(_choose_identity_export.bind("player"))
|
%ExportPlayerIdentity.pressed.connect(_choose_identity_export.bind("player"))
|
||||||
%ImportPlayerIdentity.pressed.connect(_choose_identity_import.bind("player"))
|
%ImportPlayerIdentity.pressed.connect(_choose_identity_import.bind("player"))
|
||||||
%CopyPlayerFingerprint.pressed.connect(_copy_player_fingerprint)
|
|
||||||
%ExportHostIdentity.pressed.connect(_choose_identity_export.bind("host"))
|
%ExportHostIdentity.pressed.connect(_choose_identity_export.bind("host"))
|
||||||
%ImportHostIdentity.pressed.connect(_choose_identity_import.bind("host"))
|
%ImportHostIdentity.pressed.connect(_choose_identity_import.bind("host"))
|
||||||
for index: int in _world_options.size():
|
for index: int in _world_options.size():
|
||||||
|
|
@ -486,16 +485,6 @@ func _refresh_data_page() -> void:
|
||||||
_data_root.override_active
|
_data_root.override_active
|
||||||
or (_network_session != null and _network_session.is_session_active())
|
or (_network_session != null and _network_session.is_session_active())
|
||||||
)
|
)
|
||||||
var fingerprint: String = (
|
|
||||||
_player_identity.fingerprint if _player_identity != null else ""
|
|
||||||
)
|
|
||||||
%CopyPlayerFingerprint.disabled = not NetworkIdentityCrypto.valid_fingerprint(
|
|
||||||
fingerprint
|
|
||||||
)
|
|
||||||
%CopyPlayerFingerprint.text = (
|
|
||||||
"Copy Player Fingerprint · %s"
|
|
||||||
% NetworkIdentityCrypto.compact_suffix(fingerprint)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _open_data_folder() -> void:
|
func _open_data_folder() -> void:
|
||||||
|
|
@ -503,17 +492,6 @@ func _open_data_folder() -> void:
|
||||||
_feedback.text = "Could not open the data folder."
|
_feedback.text = "Could not open the data folder."
|
||||||
|
|
||||||
|
|
||||||
func _copy_player_fingerprint() -> void:
|
|
||||||
var fingerprint: String = (
|
|
||||||
_player_identity.fingerprint if _player_identity != null else ""
|
|
||||||
)
|
|
||||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
|
||||||
_feedback.text = "Player identity is unavailable."
|
|
||||||
return
|
|
||||||
DisplayServer.clipboard_set(fingerprint)
|
|
||||||
_feedback.text = "Full player fingerprint copied for server operator setup."
|
|
||||||
|
|
||||||
|
|
||||||
func _choose_data_folder() -> void:
|
func _choose_data_folder() -> void:
|
||||||
if _data_root == null or _data_root.override_active:
|
if _data_root == null or _data_root.override_active:
|
||||||
_feedback.text = "The data folder is externally managed."
|
_feedback.text = "The data folder is externally managed."
|
||||||
|
|
|
||||||
|
|
@ -913,11 +913,6 @@ layout_mode = 2
|
||||||
text = "Active identity keys stay on this device.\nEncrypted backups can be stored in your synced data folder.\nDo not play the same profile on two devices at the same time."
|
text = "Active identity keys stay on this device.\nEncrypted backups can be stored in your synced data folder.\nDo not play the same profile on two devices at the same time."
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
|
|
||||||
[node name="CopyPlayerFingerprint" type="Button" parent="DataPage/Paper/Content"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Copy Player Fingerprint"
|
|
||||||
|
|
||||||
[node name="IdentityGrid" type="GridContainer" parent="DataPage/Paper/Content"]
|
[node name="IdentityGrid" type="GridContainer" parent="DataPage/Paper/Content"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
columns = 2
|
columns = 2
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue