using UnityEngine;
namespace ServiceSim
{
///
/// 店の回し方の数字。試算(haizen_sim.py の COMMON と STORES["family"])と同じ値にそろえる。
/// 店の運営に関する数字はすべてこの .asset に置く。
///
/// 三角分布は(いちばん短い, いちばん多い, いちばん長い)の分(min)で持つ。
///
[CreateAssetMenu(menuName = "ServiceSim/Service Spec", fileName = "ServiceSpec")]
public class ServiceSpec : ScriptableObject
{
[Header("来客(STORES[\"family\"][\"arrivals\"])")]
[Tooltip("混み具合ごとの来客のペース(組/時)。[空いている, 普通, 混雑]")]
public int[] arrivalsPerHourByCrowd = { 25, 35, 45 };
[Tooltip("ピークの長さ(分)。1回の実行の長さ(STORES[\"family\"][\"peak_min\"])")]
public float peakMinutes = 120f;
[Header("サイトの試算から読む値(v5 3章。SimParams が回すときだけ入れる)")]
[Tooltip("その条件の来客のペース(組/時)。unity_conditions の arrivals_per_h。" +
"0 なら、これまでどおり arrivalsPerHourByCrowd(混み具合)を使う")]
public int arrivalsPerHour;
[Tooltip("動けなくなったロボットを、スタッフが動かす時間(秒)(common.rescue_s)")]
public float rescueSec = 15f;
[Tooltip("来客が終わったあと、全員が帰るのを待つ上限(分)(common.max_extra_min)")]
public float maxExtraMin = 600f;
[Header("組(Unity だけの値。試算に人数はない)")]
[Tooltip("組の人数を選ぶ重み。[1人, 2人, 3人, 4人]")]
public float[] groupSizeWeights = { 0.20f, 0.35f, 0.20f, 0.25f };
[Header("案内・会計・呼び出し(COMMON)")]
[Tooltip("案内にかかる時間(秒)。歩く時間は別(guide_s)")]
public float guideSec = 45f;
[Tooltip("会計の時間(秒)(pay_s)")]
public float paySec = 90f;
[Tooltip("呼び出し対応の時間(秒)。歩く時間は別(call_s)")]
public float callSec = 30f;
[Tooltip("レジの前の人数を数える半径(m)。記録だけに使う")]
public float registerCrowdRadiusM = 2.0f;
[Tooltip("会計はレジに立つ組の1人だけで済ませる(ほかの人は席で待つ)。" +
"外すと前のとおり、誰もレジに行かずスタッフだけで会計する(前後を比べるときに使う)")]
public bool payAtRegisterByOne = true;
[Tooltip("会計のとき、スタッフが立つ点からこれ以上離れていたら数える(m)。" +
"店員側に入れていないしるしなので、0 回になるはず")]
public float registerPayOffSpotM = 0.1f;
[Tooltip("1組あたりの呼び出しの起きやすさ(call_rate)。試算は2回ぶん 0.6/2 で判定")]
public float callRate = 0.6f;
[Header("料理(first_dishes・extra・fragile)")]
[Tooltip("最初の注文:1皿めができるまで(分, 最短/最頻/最長)")]
public Vector3 firstDish1CookMin = new Vector3(5f, 8f, 12f);
[Tooltip("最初の注文:2皿めができるまで(分, 最短/最頻/最長)")]
public Vector3 firstDish2CookMin = new Vector3(8f, 12f, 18f);
[Tooltip("食事中のデザート・ドリンクの数の平均(fragile)")]
public float fragilePerGroup = 0.5f;
[Tooltip("デザート・ドリンクができるまで(分, 最短/最頻/最長)(fragile_cook)")]
public Vector3 fragileCookMin = new Vector3(2f, 4f, 7f);
[Tooltip("デザート・ドリンクをロボットも運ぶか。false なら人だけ(fragile_by)")]
public bool fragileByRobot = true;
[Tooltip("これを超えて届いた皿を「待たせた皿」とする(分)(cold_min)")]
public float coldMin = 3f;
[Header("運ぶ(COMMON)")]
[Tooltip("ロボットが1走りで載せる料理の数(trays)。1 か 3")]
public int trays = 3;
[Tooltip("客がロボットから皿を取る時間(秒)(robot_handoff_s)")]
public float robotHandoffSec = 25f;
[Tooltip("スタッフが皿を置く時間(秒)(staff_place_s)")]
public float staffPlaceSec = 10f;
[Tooltip("料理ができてから、ロボットだけを待つ秒数(robot_first_wait_s)。" +
"このあいだスタッフは料理を持たず、案内・片付け・会計を続ける。" +
"過ぎたら、ロボットとスタッフのうち先に空いた方が運ぶ。" +
"0 にすると、はじめから先に空いた方が運ぶ(v2.7 より前と同じ)")]
public float robotFirstWaitSec = 120f;
[Header("食事(eat_min)")]
[Tooltip("食事の長さ(分, 最短/最頻/最長)")]
public Vector3 eatMin = new Vector3(20f, 30f, 45f);
[Tooltip("1人あたりドリンクバーへ行く平均の回数(Unity だけの値)")]
public float drinkTripsPerPerson = 1.5f;
[Tooltip("ドリンクバーで過ごす時間(秒)(Unity だけの値)")]
public float drinkBarSec = 20f;
[Tooltip("1組あたりトイレへ行く平均の回数(Unity だけの値)")]
public float toiletTripsPerGroup = 0.3f;
[Tooltip("トイレの中で過ごす時間(秒)(Unity だけの値)")]
public float toiletSec = 180f;
[Header("片付け(COMMON・bus_trips)")]
[Tooltip("卓の皿をまとめる時間(秒)(clear_s)")]
public float clearSec = 40f;
[Tooltip("卓を拭いてセットする時間(秒)(wipe_s)")]
public float wipeSec = 40f;
[Tooltip("下げ膳の往復回数(皿の量)(bus_trips)")]
public int busTrips = 2;
[Header("待合")]
[Tooltip("待合に立たせる組の上限。あふれた組は店の外で待つ扱い(人として置かない)")]
public int waitingVisibleMax = 8;
[Header("あきらめて、やり直す(B1)")]
[Tooltip("会計・片付けで止まって卓が空かない状態がこの秒数続いたら、卓を空ける")]
public float tableStuckFreeSec = 600f;
// ---- 読み出し ----
public int ArrivalsPerHour(CrowdLevel crowd)
{
// サイトの条件で回すときは、混み具合を使わずその値をそのまま使う(v5 3章)
if (arrivalsPerHour > 0) return arrivalsPerHour;
if (arrivalsPerHourByCrowd == null || arrivalsPerHourByCrowd.Length == 0) return 35;
int i = Mathf.Clamp((int)crowd, 0, arrivalsPerHourByCrowd.Length - 1);
return arrivalsPerHourByCrowd[i];
}
/// 三角分布(分)から秒を引く。試算の tri() と同じ
public static float TriangularSec(System.Random rng, Vector3 lowModeHigh)
{
float lo = lowModeHigh.x, mode = lowModeHigh.y, hi = lowModeHigh.z;
if (hi <= lo) return lo * 60f;
mode = Mathf.Clamp(mode, lo, hi);
double u = rng.NextDouble();
double c = (mode - lo) / (double)(hi - lo);
double v = u < c
? lo + System.Math.Sqrt(u * (hi - lo) * (mode - lo))
: hi - System.Math.Sqrt((1.0 - u) * (hi - lo) * (hi - mode));
return (float)v * 60f;
}
/// 指数分布(秒)。試算の expovariate と同じ
public static float ExponentialSec(System.Random rng, float ratePerSec)
{
if (ratePerSec <= 0f) return float.MaxValue;
double u = rng.NextDouble();
if (u <= 1e-12) u = 1e-12;
return (float)(-System.Math.Log(u) / ratePerSec);
}
/// 組の人数を重みで選ぶ
public int PickGroupSize(System.Random rng)
{
float total = 0f;
int n = groupSizeWeights != null ? groupSizeWeights.Length : 0;
for (int i = 0; i < n; i++) total += Mathf.Max(0f, groupSizeWeights[i]);
if (total <= 0f) return 2;
float r = (float)rng.NextDouble() * total;
for (int i = 0; i < n; i++)
{
r -= Mathf.Max(0f, groupSizeWeights[i]);
if (r <= 0f) return i + 1;
}
return n;
}
/// 平均 mean 回を、整数回に落とす(試算の fragile と同じやり方)
public static int PickCount(System.Random rng, float mean)
{
int n = (int)mean;
if (rng.NextDouble() < mean - n) n++;
return n;
}
}
}