P07-01-WX: 仕上がった天候の機能の受入:自然天候・雨・嵐・世界樹と成人客のマナ霧・上部の表示と予報・保存と移行(マナ炉の産出 120 を除く。P07-01 の補助の作業。原単位への加算 0)
This commit is contained in:
@@ -37,6 +37,7 @@ const CleaningBridge = preload("res://scripts/environment/facility_cleaning_brid
|
||||
const CleaningPolicy = preload("res://scripts/environment/facility_cleaning_policy.gd")
|
||||
const UPKEEP_RUNTIME_SNAPSHOT_VERSION := 10
|
||||
const WeatherTimeline = preload("res://scripts/environment/weather_timeline.gd")
|
||||
const NaturalWeather = preload("res://scripts/environment/natural_weather_policy.gd")
|
||||
const STAIRS_RUNTIME_SNAPSHOT_VERSION := 9
|
||||
const TRANSPORT_RUNTIME_SNAPSHOT_VERSION := 8
|
||||
const SUPPLY_RUNTIME_SNAPSHOT_VERSION := 7
|
||||
@@ -85,6 +86,7 @@ const RNG_STREAM_IDS: Array[StringName] = [
|
||||
&"defense",
|
||||
&"event",
|
||||
&"loot",
|
||||
&"weather",
|
||||
]
|
||||
const DEFENSE_COMMAND_TYPES: Array[StringName] = [
|
||||
&"create_defense_raid",
|
||||
@@ -463,6 +465,8 @@ func initialize(clock: SimulationKernel, state: Variant, seed := DEFAULT_WORLD_S
|
||||
if not report[&"valid"]:
|
||||
_clock.set_paused(true)
|
||||
definition_validation_failed.emit(report)
|
||||
# A13: a new game opening at 06:00 draws its first day once streams exist.
|
||||
_draw_natural_weather_if_due(_clock.absolute_minute)
|
||||
_publish_snapshot(_clock.absolute_minute)
|
||||
return report
|
||||
|
||||
@@ -1291,6 +1295,15 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
return false
|
||||
elif runtime_data.has(&"weather") or not restored_weather.initialize(&"clear", restored_minute):
|
||||
return false
|
||||
# A13 rain holds must still describe a present guest's extended plan.
|
||||
var rain_holds := restored_weather.get_rain_holds()
|
||||
for held_key: Variant in rain_holds:
|
||||
var held_guest := restored_guests.get_guest(StringName(held_key))
|
||||
if held_guest == null or held_guest.planned_exit_at != int(rain_holds[held_key]["extended_to"]):
|
||||
return false
|
||||
var saved_weather: Variant = runtime_data.get(&"weather", null)
|
||||
var weather_stream_required: bool = saved_weather is Dictionary \
|
||||
and int(saved_weather.get("version", 0)) >= WeatherTimeline.VERSION
|
||||
var restored_cleanliness := FacilityCleanliness.new()
|
||||
if runtime_schema_version >= CLEANING_RUNTIME_SNAPSHOT_VERSION:
|
||||
if not runtime_data.get(&"facility_cleanliness") is Dictionary or not restored_cleanliness.restore_snapshot(runtime_data[&"facility_cleanliness"]):
|
||||
@@ -1323,6 +1336,12 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
if has_defense_snapshot:
|
||||
return false
|
||||
restored_streams[&"defense"] = DeterministicRng.from_root_seed(restored_world_seed, &"defense")
|
||||
# A13 migration: saves before natural weather start the weather stream from
|
||||
# the world seed, as defense did (SPEC section 5.2).
|
||||
if not restored_streams.has(&"weather"):
|
||||
if weather_stream_required:
|
||||
return false
|
||||
restored_streams[&"weather"] = DeterministicRng.from_root_seed(restored_world_seed, &"weather")
|
||||
if runtime_schema_version >= DEFENSE_RUNTIME_SNAPSHOT_VERSION:
|
||||
for required_stream_id: StringName in RNG_STREAM_IDS:
|
||||
if not restored_streams.has(required_stream_id):
|
||||
@@ -2951,6 +2970,8 @@ func _operations_arrival_boundary(
|
||||
&"ok": not captured.is_empty(),
|
||||
&"token": captured,
|
||||
&"capacity_remaining": maxi(population_cap - guest_service.guest_count(), 0) if int(_state.get_resource(&"mana")) > 0 and weather.permits_new_arrivals() else 0,
|
||||
# A13: rain halves the planned new arrivals of this boundary.
|
||||
&"arrival_percent": NaturalWeather.arrival_percent(weather.get_kind()),
|
||||
}
|
||||
if phase == OperationsCoordinator.PHASE_VALIDATE:
|
||||
return {
|
||||
@@ -8784,6 +8805,7 @@ func _advance_guest_vitals(absolute_minute: int) -> void:
|
||||
&"is_night": minute_of_day < 360 or minute_of_day >= 1_080,
|
||||
&"intimacy_day": 3,
|
||||
&"intimacy_night": 7,
|
||||
&"mana_fog": weather.get_kind() == &"mana_fog",
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9035,7 +9057,7 @@ func _advance_guest_ai_decisions(absolute_minute: int) -> void:
|
||||
GuestRuntime.State.ENTERING:
|
||||
guest_service.transition_guest(guest.guest_id, GuestRuntime.State.DECIDING, absolute_minute, &"ENTRY_COMPLETED")
|
||||
GuestRuntime.State.DECIDING:
|
||||
if guest.planned_exit_at <= absolute_minute:
|
||||
if guest.planned_exit_at <= absolute_minute and not _hold_guest_for_rain(guest, absolute_minute):
|
||||
guest_service.begin_leaving(guest.guest_id, absolute_minute, &"PLANNED_EXIT")
|
||||
continue
|
||||
guest.wallet_gold = economy_service.get_balance(_guest_wallet_account(guest.guest_id))
|
||||
@@ -10993,13 +11015,14 @@ func _reservation_references_are_valid(
|
||||
## HUD view of the A06 reserve: supply, consumption and storage per interval.
|
||||
func get_mana_reserve_report() -> Dictionary:
|
||||
var demand := _mana_reserve_demand()
|
||||
var generation := NaturalWeather.mana_yield(ManaReserveCycle.WORLD_TREE_GENERATION, weather.get_kind())
|
||||
return {
|
||||
&"amount": int(_state.get_resource(&"mana")),
|
||||
&"capacity": ManaReserveCycle.CAPACITY,
|
||||
&"generation": ManaReserveCycle.WORLD_TREE_GENERATION,
|
||||
&"generation": generation,
|
||||
&"interval_minutes": ManaReserveCycle.INTERVAL_MINUTES,
|
||||
&"demand": demand,
|
||||
&"net": ManaReserveCycle.WORLD_TREE_GENERATION - demand,
|
||||
&"net": generation - demand,
|
||||
&"last_flow": mana_reserve.get_last_flow(),
|
||||
}
|
||||
|
||||
@@ -11032,8 +11055,10 @@ func _advance_mana(absolute_minute: int) -> void:
|
||||
return
|
||||
# A06: generation and upkeep demand settle before local distribution and the
|
||||
# whole-tower gate, so the network and projection see the committed reserve.
|
||||
# A13: mana fog raises the world tree yield of this boundary to x1.2 (floor).
|
||||
var reserve_flow := mana_reserve.advance_to(
|
||||
absolute_minute, int(_state.get_resource(&"mana")), _mana_reserve_demand()
|
||||
absolute_minute, int(_state.get_resource(&"mana")), _mana_reserve_demand(),
|
||||
NaturalWeather.mana_yield(ManaReserveCycle.WORLD_TREE_GENERATION, weather.get_kind())
|
||||
)
|
||||
if not bool(reserve_flow.get(&"ok", false)):
|
||||
_latch_infrastructure_failure(
|
||||
@@ -13572,10 +13597,66 @@ func get_staff_rest_report(staff_id: StringName) -> Dictionary:
|
||||
|
||||
func _advance_weather(absolute_minute: int) -> void:
|
||||
weather.advance_to(absolute_minute)
|
||||
_draw_natural_weather_if_due(absolute_minute)
|
||||
if not weather.get_rain_holds().is_empty():
|
||||
var present := {}
|
||||
for guest: GuestRuntime in guest_service.get_guests():
|
||||
present[guest.guest_id] = true
|
||||
weather.prune_rain_holds(present)
|
||||
|
||||
|
||||
## A13: one draw per day at 06:00 from the saved weather stream. The weather
|
||||
## boundary runs first, so the same minute's arrivals and closes see the result.
|
||||
func _draw_natural_weather_if_due(absolute_minute: int) -> void:
|
||||
if not NaturalWeather.is_draw_minute(absolute_minute) or weather.get_minute() != absolute_minute:
|
||||
return
|
||||
var day := NaturalWeather.day_of(absolute_minute)
|
||||
var stream := get_rng_stream(&"weather")
|
||||
if day <= weather.get_last_draw_day() or stream == null:
|
||||
return
|
||||
var draw := NaturalWeather.draw(stream, day)
|
||||
var applied := weather.apply_draw(draw)
|
||||
var no_entities: Array[StringName] = []
|
||||
_publish_actor_event(&"WEATHER_FORECAST_DRAWN", &"weather", no_entities, {
|
||||
&"day": day,
|
||||
&"kind": String(draw[&"kind"]),
|
||||
&"roll": int(draw[&"roll"]),
|
||||
&"slot": int(draw[&"slot"]),
|
||||
&"start": int(draw[&"start"]),
|
||||
&"end": int(draw[&"end"]),
|
||||
&"applied": bool(applied.get(&"ok", false)),
|
||||
&"code": String(applied.get(&"code", &"")),
|
||||
})
|
||||
|
||||
|
||||
## A13: a guest due to leave while it rains stays until the rain ends, at most
|
||||
## 240 minutes after the original plan, once per guest (SPEC section 4.3).
|
||||
func _hold_guest_for_rain(guest: GuestRuntime, absolute_minute: int) -> bool:
|
||||
if weather.get_kind() != &"rain" or not weather.get_rain_hold(guest.guest_id).is_empty():
|
||||
return false
|
||||
var original := guest.planned_exit_at
|
||||
var extended := original + NaturalWeather.RAIN_HOLD_MAX_MINUTES
|
||||
var rain_end := weather.rain_end_minute()
|
||||
if rain_end >= 0:
|
||||
extended = mini(extended, rain_end)
|
||||
if extended <= absolute_minute or not weather.add_rain_hold(guest.guest_id, original, extended):
|
||||
return false
|
||||
guest.planned_exit_at = extended
|
||||
return true
|
||||
|
||||
|
||||
func get_weather_report() -> Dictionary:
|
||||
return {&"kind": weather.get_kind(), &"minute": weather.get_minute()}
|
||||
var kind := weather.get_kind()
|
||||
return {
|
||||
&"kind": kind,
|
||||
&"minute": weather.get_minute(),
|
||||
&"since": weather.get_since(),
|
||||
&"next_change": weather.next_change(),
|
||||
&"today_draw": weather.get_today_draw(),
|
||||
&"effect_text": NaturalWeather.effect_text(kind),
|
||||
&"arrival_percent": NaturalWeather.arrival_percent(kind),
|
||||
&"rain_holds": weather.get_rain_holds().size(),
|
||||
}
|
||||
|
||||
|
||||
## Read-only migration preview; applying dimensions remains a separate transaction.
|
||||
|
||||
Reference in New Issue
Block a user