class_name FacilityPricing extends RefCounted ## Shared integer pricing rules for quotations, guest outcomes and management UI. ## Unrelated policy keys remain the responsibility of their owning systems. const DEFAULT_MODE := &"standard" const MODES: Array[Dictionary] = [ {&"id": &"charitable", &"label": "奉仕価格", &"percent": 60, &"satisfaction_delta": 10}, {&"id": &"affordable", &"label": "安価", &"percent": 80, &"satisfaction_delta": 5}, {&"id": &"standard", &"label": "標準", &"percent": 100, &"satisfaction_delta": 0}, {&"id": &"expensive", &"label": "高価", &"percent": 150, &"satisfaction_delta": -5}, {&"id": &"exorbitant", &"label": "ぼったくり", &"percent": 250, &"satisfaction_delta": -20}, ] static func modes() -> Array[Dictionary]: return MODES.duplicate(true) static func quote(base_price: int, policy: Dictionary) -> int: var percent := int(_mode(policy)[&"percent"]) # Currency is integer gold. An exact half gold rounds up, without depending # on floating-point rounding (35 G at 150 percent is 53 G). @warning_ignore("integer_division") var result := (maxi(base_price, 0) * percent + 50) / 100 return result static func satisfaction_modifier( policy: Dictionary, quality: int, expected_quality: int ) -> int: var modifier := int(_mode(policy)[&"satisfaction_delta"]) # GAME_SPECIFICATION 7.4 includes a difference of exactly twenty points. if modifier < 0 and quality - expected_quality >= 20: # Keep the same nearest-integer rule for a half penalty: -2.5 becomes -3. @warning_ignore("integer_division") return -((-modifier + 1) / 2) return modifier static func validate_patch(patch: Dictionary) -> Dictionary: if _has(patch, &"price_mode"): var mode: Variant = _value(patch, &"price_mode", DEFAULT_MODE) if not (mode is String or mode is StringName) or not _known_mode(StringName(mode)): return _invalid(&"INVALID_PRICE_MODE", "価格設定は5段階のいずれかを選んでください。") if _has(patch, &"business_open") and not _value(patch, &"business_open", true) is bool: return _invalid(&"INVALID_BUSINESS_OPEN", "営業状態は営業中または休業を指定してください。") var normalized: Dictionary = {} for raw_key: Variant in patch: if not (raw_key is String or raw_key is StringName): return _invalid(&"INVALID_POLICY_KEY", "施設方針の項目名が正しくありません。") var value: Variant = patch[raw_key] normalized[StringName(raw_key)] = value.duplicate(true) if value is Array or value is Dictionary else value if normalized.has(&"price_mode"): normalized[&"price_mode"] = StringName(normalized[&"price_mode"]) return {&"valid": true, &"reason": &"", &"message": "", &"patch": normalized} static func _mode(policy: Dictionary) -> Dictionary: var value: Variant = _value(policy, &"price_mode", DEFAULT_MODE) if value is String or value is StringName: for mode: Dictionary in MODES: if mode[&"id"] == StringName(value): return mode return MODES[2] static func _known_mode(mode_id: StringName) -> bool: for mode: Dictionary in MODES: if mode[&"id"] == mode_id: return true return false static func _invalid(reason: StringName, message: String) -> Dictionary: return {&"valid": false, &"reason": reason, &"message": message, &"patch": {}} static func _has(source: Dictionary, key: StringName) -> bool: return source.has(key) or source.has(String(key)) static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant: return source.get(key, source.get(String(key), fallback))