Add synchronized Fishnet jobs and laptop UI
This commit is contained in:
parent
46df74361a
commit
26fbd41132
39 changed files with 3257 additions and 188 deletions
|
|
@ -28,6 +28,7 @@ var _persistent_time_hours: float = DEFAULT_START_HOUR
|
|||
var _persistence_tracking_enabled: bool = false
|
||||
var _running: bool = false
|
||||
var _phase: Phase = Phase.DAWN
|
||||
var _last_emitted_clock_minute: int = floori(DEFAULT_START_HOUR * 60.0)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -140,14 +141,18 @@ func _set_time_hours(time_hours: float, force_emit: bool) -> void:
|
|||
var normalized: float = _normalized_hour(time_hours)
|
||||
var next_phase: Phase = phase_for_hour(normalized)
|
||||
var phase_was_changed: bool = next_phase != _phase
|
||||
var time_was_changed: bool = not is_equal_approx(normalized, _time_hours)
|
||||
var clock_minute: int = floori(normalized * 60.0)
|
||||
var clock_minute_changed: bool = (
|
||||
clock_minute != _last_emitted_clock_minute
|
||||
)
|
||||
_time_hours = normalized
|
||||
_phase = next_phase
|
||||
if _persistence_tracking_enabled:
|
||||
_persistent_time_hours = normalized
|
||||
if phase_was_changed:
|
||||
phase_changed.emit(_phase)
|
||||
if force_emit or time_was_changed or phase_was_changed:
|
||||
if force_emit or clock_minute_changed or phase_was_changed:
|
||||
_last_emitted_clock_minute = clock_minute
|
||||
time_changed.emit(_time_hours, _phase)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ const UPDATE_INTERVAL_SECONDS: float = 0.1
|
|||
const SUN_YAW_DEGREES: float = -32.0
|
||||
const WEATHER_TRANSITION_SECONDS: float = 10.0
|
||||
const RAIN_EMITTER_OFFSET := Vector3(0.0, 7.0, 0.0)
|
||||
const RAIN_PARTICLE_AMOUNT: int = 560
|
||||
const RAIN_VELOCITY_MIN: float = 16.0
|
||||
const RAIN_VELOCITY_MAX: float = 20.0
|
||||
const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014)
|
||||
|
||||
const DAY_SKY_TOP := Color(0.204, 0.498, 0.643)
|
||||
const DAY_SKY_HORIZON := Color(0.663, 0.843, 0.847)
|
||||
|
|
@ -145,7 +149,7 @@ func _prepare_runtime_environment() -> bool:
|
|||
func _prepare_rain() -> void:
|
||||
_rain = GPUParticles3D.new()
|
||||
_rain.name = "LocalRain"
|
||||
_rain.amount = 480
|
||||
_rain.amount = RAIN_PARTICLE_AMOUNT
|
||||
_rain.amount_ratio = 0.0
|
||||
_rain.lifetime = 1.25
|
||||
_rain.fixed_fps = 30
|
||||
|
|
@ -160,12 +164,12 @@ func _prepare_rain() -> void:
|
|||
process_material.emission_box_extents = Vector3(6.5, 1.0, 6.5)
|
||||
process_material.direction = Vector3.DOWN
|
||||
process_material.spread = 5.0
|
||||
process_material.initial_velocity_min = 11.0
|
||||
process_material.initial_velocity_max = 15.0
|
||||
process_material.initial_velocity_min = RAIN_VELOCITY_MIN
|
||||
process_material.initial_velocity_max = RAIN_VELOCITY_MAX
|
||||
process_material.gravity = Vector3(0.0, -2.0, 0.0)
|
||||
_rain.process_material = process_material
|
||||
var drop_mesh := BoxMesh.new()
|
||||
drop_mesh.size = Vector3(0.018, 0.42, 0.018)
|
||||
drop_mesh.size = RAIN_DROP_SIZE
|
||||
var drop_material := StandardMaterial3D.new()
|
||||
drop_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
drop_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
|
|
|
|||
|
|
@ -15,11 +15,21 @@ const SUNNY_DURATION_RANGE := Vector2(480.0, 900.0)
|
|||
const CLOUDY_DURATION_RANGE := Vector2(300.0, 720.0)
|
||||
const RAINY_DURATION_RANGE := Vector2(300.0, 600.0)
|
||||
const FOGGY_DURATION_RANGE := Vector2(300.0, 600.0)
|
||||
const MAX_PERSISTED_SECONDS: float = 1800.0
|
||||
const DAILY_PLAN_SEGMENT_HOURS: float = 2.0
|
||||
const DAILY_PLAN_SEGMENT_COUNT: int = 12
|
||||
|
||||
var _weather: Weather = DEFAULT_WEATHER
|
||||
var _seconds_remaining: float = SUNNY_DURATION_RANGE.x
|
||||
var _persistent_weather: Weather = DEFAULT_WEATHER
|
||||
var _persistent_seconds_remaining: float = SUNNY_DURATION_RANGE.x
|
||||
var _has_persistent_state: bool = false
|
||||
var _persistence_tracking_enabled: bool = false
|
||||
var _running_authority: bool = false
|
||||
var _rng := RandomNumberGenerator.new()
|
||||
var _daily_plan_id: String = ""
|
||||
var _daily_schedule: Array[Dictionary] = []
|
||||
var _world_time: WorldTimeService
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -34,7 +44,21 @@ func begin_authoritative_session(seed_value: int) -> void:
|
|||
_rng.seed = seed_value
|
||||
_running_authority = true
|
||||
set_process(true)
|
||||
_set_weather(DEFAULT_WEATHER, _roll_duration(DEFAULT_WEATHER), true)
|
||||
if _daily_schedule.is_empty() or _world_time == null:
|
||||
if _has_persistent_state:
|
||||
_set_weather(
|
||||
_persistent_weather,
|
||||
_persistent_seconds_remaining,
|
||||
true,
|
||||
)
|
||||
else:
|
||||
_set_weather(
|
||||
DEFAULT_WEATHER,
|
||||
_roll_duration(DEFAULT_WEATHER),
|
||||
true,
|
||||
)
|
||||
else:
|
||||
_update_scheduled_weather(true)
|
||||
|
||||
|
||||
func begin_remote_session() -> void:
|
||||
|
|
@ -52,12 +76,17 @@ func end_session() -> void:
|
|||
func advance_weather(real_seconds: float) -> void:
|
||||
if not _running_authority or real_seconds <= 0.0:
|
||||
return
|
||||
if not _daily_schedule.is_empty() and _world_time != null:
|
||||
_update_scheduled_weather(false)
|
||||
return
|
||||
_seconds_remaining -= real_seconds
|
||||
while _seconds_remaining <= 0.0:
|
||||
var overrun: float = -_seconds_remaining
|
||||
var next_weather: Weather = _choose_next_weather()
|
||||
_set_weather(next_weather, _roll_duration(next_weather), true)
|
||||
_seconds_remaining -= overrun
|
||||
if _persistence_tracking_enabled:
|
||||
_store_persistent_state()
|
||||
|
||||
|
||||
func apply_authoritative_snapshot(
|
||||
|
|
@ -69,6 +98,56 @@ func apply_authoritative_snapshot(
|
|||
_set_weather(weather, maxf(seconds_remaining, 0.0), false)
|
||||
|
||||
|
||||
func set_persistence_tracking_enabled(enabled: bool) -> void:
|
||||
if _persistence_tracking_enabled == enabled:
|
||||
return
|
||||
if _persistence_tracking_enabled:
|
||||
_store_persistent_state()
|
||||
_persistence_tracking_enabled = enabled
|
||||
|
||||
|
||||
func restore_persistent_state(
|
||||
weather: Weather,
|
||||
seconds_remaining: float,
|
||||
) -> bool:
|
||||
if not is_valid_persistent_state(weather, seconds_remaining):
|
||||
return false
|
||||
_persistent_weather = weather
|
||||
_persistent_seconds_remaining = seconds_remaining
|
||||
_has_persistent_state = true
|
||||
if _persistence_tracking_enabled:
|
||||
_set_weather(
|
||||
_persistent_weather,
|
||||
_persistent_seconds_remaining,
|
||||
true,
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func reset_persistent_state() -> void:
|
||||
_has_persistent_state = false
|
||||
_persistent_weather = DEFAULT_WEATHER
|
||||
_persistent_seconds_remaining = SUNNY_DURATION_RANGE.x
|
||||
if _persistence_tracking_enabled and _running_authority:
|
||||
_set_weather(
|
||||
DEFAULT_WEATHER,
|
||||
_roll_duration(DEFAULT_WEATHER),
|
||||
true,
|
||||
)
|
||||
|
||||
|
||||
func has_persistent_state() -> bool:
|
||||
return _has_persistent_state
|
||||
|
||||
|
||||
func get_persistent_weather() -> Weather:
|
||||
return _persistent_weather
|
||||
|
||||
|
||||
func get_persistent_seconds_remaining() -> float:
|
||||
return _persistent_seconds_remaining
|
||||
|
||||
|
||||
func get_weather() -> Weather:
|
||||
return _weather
|
||||
|
||||
|
|
@ -89,10 +168,95 @@ func get_weather_name() -> String:
|
|||
return weather_name(_weather)
|
||||
|
||||
|
||||
func configure_daily_plan(
|
||||
plan_id: String,
|
||||
schedule: Array,
|
||||
world_time: WorldTimeService,
|
||||
) -> bool:
|
||||
if (
|
||||
plan_id.is_empty()
|
||||
or not is_valid_daily_plan_schedule(schedule)
|
||||
or world_time == null
|
||||
):
|
||||
return false
|
||||
_daily_plan_id = plan_id
|
||||
_daily_schedule.clear()
|
||||
for value: Variant in schedule:
|
||||
_daily_schedule.append((value as Dictionary).duplicate(true))
|
||||
_world_time = world_time
|
||||
if _running_authority:
|
||||
_update_scheduled_weather(true)
|
||||
return true
|
||||
|
||||
|
||||
func clear_daily_plan() -> void:
|
||||
_daily_plan_id = ""
|
||||
_daily_schedule.clear()
|
||||
_world_time = null
|
||||
|
||||
|
||||
func get_daily_plan_id() -> String:
|
||||
return _daily_plan_id
|
||||
|
||||
|
||||
static func is_valid_weather(value: int) -> bool:
|
||||
return value >= Weather.SUNNY and value <= Weather.FOGGY
|
||||
|
||||
|
||||
static func is_valid_persistent_state(
|
||||
weather: Weather,
|
||||
seconds_remaining: float,
|
||||
) -> bool:
|
||||
return (
|
||||
is_valid_weather(int(weather))
|
||||
and is_finite(seconds_remaining)
|
||||
and seconds_remaining >= 0.0
|
||||
and seconds_remaining <= MAX_PERSISTED_SECONDS
|
||||
)
|
||||
|
||||
|
||||
static func is_valid_daily_plan_schedule(value: Variant) -> bool:
|
||||
if typeof(value) != TYPE_ARRAY:
|
||||
return false
|
||||
var schedule: Array = value
|
||||
if schedule.size() != DAILY_PLAN_SEGMENT_COUNT:
|
||||
return false
|
||||
for entry_value: Variant in schedule:
|
||||
if typeof(entry_value) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var entry: Dictionary = entry_value
|
||||
if (
|
||||
typeof(entry.get("start_hour")) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or not is_finite(float(entry.get("start_hour", -1.0)))
|
||||
or float(entry.get("start_hour", -1.0)) < 0.0
|
||||
or float(entry.get("start_hour", -1.0)) >= 24.0
|
||||
or not _is_bounded_integer(
|
||||
entry.get("weather"), Weather.SUNNY, Weather.FOGGY
|
||||
)
|
||||
or not is_valid_weather(int(entry.get("weather", -1)))
|
||||
):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
static func _is_bounded_integer(
|
||||
value: Variant,
|
||||
minimum: int,
|
||||
maximum: int,
|
||||
) -> bool:
|
||||
if typeof(value) == TYPE_INT:
|
||||
return int(value) >= minimum and int(value) <= maximum
|
||||
if typeof(value) != TYPE_FLOAT:
|
||||
return false
|
||||
var number: float = float(value)
|
||||
return (
|
||||
is_finite(number)
|
||||
and number >= float(minimum)
|
||||
and number <= float(maximum)
|
||||
and is_equal_approx(number, round(number))
|
||||
)
|
||||
|
||||
|
||||
static func weather_name(weather: Weather) -> String:
|
||||
match weather:
|
||||
Weather.SUNNY:
|
||||
|
|
@ -114,10 +278,18 @@ func _set_weather(
|
|||
var changed: bool = weather != _weather
|
||||
_weather = weather
|
||||
_seconds_remaining = maxf(seconds_remaining, 0.0)
|
||||
if _persistence_tracking_enabled:
|
||||
_store_persistent_state()
|
||||
if force_emit or changed:
|
||||
weather_changed.emit(_weather, _seconds_remaining)
|
||||
|
||||
|
||||
func _store_persistent_state() -> void:
|
||||
_persistent_weather = _weather
|
||||
_persistent_seconds_remaining = _seconds_remaining
|
||||
_has_persistent_state = true
|
||||
|
||||
|
||||
func _choose_next_weather() -> Weather:
|
||||
match _weather:
|
||||
Weather.SUNNY:
|
||||
|
|
@ -150,3 +322,26 @@ func _duration_range(weather: Weather) -> Vector2:
|
|||
Weather.FOGGY:
|
||||
return FOGGY_DURATION_RANGE
|
||||
return SUNNY_DURATION_RANGE
|
||||
|
||||
|
||||
func _update_scheduled_weather(force_emit: bool) -> void:
|
||||
if _daily_schedule.is_empty() or _world_time == null:
|
||||
return
|
||||
var elapsed_hours: float = fposmod(
|
||||
_world_time.get_time_hours() - WorldTimeService.DAY_START_HOUR,
|
||||
WorldTimeService.HOURS_PER_DAY,
|
||||
)
|
||||
var segment_hours: float = DAILY_PLAN_SEGMENT_HOURS
|
||||
var segment_index: int = clampi(
|
||||
floori(elapsed_hours / segment_hours),
|
||||
0,
|
||||
_daily_schedule.size() - 1,
|
||||
)
|
||||
var entry: Dictionary = _daily_schedule[segment_index]
|
||||
var weather: Weather = int(entry.get("weather", DEFAULT_WEATHER)) as Weather
|
||||
var next_boundary: float = float(segment_index + 1) * segment_hours
|
||||
var hours_remaining: float = maxf(next_boundary - elapsed_hours, 0.0)
|
||||
var seconds_remaining: float = (
|
||||
hours_remaining / WorldTimeService.HOURS_PER_REAL_SECOND
|
||||
)
|
||||
_set_weather(weather, seconds_remaining, force_emit)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue