コンテンツにスキップ

スクリプトを用いた例

速度制御メソッドやサーバー通信機能を用いたスクリプトの例を紹介します。

動作保証について

以下の例で必ずしも動く保証はありません。修正が必要な場合もありますので、JavaScript経験者向けとなります。

段階的な速度制御を行う簡易的なATC

function create(ctx, state, train) {
    state.oldSpeed = train.speed()*3.6*20;
    state.first = true;
    state.sendedRouteId = false;
}


function render(ctx, state, train) {
    train.getBlockDistance(train.id());
    let stoppingIndex = train.getServerResponse(train.id(), "getBlockDistance");

    if (!state.sendedRouteId) {
        try {
            let routeId = train.getThisRoutePlatforms().get(0).route.id;
            train.sendRequestToServer(train.id(), "setRouteIdToServer", routeId);
            state.sendedRouteId = true;
            state.routeId = routeId;

        } catch {}
    }
    let speed = train.speed()*3.6*20;
    train.sendRequestToServer(train.id(), "getNextPlatformIndex");
    let railIndex = train.getRailIndex(train.railProgress(), true);
    let nextPlatformIndex = train.getServerResponse(train.id(), "getNextPlatformIndex");
    let nextPlatformIndexSubRailIndex = train.getServerResponse(train.id(), "getNextPlatformIndex") - railIndex;
    let safeDistance = stoppingIndex - railIndex;
    ctx.setDebugInfo("NextStoppingIndex", stoppingIndex);
    ctx.setDebugInfo("railIndex", railIndex);
    ctx.setDebugInfo("TEMP", state.routeId);
    ctx.setDebugInfo("前方車両(赤信号や駅)までの閉塞単位距離", safeDistance);
    ctx.setDebugInfo("駅までの閉塞単位距離", nextPlatformIndexSubRailIndex);
    signalSpeed = maxSpeed;
    if (safeDistance <= 1) signalSpeed = 25;
    else if (safeDistance <= 2) signalSpeed = 40;
    else if(safeDistance <= 3) signalSpeed = 55;
    else if(safeDistance <= 4) signalSpeed = 70;
    ctx.setDebugInfo("信号速度", signalSpeed);
    train.setRailSpeed(train.id(), signalSpeed/3.6/20);
    if (speed > signalSpeed && train.isCurrentlyManual()) {
        train.setSpeed(train.id(), speed/3.6/20 - (0.0005 * (speed / state.oldSpeed)));
    }
    state.oldSpeed = train.speed()*3.6*20;
}

さらにきめ細やかな速度制御を行う簡易的なATC

負荷について

このスクリプトでは、列車から離れても処理が動き続けるよう設計しています。そのため、ゲームが非常に重くなる場合がありますのでご注意ください。

importPackage(java.util.concurrent);

let activeLCDs = [];
let asyncPool = null;

function create(ctx, state, train) {
    state.oldSpeed = train.speed()*3.6*20;
    state.first = true;
    state.sendedRouteId = false;
    state.oldSafeDistance = [];
    let onLoaded = false;
    for (let i = 0; i < activeLCDs.length; i++) {
        let selected = activeLCDs[i];
        if (selected["trainId"] == train.id()) {
            onLoaded = true;
            break;
        }
    }
    activeLCDs.push({ trainId: train.id(), ctx: ctx, state: state, blockEntity: train });


    if (Resources.ANTE_FLAG && asyncPool === null) {
        asyncPool = Executors.newScheduledThreadPool(1);
        asyncPool.scheduleAtFixedRate(new java.lang.Runnable({
            run: () => {
                for (let i = 0; i < activeLCDs.length; i++) {
                    let entry = activeLCDs[i];
                    try {
                        tick(entry.ctx, entry.state, entry.blockEntity);
                    } catch (e) {
                      print("描画中に例外が発生しました: " + e);
                      print("描画中に例外が発生しました: " + e.stack);
                      print("=== DISPOSE ===")
                      close(entry.ctx, entry.state, entry.blockEntity);
                    }
                }
            }
        }), 0, 1000 / 12, TimeUnit.MILLISECONDS);
    }
}

function close(ctx, state, blockEntity) {
    print("Dispose")
    for (let i = activeLCDs.length - 1; i >= 0; i--) {
        if (activeLCDs[i].blockEntity.equals(blockEntity)) {
            activeLCDs.splice(i, 1);
            break;
        }
    }

    if (activeLCDs.length === 0 && asyncPool !== null) {
        asyncPool.shutdown();
        print("Closed asyncPool");
        asyncPool = null;
    }

}

function checkLast10Values(arr) {
  if (!Array.isArray(arr) || arr.length < 10) {
    return false; // 10個未満なら判定不可
  }

  const last10 = arr.slice(arr.length - 10);

  // 出現回数カウント
  const countMap = {};

  for (let i = 0; i < last10.length; i++) {
    const v = last10[i];
    if (countMap[v] === undefined) {
      countMap[v] = 1;
    } else {
      countMap[v]++;
    }
  }

  // 最頻値を探す
  let normalValue = null;
  let maxCount = 0;

  const keys = Object.keys(countMap);
  for (let i = 0; i < keys.length; i++) {
    const value = keys[i];
    const count = countMap[value];
    if (count > maxCount) {
      maxCount = count;
      normalValue = Number(value);
    }
  }

  // 最頻値以外が含まれていれば異常
  for (let i = 0; i < last10.length; i++) {
    if (last10[i] !== normalValue) {
      return false;
    }
  }

  return true;
}



function tick(ctx, state, train) {
    train.getBlockDistance(train.id());
    let stoppingIndex = train.getServerResponse(train.id(), "getBlockDistance");

    if (!state.sendedRouteId) {
        try {
            let routeId = train.getThisRoutePlatforms().get(0).route.id;
            train.sendRequestToServer(train.id(), "setRouteIdToServer", routeId);
            state.sendedRouteId = true;
            state.routeId = routeId;

        } catch {}
    }
    let speed = train.speed()*3.6*20;
    train.sendRequestToServer(train.id(), "getNextPlatformIndex");
    let railIndex = train.getRailIndex(train.railProgress(), true);
    let nextPlatformIndex = train.getServerResponse(train.id(), "getNextPlatformIndex");
    let nextPlatformIndexSubRailIndex = train.getServerResponse(train.id(), "getNextPlatformIndex") - railIndex;
    let safeDistance = stoppingIndex - railIndex;

    ctx.setDebugInfo("NextStoppingIndex", stoppingIndex);
    ctx.setDebugInfo("railIndex", railIndex);
    ctx.setDebugInfo("前方車両(赤信号や駅)までの閉塞単位距離", safeDistance);
    ctx.setDebugInfo("駅までの閉塞単位距離", nextPlatformIndexSubRailIndex);
    let signalSpeed;
    if (safeDistance <= 2) signalSpeed = 20;
    else if (safeDistance <= 3) signalSpeed = 30;
    else if (safeDistance <= 5) signalSpeed = 40;
    else if (safeDistance <= 7) signalSpeed = 50;
    else if (safeDistance <= 9) signalSpeed = 60;
    else if (safeDistance <= 11) signalSpeed = 70;
    else if(safeDistance <= 13) signalSpeed = 80;
    else if(safeDistance <= 15) signalSpeed = 90;
    else if(safeDistance <= 17) signalSpeed = 100;
    else if(safeDistance <= 19) signalSpeed = 110;
    else if(safeDistance <= 21) signalSpeed = 120;
    else if(safeDistance <= 23) signalSpeed = 130;
    else signalSpeed = maxSpeed;
    ctx.setDebugInfo("信号速度", signalSpeed);
    state.signalSpeed = signalSpeed;
    train.setRailSpeed(train.id(), state.signalSpeed/3.6/20);
    state.oldSpeed = train.speed()*3.6*20;
    state.oldSafeDistance.push(safeDistance);
}

レールによる制限速度にも対応する簡易的なATC/加速度のカスタマイズ

負荷について

このスクリプトでは、列車から離れても処理が動き続けるよう設計しています。そのため、ゲームが非常に重くなる場合がありますのでご注意ください。

importPackage(java.util.concurrent);

let activeLCDs = [];
let asyncPool = null;


/**
 * 加速度(km/h/s)を内部値に変換する
 *
 * 前提:
 *  - 0.4 m/s^2 → 内部値 0.001
 *  - 比例関係と仮定
 *
 * @param {number} kmhPerSec  加速度 [km/h/s]
 * @returns {number} 内部値
 */
function calcInternalValue(kmhPerSec) {
  // km/h/s → m/s^2
  const ms2 = kmhPerSec * 1000 / 3600;

  // 基準値
  const baseMs2 = 0.4;
  const baseInternal = 0.001;

  return baseInternal * (ms2 / baseMs2);
}


function create(ctx, state, train) {
    state.oldSpeed = train.speed()*3.6*20;
  train.setAcceleration(train.id(), calcInternalValue(3.3));
    state.first = true;
    state.sendedRouteId = false;
    state.oldSafeDistance = [];
    let onLoaded = false;


  state.oldPos = Vector3f.ZERO;
  state.trainPos = Vector3f.ZERO;

    for (let i = 0; i < activeLCDs.length; i++) {
        let selected = activeLCDs[i];
        if (selected["trainId"] == train.id()) {
            onLoaded = true;
            break;
        }
    }
  for (let i = 0; i < activeLCDs.length; i++) {
    let trainId = activeLCDs["trainId"];
    if (train.id() == trainId) {
      activeLCDs.splice(i, 1);
    }
  }
    activeLCDs.push({ trainId: train.id(), ctx: ctx, state: state, blockEntity: train });


    if (Resources.ANTE_FLAG && asyncPool === null) {
        asyncPool = Executors.newScheduledThreadPool(2);
        asyncPool.scheduleAtFixedRate(new java.lang.Runnable({
            run: () => {
                for (let i = 0; i < activeLCDs.length; i++) {
                    let entry = activeLCDs[i];
                    try {
                        tickCustomSpeed(entry.ctx, entry.state, entry.blockEntity);
                    } catch (e) {
                      print("描画中に例外が発生しました: " + e);
                      print("描画中に例外が発生しました: " + e.stack);
                      print("=== DISPOSE ===")
                      close(entry.ctx, entry.state, entry.blockEntity);
                    }
                }
            }
        }), 0, 1000 / 12, TimeUnit.MILLISECONDS);

        asyncPool.scheduleAtFixedRate(new java.lang.Runnable({
            run: () => {
                for (let i = 0; i < activeLCDs.length; i++) {
                    let entry = activeLCDs[i];
                    try {
                        atc_point_detect(entry.ctx, entry.state, entry.blockEntity);
                    } catch (e) {
                      print("描画中に例外が発生しました: " + e);
                      print("描画中に例外が発生しました: " + e.stack);
                      print("=== DISPOSE ===")
                      close(entry.ctx, entry.state, entry.blockEntity);
                    }
                }
            }
        }), 0, 1000 / 12, TimeUnit.MILLISECONDS);
    }
}

function atc_point_detect(ctx, state, train) {
  try {
    let railTypes = {
      "WOODEN": 20,
      "STONE": 40,
      "EMERALD": 60,
      "IRON": 80,
      "OBSIDIAN": 120,
      "BLAZE": 160,
      "QUARTZ": 200,
      "DIAMOND": 300,
      "PLATFORM": 80,
      "SIDING": 40,
      "TURN_BACK": 80,
      "CABLE_CAR": 30,
      "CABLE_CAR_STATION": 2,
      "RUNWAY": 300,
      "AIRPLANE_DUMMY": 900,
      "NONE": 20
    }
    let railIndex = train.getRailIndex(train.railProgress(), true);
    let path = train.path().get(railIndex);
    let railType = String(path.rail.railType);
    if (railType.startsWith("P")) {
      let railMaxSpeed = Number(String(railType).substring(1));
      state.atcMaxSpeed = railMaxSpeed;
    } else {
      state.atcMaxSpeed = railTypes[railType];
    }

    ctx.setDebugInfo("railType", railType);
  } catch(e) {
    ctx.setDebugInfo("error_atc_point", e + e.stack);
  }
}

function close(ctx, state, blockEntity) {
    print("Dispose");
    for (let i = activeLCDs.length - 1; i >= 0; i--) {
        if (activeLCDs[i].blockEntity.equals(blockEntity)) {
            activeLCDs.splice(i, 1);
            break;
        }
    }

    if (activeLCDs.length === 0 && asyncPool !== null) {
        asyncPool.shutdown();
        print("Closed asyncPool");
        asyncPool = null;
    }

}

function checkLast10Values(arr) {
  if (!Array.isArray(arr) || arr.length < 10) {
    return false; // 10個未満なら判定不可
  }

  const last10 = arr.slice(arr.length - 10);

  // 出現回数カウント
  const countMap = {};

  for (let i = 0; i < last10.length; i++) {
    const v = last10[i];
    if (countMap[v] === undefined) {
      countMap[v] = 1;
    } else {
      countMap[v]++;
    }
  }

  // 最頻値を探す
  let normalValue = null;
  let maxCount = 0;

  const keys = Object.keys(countMap);
  for (let i = 0; i < keys.length; i++) {
    const value = keys[i];
    const count = countMap[value];
    if (count > maxCount) {
      maxCount = count;
      normalValue = Number(value);
    }
  }

  // 最頻値以外が含まれていれば異常
  for (let i = 0; i < last10.length; i++) {
    if (last10[i] !== normalValue) {
      return false;
    }
  }

  return true;
}



function tickCustomSpeed(ctx, state, train) {
  state.atc_stopped = false;
  try {
    let railIndex = train.getRailIndex(train.railProgress(), true);
    train.getBlockDistance(train.id());
    let stoppingIndex = train.getServerResponse(train.id(), "getBlockDistance");

    if (!state.sendedRouteId) {
      try {
        let routeId = train.getThisRoutePlatforms().get(0).route.id;
        train.sendRequestToServer(train.id(), "setRouteIdToServer", routeId);
        state.sendedRouteId = true;
        state.routeId = routeId;

      } catch {}
    }
    let speed = train.speed()*3.6*20;
    train.sendRequestToServer(train.id(), "getNextPlatformIndex");
    let nextPlatformIndex = train.getServerResponse(train.id(), "getNextPlatformIndex");
    let nextPlatformIndexSubRailIndex = train.getServerResponse(train.id(), "getNextPlatformIndex") - railIndex;
    let safeDistance = stoppingIndex - railIndex;

    ctx.setDebugInfo("NextStoppingIndex", stoppingIndex);
    ctx.setDebugInfo("railIndex", railIndex);
    ctx.setDebugInfo("前方車両(赤信号や駅)までの閉塞単位距離", safeDistance);
    ctx.setDebugInfo("駅までの閉塞単位距離", nextPlatformIndexSubRailIndex);

    if (safeDistance == nextPlatformIndexSubRailIndex && train.getThisRoutePlatformsNextIndex() == train.getThisRoutePlatforms().size()-1) {
      state.station_safe_stopping = true;
    } else {
      state.station_safe_stopping = false;
    }

    let signalSpeed;
    if (safeDistance != nextPlatformIndexSubRailIndex || state.station_safe_stopping) {
      if (safeDistance < 1) signalSpeed = 25;
      else if (safeDistance <= 2) signalSpeed = 40;
      else if (safeDistance <= 3) signalSpeed = 45;
      else if (safeDistance <= 5) signalSpeed = 50;
      else if (safeDistance <= 7) signalSpeed = 60;
      else if (safeDistance <= 9) signalSpeed = 70;
      else if (safeDistance <= 11) signalSpeed = 80;
      else if(safeDistance <= 13) signalSpeed = 90;
      else if(safeDistance <= 15) signalSpeed = 100;
      else if(safeDistance <= 17) signalSpeed = 110;
      else if(safeDistance <= 19) signalSpeed = 120;
      else signalSpeed = maxSpeed;
    } else signalSpeed = maxSpeed;
    if (signalSpeed > maxSpeed) {
      signalSpeed = maxSpeed;
    }
    if (signalSpeed > state.atcMaxSpeed) {
      signalSpeed = state.atcMaxSpeed;
    }
    ctx.setDebugInfo("信号速度", signalSpeed);
    if (signalSpeed != state.signalSpeed) {
      train.setRailSpeed(train.id(), signalSpeed/3.6/20);
      let sound = TickableSound(Resources.id("mtr:atc_speed_changed"));
      sound.setData(1, 1, train.lastCarPosition[0]);
      sound.play();
      let sound2 = TickableSound(Resources.id("mtr:atc_speed_changed"));
      sound2.setData(1, 1, train.lastCarPosition[train.trainCars()-1]);
      sound2.play();

      state.signalSpeed = signalSpeed;
    }
    state.oldSpeed = train.speed()*3.6*20;
    state.oldSafeDistance.push(safeDistance);

    train.getServerResponse(train.id(), "")
  } catch {
    state.atc_stopped = true;
  }
}