--- cs:140/Robot/ServingRobotController.cs
+++ cs:141/Robot/ServingRobotController.cs
@@ -336,6 +336,21 @@
/// この走りで寄る卓の数
public int RunStopCount { get { return _stops.Count; } }
+ readonly List _stopArrivedS = new List();
+
+ ///
+ /// 止まる所ごとに着いた時刻(店の中, 秒)。着いていない所は -1(依頼 v8 の 2)。
+ /// trips.csv の stop_arrived_s に出す
+ ///
+ public IReadOnlyList StopArrivedS { get { return _stopArrivedS; } }
+
+ ///
+ /// 止まる所に着いた(引数:ロボット・止まる所の番号)。依頼 v8 の 1。
+ /// ServiceOps が受けて、その所の皿を「届いた」にする。
+ /// 受け渡しの 25 秒(tableDwellSec)の **前** に出る
+ ///
+ public event Action StopArrived;
+
/// 1往復が終わったときに呼ばれる
public event Action TripCompleted;
@@ -540,16 +555,19 @@
RunStartS = startS;
_stops.Clear();
_tableIds.Clear();
+ _stopArrivedS.Clear();
if (stops != null)
{
for (int i = 0; i < stops.Count; i++)
{
_stops.Add(stops[i]);
_tableIds.Add(stops[i].tableId);
+ _stopArrivedS.Add(-1f); // 着いたら時刻を入れる(依頼 v8 の 2)
}
}
_stopIndex = 0;
StopsCompleted = 0;
+ ResetTo = ""; // 打ち切りで置き直したときだけ入れる(依頼 v8b の A)
if (_stops.Count == 0) { _phase = Phase.Idle; return; }
BeginRun(passPoint);
@@ -801,7 +819,11 @@
if (TotalS > spec.maxTripSec)
{
TimedOut = true;
- PlaceAt(_passPoint, transform.rotation);
+ // ほかの台と重ならない所へ置き直す(依頼 v8b の A)。
+ // 前は必ず受け渡し口の点に置いていたので、2台が打ち切られると
+ // 同じ点で重なり、お互いを「もう1台のロボット」として止まり続けていた
+ Vector3 to = PickResetPoint();
+ PlaceAt(to, transform.rotation);
Finish();
return;
}
@@ -1069,6 +1091,79 @@
List _sideCandidates;
+ // ---------------- 打ち切りで置き直す先(依頼 v8b の A)----------------
+
+ ///
+ /// 打ち切りで置き直した先(trips.csv の reset_to)。
+ /// pass / standby1 / standby2 …。打ち切りでない往復は空
+ ///
+ public string ResetTo { get; private set; }
+
+ ///
+ /// 置く先を選ぶ。ほかの台が近く(直径+余裕)にいない最初の点。
+ /// 受け渡し口の点 → 待機場所を順に
+ /// どこも空いていなければ、ほかの台からいちばん遠い待機場所
+ /// (待機場所が無ければ受け渡し口の点)。
+ ///
+ Vector3 PickResetPoint()
+ {
+ float near = spec != null ? spec.diameter + ResetClearMarginM : 0.8f;
+
+ IList standby = ops != null && ops.store != null
+ ? ops.store.RobotStandbyPoints : null;
+
+ // 1. 受け渡し口の点
+ if (NearestRobotDistance(_passPoint) > near) { ResetTo = "pass"; return _passPoint; }
+
+ // 2. 待機場所を順に
+ if (standby != null)
+ {
+ for (int i = 0; i < standby.Count; i++)
+ {
+ if (NearestRobotDistance(standby[i]) <= near) continue;
+ ResetTo = "standby" + (i + 1);
+ return standby[i];
+ }
+
+ // 3. どこも空いていない。ほかの台からいちばん遠い待機場所
+ int best = -1;
+ float bestD = -1f;
+ for (int i = 0; i < standby.Count; i++)
+ {
+ float d = NearestRobotDistance(standby[i]);
+ if (d <= bestD) continue;
+ best = i; bestD = d;
+ }
+ if (best >= 0)
+ {
+ ResetTo = "standby" + (best + 1);
+ return standby[best];
+ }
+ }
+
+ ResetTo = "pass";
+ return _passPoint;
+ }
+
+ /// 置く先として見るときの、直径に足す余裕(m)
+ const float ResetClearMarginM = 0.3f;
+
+ /// その点から、いちばん近いほかの台までの距離(m)。ほかに台が無ければ大きい値
+ float NearestRobotDistance(Vector3 p)
+ {
+ p.y = 0f;
+ float best = float.MaxValue;
+ for (int i = 0; i < _all.Count; i++)
+ {
+ ServingRobotController o = _all[i];
+ if (o == null || o == this) continue;
+ Vector3 q = o.transform.position; q.y = 0f;
+ float d = Vector3.Distance(p, q);
+ if (d < best) best = d;
+ }
+ return best;
+ }
+
/// 相手の外周と自分の外周のあいだの空き(m)
float Gap(ServingRobotController other)
{
@@ -1545,6 +1640,14 @@
_dwellTimer = 0f;
_speed = 0f;
StopsCompleted = _stopIndex + 1; // この卓には届いた
+
+ // 卓に着いた時点を残して知らせる(依頼 v8 の 1)。
+ // サイトの試算と同じで、ここで皿は「届いた」。
+ // 受け渡しの 25 秒(tableDwellSec)はこのあと
+ int at = _stopIndex;
+ if (at >= 0 && at < _stopArrivedS.Count)
+ _stopArrivedS[at] = ops != null ? ops.NowS : 0f;
+ if (StopArrived != null) StopArrived(this, at);
}
else if (_phase == Phase.ToPass)
{