PMD+

Extension qui ajoute des fonctionnalités (timer, pilote automatique et plan dynamique) dans le jeu PMD.

// ==UserScript==
// @name         PMD+
// @namespace    https://github.com/PieroLB/pmd-plus
// @version      0.4.6
// @description  Extension qui ajoute des fonctionnalités (timer, pilote automatique et plan dynamique) dans le jeu PMD.
// @author       PieroLB
// @match        https://pmdapp.fr/*
// @match        https://www.pmdapp.fr/*
// @icon         https://lh3.googleusercontent.com/dJpt27Lfg0upeK1rRqPk8W5pMv5JAdOVeCIlM28XQ3o_SL-pCIPH51dnZzPY7lcCNf_tWSOkC-4lzTCToIPGDI3vizk=s60
// @license      MIT
// @grant        none
// ==/UserScript==

const auto = () => {
  var auto = false;
  const TIME_RINGING_DOORS = 3000; // en millisecondes
  const TIME_MIN_DOORS_OPENED = 3000; // en millisecondes
  const TIME_CLOSING_OPENING_DOORS = 3000; // en millisecondes
  const TIME_MIN_DEPARTURE = 70; // en secondes
  const SPEED_PADDING_BOTTOM = 2; // en km/h
  const TIME_IN_STATION = 40;
  var eventInStation = false;
  var depart = false;
  var deceleration = 0;
  var limiteMaxDecelerationAtteinte = false;
  var limiteMinDecelerationAtteinte = false;
  var decelerationComplete = false;
  var decelerationAvtStation = false;
  const getTime = (station1, station2) => {
    let t = 0;
    let listSpeedLimits = {};
    if (typeof speedLimitNG !== "undefined") {
      listSpeedLimits = speedLimitNG;
    } else if (typeof speedLimits !== "undefined") {
      speedLimits.forEach((l) => {
        listSpeedLimits[l.start] = l.limit;
      });
    }
    let listPos = Object.keys(listSpeedLimits);
    let posAvt = listPos[0];
    if (station1.end < posAvt && station2.start < posAvt) {
      // Permet de gérer les garages qui nont pas de speedLimit (elles arrivent après). Ex : ligne 3
      let v = localSpeedLimit / 3.6;
      let d = (station2.start - station1.end) / 75;
      t = d / v;
    } else if (station1.end < posAvt) {
      let v = localSpeedLimit / 3.6;
      let d = (posAvt - station1.end) / 75;
      t += d / v;
    } else if (listPos.length == 1) {
      // Permet de gérer sil y a quune seule speedLimit sur toute la ligne. Ex : ligne 7bis et 9
      let v = listSpeedLimits[posAvt] / 3.6;
      let d = (station2.start - Math.max(station1.end, posAvt)) / 75;
      t = d / v;
    } else if (listPos.length == 0) {
      // Permet de gérer sil y a aucune speedLimit sur toute la ligne. Ex : ligne 13 et 12
      let v = localSpeedLimit / 3.6;
      let d = (station2.start - station1.end) / 75;
      t = d / v;
    } else {
      for (let i = 1; i < listPos.length; i++) {
        if (listPos[i] >= station1.end && listPos[i] <= station2.start) {
          let v = listSpeedLimits[posAvt] / 3.6;
          let d = (listPos[i] - Math.max(station1.end, posAvt)) / 75;
          t += d / v;
        } else if (listPos[i] > station2.start) {
          let v = listSpeedLimits[posAvt] / 3.6;
          let d = (station2.start - posAvt) / 75;
          t += d / v;
          break;
        }
        posAvt = listPos[i];
      }
    }
    return t;
  };
  var timeToGoNextStation = null;
  var lastStation = null;
  var terminus = false;
  var isDoorOpened = false;
  const AUTOcloseDoor = (t = TIME_RINGING_DOORS) => {
    isDoorOpened = false;
    if (NADOMAS) {
      KEYBOARD_DOWN["KeyO"] = false;
      KEYBOARD_DOWN["KeyF"] = true;
      t = TIME_RINGING_DOORS;
    } else {
      closeDoor();
    }
    setTimeout(() => {
      if (areDoorFullyClosed() == false) {
        // ECHEC DE LA FERMETURE => REOUVERTURE
        isDoorOpened = true;
        if (NADOMAS) {
          KEYBOARD_DOWN["KeyF"] = false;
          KEYBOARD_DOWN["KeyO"] = true;
        } else {
          doorOpen();
        }
        AUTOcloseDoor(0);
      } else {
        if (NADOMAS) {
          KEYBOARD_DOWN["KeyF"] = false;
          KEYBOARD_DOWN["KeyO"] = false;
        }
        decelerationAvtStation = false;
        depart = true;
      }
    }, t);
  };
  setInterval(() => {
    if (auto) {
      if (!eventInStation) {
        // ON EST DANS UNE STATION ET IL NE FAUT PAS PARTIR
        if (
          !depart &&
          UTILS.isStation() &&
          ((!UTILS.isStation().closed &&
            UTILS.isStation().allowPassengers &&
            UTILS.isStation().freq > 0) ||
            zoneStations.indexOf(UTILS.isStation()) == zoneStations.length - 1)
        ) {
          if (
            zoneStations.indexOf(UTILS.isStation()) ==
            zoneStations.length - 1
          )
            terminus = true;
          if (!terminus && lastStation != UTILS.isStation()) {
            timeToGoNextStation = getTime(
              UTILS.isStation(),
              zoneStations[zoneStations.indexOf(UTILS.isStation()) + 1]
            );
            lastStation = UTILS.isStation();
          }

          // A LARRET
          if (currentSpeed == 0) {
            if (!isDoorOpened) {
              console.log("OUVERTURE DES PORTES");
              console.log(KEYBOARD_DOWN);
              isDoorOpened = true;
              eventInStation = true;
              if (NADOMAS) {
                KEYBOARD_DOWN["KeyF"] = false;
                KEYBOARD_DOWN["KeyO"] = true;
                // let loop = setInterval(()=>{
                //   if (doorOpened){
                //     KEYBOARD_DOWN["KeyO"] = false;
                //     clearInterval(loop)
                //   }
                // });
              } else {
                doorOpen();
              }
              setTimeout(() => {
                eventInStation = false;
              }, TIME_CLOSING_OPENING_DOORS + TIME_MIN_DOORS_OPENED); // TEMPS MINIMUM DE LOUVERTURE A LA FERMETURE DES PORTES
            } else if (
              (terminus && UTILS.getModule("Passagers").onBoard.length == 0) ||
              (!terminus &&
                timeBeforeArriving <=
                  timeToGoNextStation +
                    (TIME_CLOSING_OPENING_DOORS + TIME_RINGING_DOORS) / 1000 +
                    8)
            ) {
              console.log("FERMETURE DES PORTES");
              eventInStation = true;
              AUTOcloseDoor();
              setTimeout(() => {
                eventInStation = false;
              }, TIME_RINGING_DOORS + TIME_CLOSING_OPENING_DOORS + 1000); // TEMPS MINIMUM DE LOUVERTURE A LA FERMETURE DES PORTES
            }
          }
          // IL FAUT RALENTIR POUR SARRETER EN STATION
          else {
            let virtualSpeed = currentSpeed;
            let virtualPosition = 0;
            while (virtualSpeed > 0) {
              virtualSpeed += 1 * ((-13 / 80) * 1.2) * 1.2;
              virtualPosition += (virtualSpeed / 3) * 1.3 * 1;
            }
            virtualPosition = -globalTranslate + virtualPosition;

            if (virtualPosition >= UTILS.isStation().end) {
              currentThrottle = -11;
              limiteMaxDecelerationAtteinte = true;
            } else if (
              limiteMaxDecelerationAtteinte &&
              !limiteMinDecelerationAtteinte &&
              currentSpeed <= 33
            ) {
              currentThrottle = 0;
              limiteMinDecelerationAtteinte = true;
            } else if (!limiteMaxDecelerationAtteinte) {
              currentThrottle = 0;
            }
          }
        } else {
          // ON SORT DUNE STATION
          if (!UTILS.isFullyInStation() && depart)
            depart =
              limiteMinDecelerationAtteinte =
              limiteMaxDecelerationAtteinte =
                false;
          // ON RALENTIT AVANT LA STATION TELLEMENT ON VA VITE
          if (
            decelerationAvtStation ||
            (currentSpeed > 80 && nextStation.start + globalTranslate <= 10000)
          ) {
            currentThrottle = -5;
            decelerationAvtStation = true;
          }
          // ADAPTION DE LA VITESSE DE ROULEMENT
          else {
            let speedLim = UTILS.currentSpeedLimit();
            if (currentSpeed < speedLim - SPEED_PADDING_BOTTOM) {
              currentThrottle = 5;
            }
            if (currentSpeed >= speedLim + 1) {
              currentThrottle = -9;
            }
            if (currentSpeed < speedLim + 1 && currentSpeed > speedLim) {
              if (speedLim > 80) {
                currentThrottle = 3;
              } else if (speedLim > 50) {
                currentThrottle = 1;
              } else {
                currentThrottle = 0;
              }
            }
          }
        }
      }
    }
  });

  document.getElementById("auto").addEventListener("click", () => {
    auto = !auto;
    if (!auto) {
      if (currentThrottle > 0) currentThrottle = 0;
      KEYBOARD_DOWN["KeyF"] = KEYBOARD_DOWN["KeyO"] = false;
    }
    document.getElementById("auto").style.backgroundColor = auto
      ? "green"
      : "darkred";
  });
};

const plan = () => {
  // document.getElementById("plan").addEventListener("click", (event) => {
  //   if (modules[2].displayMode) modules[2].displayMode = 0;
  // });
  const listLignes = {
    Metro: {
      img: null,
      lignes: {
        1: { img: null },
        2: { img: null },
        3: { img: null },
        "3bis": { img: null },
        4: { img: null },
        5: { img: null },
        6: { img: null },
        7: { img: null },
        "7bis": { img: null },
        8: { img: null },
        9: { img: null },
        10: { img: null },
        11: { img: null },
        12: { img: null },
        13: { img: null },
        14: { img: null },
      },
    },
    RER: {
      img: null,
      lignes: {
        A: { img: null },
        B: { img: null },
        C: { img: null },
        D: { img: null },
        E: { img: null },
      },
    },
    Transilien: {
      img: null,
      lignes: {
        H: { img: null },
        J: { img: null },
        K: { img: null },
        L: { img: null },
        N: { img: null },
        P: { img: null },
        R: { img: null },
        U: { img: null },
      },
    },
    Tramway: {
      img: null,
      lignes: {
        T1: { img: null },
        T2: { img: null },
        T3a: { img: null },
        T3b: { img: null },
        T4: { img: null },
        T5: { img: null },
        T6: { img: null },
        T7: { img: null },
        T8: { img: null },
        T9: { img: null },
        T10: { img: null },
        T11: { img: null },
        T12: { img: null },
        T13: { img: null },
      },
    },
    Autres: {
      img: null,
      lignes: {
        RoissyBus: { img: null },
        OrlyBus: { img: null },
        OrlyVal: { img: null },
        Tvm: { img: null },
        TER: { img: null },
      },
    },
  };
  var accessibleImg = null;
  var addImg = null;
  const urlAssets =
    "https://raw.githubusercontent.com/PieroLB/pmd-tools-assets/main/plan/";
  const refHeight = 190;
  var padX = 0;
  class PLANStation {
    static stations = [];
    static storage;
    static planIndex;
    static canvas;
    static ctx;
    static colorLine = "#FECE03";
    static photo = false;
    static logoImg = null;
    static closedImg = null;
    static widthTot = 40;
    static y = 90;
    static arc = { width: 20 };
    static rect = { width: 50, height: 7 };
    static bgText = { padding: { y: 3, x: 6 } };
    static text = { nom: 10, tourist: 9 };
    static rectCorres = { width: 2, height: 10 };
    static imgCorres = { width: 15 };
    static scrollValue = false;
    static loadingImg = () => {
      const promises = [];

      const accessibleImgPromise = new Promise((resolve, reject) => {
        let img = new Image();
        img.src = `${urlAssets}accessible.svg`;
        img.onload = () => {
          accessibleImg = img;
          resolve();
        };
        img.onerror = () => reject(new Error(`Failed to load image: ${type}`));
      });
      promises.push(accessibleImgPromise);
      const addImgPromise = new Promise((resolve, reject) => {
        let img = new Image();
        img.src = `${urlAssets}add.svg`;
        img.onload = () => {
          addImg = img;
          resolve();
        };
        img.onerror = () => reject(new Error(`Failed to load image: ${type}`));
      });
      promises.push(addImgPromise);
      const logoImgPromise = new Promise((resolve, reject) => {
        let img = new Image();
        img.src = `${urlAssets}PMD.svg`;
        img.onload = () => {
          PLANStation.logoImg = img;
          resolve();
        };
        img.onerror = () => reject(new Error(`Failed to load image: ${type}`));
      });
      promises.push(logoImgPromise);
      const closedImgPromise = new Promise((resolve, reject) => {
        let img = new Image();
        img.src = `${urlAssets}closed.svg`;
        img.onload = () => {
          PLANStation.closedImg = img;
          resolve();
        };
        img.onerror = () => reject(new Error(`Failed to load image: ${type}`));
      });
      promises.push(closedImgPromise);

      Object.keys(listLignes).forEach((type) => {
        if (type != "Autres") {
          const mainImgPromise = new Promise((resolve, reject) => {
            let img = new Image();
            img.src = `${urlAssets}${type}/${type}.svg`;
            img.onload = () => {
              listLignes[type].img = img;
              resolve();
            };
            img.onerror = () =>
              reject(new Error(`Failed to load image: ${type}`));
          });
          promises.push(mainImgPromise);
        }

        Object.keys(listLignes[type].lignes).forEach((ligne) => {
          const ligneImgPromise = new Promise((resolve, reject) => {
            let img = new Image();
            img.src = `${urlAssets}${type}/${ligne}.svg`;
            img.onload = () => {
              listLignes[type].lignes[ligne].img = img;
              listLignes[type].lignes[ligne].coef = null;
              if (img.naturalHeight != 0 && img.naturalHeight != 0)
                listLignes[type].lignes[ligne].coef =
                  img.naturalWidth / img.naturalHeight;
              resolve();
            };
            img.onerror = () =>
              reject(new Error(`Failed to load image: ${type}-${ligne}`));
          });
          promises.push(ligneImgPromise);
        });
      });

      return Promise.all(promises)
        .then(() => {
          return;
        })
        .catch((error) => {
          console.error(
            "Une erreur s'est produite lors du chargement des images :",
            error
          );
          throw error;
        });
    };
    constructor(
      nom = `PLANStation n°${PLANStation.stations.length + 1}`,
      lieuTouristique = "",
      accessible = false,
      correspondances = [],
      stationInPMD = null,
      pos = PLANStation.stations.length
    ) {
      this.nom =
        nom == "" ? `PLANStation n°${PLANStation.stations.length + 1}` : nom;
      this.lieuTouristique = lieuTouristique;
      this.correspondances = correspondances;
      this.accessible = accessible;
      this.closed = closed;
      this.clignote = false;
      this.stationInPMD = stationInPMD;
      PLANStation.widthTot = Math.min(
        PLANStation.widthTot + PLANStation.arc.width + PLANStation.rect.width,
        1024
      );
      PLANStation.stations.splice(pos, 0, this);
      let x = 20;
      PLANStation.stations.forEach((station) => {
        station.x = x;
        x += PLANStation.arc.width + PLANStation.rect.width;
      });
    }
    render() {
      // LIGNE
      if (
        PLANStation.stations.indexOf(this) !=
        PLANStation.stations.length - 1
      ) {
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = PLANStation.colorLine;
        PLANStation.ctx.rect(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y +
            PLANStation.arc.width / 2 -
            PLANStation.rect.height / 2,
          PLANStation.rect.width + (3 / 2) * PLANStation.arc.width,
          PLANStation.rect.height
        );
        PLANStation.ctx.fill();
        PLANStation.ctx.closePath();
      }
      // CORRESPONDANCES
      if (this.correspondances.length > 0) {
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = "#15388D";
        PLANStation.ctx.rect(
          padX +
            this.x +
            PLANStation.arc.width / 2 -
            PLANStation.rectCorres.width / 2,
          PLANStation.y + PLANStation.arc.width - 2,
          PLANStation.rectCorres.width,
          PLANStation.rectCorres.height / 2
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
        let y = 0;
        Object.keys(listLignes).forEach((type) => {
          if (
            this.correspondances.some((l) =>
              Object.keys(listLignes[type].lignes).includes(l)
            )
          ) {
            PLANStation.ctx.beginPath();
            PLANStation.ctx.fillStyle = "#15388D";
            PLANStation.ctx.rect(
              padX +
                this.x +
                PLANStation.arc.width / 2 -
                PLANStation.rectCorres.width / 2,
              PLANStation.y + (PLANStation.arc.width - 3) + y + 3,
              PLANStation.rectCorres.width,
              PLANStation.rectCorres.height - 3
            );
            PLANStation.ctx.closePath();
            PLANStation.ctx.fill();

            let X = 0;
            let n = 0;
            if (listLignes[type].img != null) {
              PLANStation.ctx.drawImage(
                listLignes[type].img,
                padX +
                  this.x +
                  PLANStation.arc.width / 2 -
                  PLANStation.imgCorres.width / 2,
                y +
                  PLANStation.y +
                  PLANStation.arc.width +
                  PLANStation.rectCorres.height,
                PLANStation.imgCorres.width,
                PLANStation.imgCorres.width
              );
            } else {
              y -= PLANStation.imgCorres.width + 3;
            }
            let yAvt = y;
            Object.keys(listLignes[type].lignes).forEach((ligne) => {
              if (this.correspondances.includes(ligne)) {
                if (listLignes[type].img != null && n != 0 && n % 3 == 0) {
                  y += PLANStation.imgCorres.width + 3;
                  X = 0;
                }
                if (listLignes[type].img != null) {
                  X += PLANStation.imgCorres.width + 3;
                } else {
                  y += PLANStation.imgCorres.width + 3;
                }
                let h = PLANStation.imgCorres.width;
                let w = PLANStation.imgCorres.width;
                if (
                  listLignes[type].lignes[ligne].coef != null &&
                  listLignes[type].lignes[ligne].coef != 1
                ) {
                  h -= 5;
                  w = h * listLignes[type].lignes[ligne].coef;
                }
                PLANStation.ctx.drawImage(
                  listLignes[type].lignes[ligne].img,
                  padX +
                    X +
                    this.x +
                    PLANStation.arc.width / 2 -
                    PLANStation.imgCorres.width / 2,
                  y +
                    PLANStation.y +
                    PLANStation.arc.width +
                    PLANStation.rectCorres.height,
                  w,
                  h
                );
                n++;
              }
            });
            if (listLignes[type].img != null) {
              PLANStation.ctx.beginPath();
              PLANStation.ctx.fillStyle = "#15388D";
              PLANStation.ctx.rect(
                padX +
                  this.x +
                  PLANStation.arc.width / 2 -
                  PLANStation.rectCorres.width / 2,
                PLANStation.y +
                  (PLANStation.arc.width - 3) +
                  yAvt +
                  3 +
                  2 * (PLANStation.imgCorres.width + 3),
                PLANStation.rectCorres.width,
                y - yAvt
              );
              PLANStation.ctx.closePath();
              PLANStation.ctx.fill();
            }
            y += 2 * PLANStation.imgCorres.width - 3;
          }
        });
      }
      // CERCLE
      if (
        this.correspondances.length > 0 &&
        (PLANStation.stations.indexOf(this) == 0 ||
          PLANStation.stations.indexOf(this) == PLANStation.stations.length - 1)
      ) {
        // Il y a au moins une correspondance et c'est un terminus
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = "black";
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = "white";
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2 - 3,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = PLANStation.colorLine;
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2 - 5,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
      } else if (
        PLANStation.stations.indexOf(this) == 0 ||
        PLANStation.stations.indexOf(this) == PLANStation.stations.length - 1
      ) {
        // C'est un terminus
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = "black";
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = PLANStation.colorLine;
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2 - 5,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
      } else if (this.correspondances.length > 0) {
        // C'est une station avec au moins une correspondance
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = "black";
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = "white";
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2 - 3,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
      } else {
        // C'est une station sans rien
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = PLANStation.colorLine;
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
      }
      if (this.closed) {
        PLANStation.ctx.drawImage(
          PLANStation.closedImg,
          padX + this.x - 7,
          PLANStation.y - 7,
          PLANStation.arc.width + 14,
          PLANStation.arc.width + 14
        );
      } else if (this.clignote) {
        PLANStation.ctx.beginPath();
        PLANStation.ctx.fillStyle = "yellow";
        PLANStation.ctx.arc(
          padX + this.x + PLANStation.arc.width / 2,
          PLANStation.y + PLANStation.arc.width / 2,
          PLANStation.arc.width / 2 - 3,
          0,
          2 * Math.PI
        );
        PLANStation.ctx.closePath();
        PLANStation.ctx.fill();
      }
      // TEXT
      PLANStation.ctx.translate(
        padX + this.x + PLANStation.arc.width / 2,
        PLANStation.y - 5
      );
      PLANStation.ctx.rotate(-30 * (Math.PI / 180));
      PLANStation.ctx.beginPath();
      if (this.lieuTouristique != "") {
        PLANStation.ctx.font = `italic ${PLANStation.text.tourist}px Arial`;
        let widthText = PLANStation.ctx.measureText(this.lieuTouristique).width;
        PLANStation.ctx.fillStyle = "#865200";
        PLANStation.ctx.rect(
          -PLANStation.bgText.padding.x + 15,
          -PLANStation.text.tourist +
            PLANStation.text.nom / 2 +
            PLANStation.bgText.padding.y * 2 +
            1,
          widthText + PLANStation.bgText.padding.x * 2,
          PLANStation.text.tourist + PLANStation.bgText.padding.y * 2
        );
        PLANStation.ctx.fill();
        PLANStation.ctx.fillStyle = "white";
        PLANStation.ctx.fillText(
          this.lieuTouristique,
          15,
          PLANStation.text.nom / 2 + PLANStation.bgText.padding.y * 2 + 2
        );
      }
      PLANStation.ctx.closePath();
      PLANStation.ctx.beginPath();
      PLANStation.ctx.fillStyle = "#15388D";
      PLANStation.ctx.font = `bold ${PLANStation.text.nom}px Arial`;
      let y = 0;
      let widthText = PLANStation.ctx.measureText(this.nom).width;
      if (
        PLANStation.stations.indexOf(this) == 0 ||
        PLANStation.stations.indexOf(this) == PLANStation.stations.length - 1
      ) {
        PLANStation.ctx.rect(
          -PLANStation.bgText.padding.y,
          -PLANStation.text.nom - PLANStation.bgText.padding.x,
          widthText + PLANStation.bgText.padding.x * 2,
          PLANStation.text.nom + PLANStation.bgText.padding.y * 2
        );
        PLANStation.ctx.fill();
        PLANStation.ctx.fillStyle = "white";
        y = -PLANStation.bgText.padding.y - 1;
      }
      PLANStation.ctx.fillText(this.nom, PLANStation.bgText.padding.x / 4, y);
      if (this.accessible)
        PLANStation.ctx.drawImage(
          accessibleImg,
          padX + widthText + PLANStation.canvas.height * (10 / refHeight),
          -PLANStation.text.nom + PLANStation.bgText.padding.y,
          PLANStation.text.nom,
          PLANStation.text.nom
        );
      PLANStation.ctx.closePath();
      PLANStation.ctx.resetTransform();
    }
    event(type, x, y) {
      if (type == "click") {
        let arc = { x: this.x, y: PLANStation.y };
        console.log("lol");
        if (
          x >= arc.x &&
          x <= arc.x + PLANStation.arc.width &&
          y >= arc.y &&
          y <= arc.y + PLANStation.arc.width
        ) {
          // Click sur une station pour la modifier
          this.closed = !this.closed;
        }
      }
    }
  }

  let nextStationPLAN;
  let isInStation = UTILS.isStation() != undefined ? true : false;
  const update = () => {
    requestAnimationFrame(update);

    PLANStation.canvas.height = 200;
    PLANStation.canvas.width = PLANStation.widthTot;

    PLANStation.ctx.clearRect(
      0,
      0,
      PLANStation.canvas.width,
      PLANStation.canvas.height
    );
    // Background blanc
    PLANStation.ctx.beginPath();
    PLANStation.ctx.fillStyle = "white";
    PLANStation.ctx.rect(
      0,
      0,
      PLANStation.canvas.width,
      PLANStation.canvas.height
    );
    PLANStation.ctx.closePath();
    PLANStation.ctx.fill();
    // Rendus des stations
    // if (!modules[2].displayMode) {
    if (UTILS.isStation() == undefined && isInStation == true) {
      // Sortie de station
      document.getElementById("plan").style.display = "none";
      isInStation = false;
    } else if (UTILS.isStation() && isInStation == false) {
      // Entrée une station
      setTimeout(() => {
        document.getElementById("plan").style.display = "block";
        isInStation = true;
      }, 800);
    } else {
      isInStation = UTILS.isStation() != undefined ? true : false;
    }
    // }
    nextStationPLAN =
      getNextStation() == false ? nextStationPLAN : getNextStation();
    PLANStation.stations.forEach((station) => {
      if (station.nom == nextStationPLAN.name) {
        if (padX + station.x < 0) {
          padX = -Math.max(
            0,
            (PLANStation.stations.indexOf(station) + 1) *
              (PLANStation.arc.width + PLANStation.rect.width) -
              1024
          );
        } else if (padX + station.x > 1024) {
          padX = -1024;
        }
      }
      station.render();
    });
  };

  PLANStation.canvas = document.getElementById("canvasPlan");
  PLANStation.ctx = PLANStation.canvas.getContext("2d");
  PLANStation.loadingImg().then(() => {
    fetch(
      "https://raw.githubusercontent.com/PieroLB/pmd-tools-assets/main/plan/data.json"
    )
      .then((resp) => resp.json())
      .then((resp) => {
        let line = new URLSearchParams(location.search).get("line");
        if (resp) {
          PLANStation.colorLine = resp[line].color;
          resp[line].stations.forEach((station) => {
            let stationInPMD = zoneStations.find(
              (s) =>
                s.name == station.nom ||
                s.name == station.nom + " v1" ||
                s.name == station.nom + " v2"
            );
            new PLANStation(
              station.nom,
              station.lieuTouristique,
              station.accessible,
              station.correspondances,
              stationInPMD
            );
          });
        }
        for (let station of zoneStations) {
          let stationPLAN = PLANStation.stations.find(
            (s) =>
              s.nom == station.name ||
              s.nom == station.name + " v1" ||
              s.nom == station.name + " v2"
          );
          if (stationPLAN) {
            padX = -Math.max(
              0,
              (PLANStation.stations.indexOf(stationPLAN) + 1) *
                (PLANStation.arc.width + PLANStation.rect.width) -
                1024
            );
            break;
          }
        }

        PLANStation.canvas.addEventListener("click", function (event) {
          PLANStation.stations.forEach((station) => {
            let arc = { x: station.x + padX, y: PLANStation.y };
            if (
              event.clientY - this.getBoundingClientRect().y >= arc.y &&
              event.clientY - this.getBoundingClientRect().y <=
                arc.y + PLANStation.arc.width &&
              event.clientX >= arc.x &&
              event.clientX <= arc.x + PLANStation.arc.width
            ) {
              station.closed = !station.closed;
              if (station.stationInPMD != null)
                station.stationInPMD.closed = station.closed;
              return;
            }
          });
        });

        let clignote = true;
        const intervalle = 500;
        setInterval(() => {
          PLANStation.stations.forEach((station) => {
            if (
              station.nom == nextStationPLAN.name ||
              station.nom + " v1" == nextStationPLAN.name ||
              station.nom + " v2" == nextStationPLAN.name
            ) {
              station.clignote = clignote;
              clignote = !clignote;
            } else if (
              zoneStations
                .slice(0, zoneStations.indexOf(nextStationPLAN) + 1)
                .find(
                  (s) =>
                    s.name == station.nom ||
                    s.name == station.nom + " v1" ||
                    s.name == station.nom + " v2"
                )
            ) {
              station.clignote = false;
            } else {
              station.clignote = true;
            }
          });
        }, intervalle);

        update();
      });
  });
};

const settings = () => {
  var toolsStatus = {
    timer: { nom: "le timer", elementID: "time-box", status: true },
    auto: { nom: "le mode auto", elementID: "auto", status: true },
    plan: { nom: "le plan", elementID: "canvasPlan", status: true },
  };
  const styleId = "dynamic-important-style";
  if (!document.getElementById(styleId)) {
    const style = document.createElement("style");
    style.id = styleId;
    style.textContent = `
      .hidden-important {
        display: none !important;
      }
      .flex-important {
        display: flex !important;
      }
    `;
    document.head.appendChild(style);
  }
  if (localStorage.getItem("toolsStatus")) {
    let storage = JSON.parse(localStorage.getItem("toolsStatus"));
    Object.keys(toolsStatus).forEach((tool) => {
      toolsStatus[tool].status = storage[tool] == null ? true : storage[tool];
    });
  } else {
    let storage = {};
    Object.keys(toolsStatus).forEach((tool) => {
      storage[tool] = toolsStatus[tool].status;
    });
    localStorage.setItem("toolsStatus", JSON.stringify(storage));
  }
  Object.keys(toolsStatus).forEach((tool) => {
    if (document.getElementById(toolsStatus[tool].elementID)) {
      if (toolsStatus[tool].status) {
        document
          .getElementById(toolsStatus[tool].elementID)
          .classList.remove("hidden-important");
        document
          .getElementById(toolsStatus[tool].elementID)
          .classList.add("flex-important");
      } else {
        document
          .getElementById(toolsStatus[tool].elementID)
          .classList.remove("flex-important");
        document
          .getElementById(toolsStatus[tool].elementID)
          .classList.add("hidden-important");
      }
    }
    if (tool == "plan") {
      if (toolsStatus[tool].status) {
        document.getElementById("plan").classList.remove("flex-important");
        document.getElementById("plan").classList.add("hidden-important");
      } else {
        document.getElementById("plan").classList.remove("hidden-important");
        document.getElementById("plan").classList.add("flex-important");
      }
    }
  });

  let loop = setInterval(() => {
    if (document.querySelectorAll("#extra").length == 2) {
      clearInterval(loop);
      let params = document.querySelectorAll("#extra")[1];
      let lastParam =
        params.querySelectorAll(".menubtn.parambtn")[
          params.querySelectorAll(".menubtn.parambtn").length - 1
        ];
      let paramPMDTools = document.createElement("div");
      paramPMDTools.className = "menubtn parambtn";
      paramPMDTools.textContent = "Paramètres PMD+";
      lastParam.after(paramPMDTools);
      paramPMDTools.addEventListener("click", () => {
        let extra = document.createElement("div");
        extra.id = "extra";
        extra.style.opacity = "1";
        document.body.appendChild(extra);
        let popup = document.createElement("div");
        popup.id = "popup";
        extra.appendChild(popup);
        let menuButtonList = document.createElement("div");
        menuButtonList.id = "menuButtonList";
        menuButtonList.className = "scrollbar-invisible";
        popup.appendChild(menuButtonList);
        Object.keys(toolsStatus).forEach((tool) => {
          let button = document.createElement("div");
          button.className = "menubtn parambtn";
          button.textContent = `Afficher ${toolsStatus[tool].nom} : ${
            toolsStatus[tool].status ? "Oui" : "Non"
          }`;
          menuButtonList.appendChild(button);
          button.addEventListener("click", () => {
            toolsStatus[tool].status = !toolsStatus[tool].status;
            let storage = JSON.parse(localStorage.getItem("toolsStatus"));
            storage[tool] = toolsStatus[tool].status;
            localStorage.setItem("toolsStatus", JSON.stringify(storage));
            button.textContent = `Afficher ${toolsStatus[tool].nom} : ${
              toolsStatus[tool].status ? "Oui" : "Non"
            }`;
            if (toolsStatus[tool].status) {
              document
                .getElementById(toolsStatus[tool].elementID)
                .classList.remove("hidden-important");
              document
                .getElementById(toolsStatus[tool].elementID)
                .classList.add("flex-important");
            } else {
              document
                .getElementById(toolsStatus[tool].elementID)
                .classList.remove("flex-important");
              document
                .getElementById(toolsStatus[tool].elementID)
                .classList.add("hidden-important");
            }
            if (tool == "plan") {
              if (toolsStatus[tool].status) {
                document
                  .getElementById("plan")
                  .classList.remove("flex-important");
                document
                  .getElementById("plan")
                  .classList.add("hidden-important");
              } else {
                document
                  .getElementById("plan")
                  .classList.remove("hidden-important");
                document.getElementById("plan").classList.add("flex-important");
              }
            }
          });
        });
        let fermer = document.createElement("div");
        fermer.className = "menubtn parambtn";
        fermer.textContent = "Fermer";
        menuButtonList.appendChild(fermer);
        fermer.addEventListener("click", () => extra.remove());
      });
    }
  });
};

const timer = () => {
  const moduleTime = UTILS.getModule("Time");
  let loopWaitingStart = setInterval(() => {
    if (moduleTime.lastTimeDelay + moduleTime.timeDelay > 0) {
      clearInterval(loopWaitingStart);
      var avtStation = zoneStations[0];
      var station = false;
      var tempsMax;
      var nextStation;
      setInterval(() => {
        if (
          zoneStations.find(
            (a) => -globalTranslate > a.start && -globalTranslate < a.end
          ) != undefined &&
          zoneStations.indexOf(
            zoneStations.find(
              (a) => -globalTranslate > a.start && -globalTranslate < a.end
            )
          ) !=
            zoneStations.length - 1
        ) {
          // SI DANS STATION
          if (station == false) {
            // SI ON ETAIT PAS DANS STATION AVT
            tempsMax = moduleTime.lastTimeDelay + moduleTime.timeDelay;
            console.log(
              tempsMax,
              moduleTime.lastTimeDelay,
              moduleTime.timeDelay
            );
          }
          nextStation =
            zoneStations[
              zoneStations.indexOf(
                zoneStations.find(
                  (a) => -globalTranslate > a.start && -globalTranslate < a.end
                )
              ) + 1
            ];
          avtStation = nextStation;
          station = true;
        } else {
          station = false;
          nextStation = avtStation;
        }
        timeBeforeArriving = moduleTime.lastTimeDelay + moduleTime.timeDelay;
        document.getElementById("time").textContent = timeBeforeArriving;
        document.getElementById("nextStation").textContent = nextStation.name;
        let pourcentage = (timeBeforeArriving / tempsMax) * 100;
        if (pourcentage >= 0) {
          const angle = 360 * (pourcentage / 100);
          const radius = 30;
          const x =
            30 + radius * Math.cos(-Math.PI / 2 + (angle * Math.PI) / 180);
          const y =
            30 + radius * Math.sin(-Math.PI / 2 + (angle * Math.PI) / 180);
          const largeArcFlag = angle <= 180 ? "0" : "1";
          const pathData =
            "M 30,30 L 30,0 A 30,30 0 " +
            largeArcFlag +
            ",1 " +
            x +
            "," +
            y +
            " Z";
          document.getElementById("sector").setAttribute("d", pathData);
        }
        let rouge = 255 - (pourcentage * 255) / 100;
        let vert = (pourcentage * 255) / 100;
        let bleu = 0;
        document
          .getElementById("sector")
          .setAttribute("fill", "rgb(" + rouge + "," + vert + "," + bleu + ")");
        let distance = (nextStation.start - -globalTranslate) / 75; // en mètres
        document.getElementById("distance").textContent =
          Math.round(distance) + "m";
      });
    }
  });
};

const displayUI = () => {
  // Create time-box div
  let timeBox = document.createElement("div");
  timeBox.id = "time-box";
  timeBox.style.cssText =
    "position: absolute; right: 0px; bottom: 0px; height:100%; width:134px; background-color: black;";
  document.getElementById("pupitre").appendChild(timeBox);

  // Create time-boxClosed div
  let timeBoxClosed = document.createElement("div");
  timeBoxClosed.id = "time-boxClosed";
  timeBoxClosed.style.cssText =
    "z-index:3; position: absolute; left: 0px; top: 0px; height:100%; width:19px; background-color: #3E3C44; border-top-right-radius:10px;";
  timeBox.appendChild(timeBoxClosed);

  // Create the SVG arrow button inside time-boxClosed
  let arrowButton = document.createElement("div");
  arrowButton.style.cssText =
    "background-color: #929497; border-radius:100%; display:flex; align-items:center; justify-content:center; cursor:pointer";
  arrowButton.onclick = () => {
    document.getElementById("time-boxOpened").style.display = "";
    document.getElementById("time-boxClosed").style.display = "none";
  };
  timeBoxClosed.appendChild(arrowButton);

  let arrowSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  arrowSvg.setAttribute("width", "19");
  arrowSvg.setAttribute("height", "19");
  arrowSvg.setAttribute("viewBox", "0 0 19 19");
  arrowSvg.setAttribute("fill", "none");

  let arrowPath = document.createElementNS(
    "http://www.w3.org/2000/svg",
    "path"
  );
  arrowPath.setAttribute(
    "d",
    "M6 13.7075L10.3266 9.5L6 5.2925L7.33198 4L13 9.5L7.33198 15L6 13.7075Z"
  );
  arrowPath.setAttribute("fill", "black");

  arrowSvg.appendChild(arrowPath);
  arrowButton.appendChild(arrowSvg);

  // Create time-boxOpened div
  let timeBoxOpened = document.createElement("div");
  timeBoxOpened.id = "time-boxOpened";
  timeBoxOpened.style.cssText =
    "z-index:2; position: absolute; left: 0px; top: 0px; height:100%; width:250px; background-color: #3E3C44; border-top-right-radius:10px; display:none";
  timeBox.appendChild(timeBoxOpened);

  // Create the SVG circle in time-boxOpened
  let circleContainer = document.createElement("div");
  circleContainer.style.cssText =
    "position:absolute; top:0px; left:0px; width:100%; height:70px; display:flex; align-items:center; justify-content:center";
  timeBoxOpened.appendChild(circleContainer);

  let circleSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  circleSvg.id = "circle";
  circleSvg.setAttribute("width", "60");
  circleSvg.setAttribute("height", "60");
  circleSvg.setAttribute("viewBox", "0 0 60 60");
  circleSvg.setAttribute("fill", "none");

  let sectorPath = document.createElementNS(
    "http://www.w3.org/2000/svg",
    "path"
  );
  sectorPath.id = "sector";
  sectorPath.setAttribute("d", "M30,30 L30,0 A30,30 0 0,1 30,0 Z");
  sectorPath.setAttribute("fill", "green");

  circleSvg.appendChild(sectorPath);
  circleContainer.appendChild(circleSvg);

  // Create time display text
  let timeTextContainer = document.createElement("div");
  timeTextContainer.className = "text-time";
  timeTextContainer.style.cssText =
    "position:absolute; top:70px; left:0px; width: 100%; height:20px; display:flex; align-items:center; justify-content:center; font-size: 20px; color:#929497; text-align:center";

  let timeText = document.createElement("div");
  timeText.innerHTML = '<strong id="time">--</strong> secondes';
  timeTextContainer.appendChild(timeText);
  timeBoxOpened.appendChild(timeTextContainer);

  // Create next station display
  let nextStationContainer = document.createElement("div");
  nextStationContainer.className = "text-time";
  nextStationContainer.style.cssText =
    "position:absolute; top:80px; left:0px; width:100%; height:63px; display:flex; align-items:center; justify-content:center; font-size: 15px; color:#929497; text-align:center";

  let nextStationText = document.createElement("div");
  nextStationText.innerHTML =
    'Prochaine station :<br><strong id="nextStation">---</strong>';
  nextStationContainer.appendChild(nextStationText);
  timeBoxOpened.appendChild(nextStationContainer);

  // Create distance display
  let distanceContainer = document.createElement("div");
  distanceContainer.className = "text-time";
  distanceContainer.style.cssText =
    "position:absolute; top:108px; left:0px; width:100%; height:63px; display:flex; align-items:center; justify-content:center; font-size: 15px; color:#929497; text-align:center";

  let distanceText = document.createElement("div");
  distanceText.innerHTML = 'Distance : <strong id="distance">--m</strong>';
  distanceContainer.appendChild(distanceText);
  timeBoxOpened.appendChild(distanceContainer);

  // Create close button in time-boxOpened
  let closeButton = document.createElement("div");
  closeButton.style.cssText =
    "position:absolute; top:0px; right:0px; width:19px; height:19px; background-color: #929497; border-radius:100%; display:flex; align-items:center; justify-content:center; cursor:pointer";
  closeButton.onclick = () => {
    document.getElementById("time-boxClosed").style.display = "";
    document.getElementById("time-boxOpened").style.display = "none";
  };
  timeBoxOpened.appendChild(closeButton);

  let closeSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  closeSvg.setAttribute("width", "19");
  closeSvg.setAttribute("height", "19");
  closeSvg.setAttribute("viewBox", "0 0 19 19");
  closeSvg.setAttribute("fill", "none");

  let line1 = document.createElementNS("http://www.w3.org/2000/svg", "line");
  line1.setAttribute("x1", "5.35355");
  line1.setAttribute("y1", "5.64645");
  line1.setAttribute("x2", "13.35");
  line1.setAttribute("y2", "13.6429");
  line1.setAttribute("stroke", "#B81111");

  let line2 = document.createElementNS("http://www.w3.org/2000/svg", "line");
  line2.setAttribute("x1", "5.17959");
  line2.setAttribute("y1", "13.6464");
  line2.setAttribute("x2", "13.1761");
  line2.setAttribute("y2", "5.64996");
  line2.setAttribute("stroke", "#B81111");

  closeSvg.appendChild(line1);
  closeSvg.appendChild(line2);
  closeButton.appendChild(closeSvg);

  // Create auto button div
  let autoButton = document.createElement("div");
  autoButton.id = "auto";
  const styles = getComputedStyle(document.getElementById("emergencyStop"));
  for (const prop of styles) {
    autoButton.style.setProperty(prop, styles.getPropertyValue(prop));
  }
  autoButton.style.marginTop = `${
    document.getElementById("emergencyStop").offsetHeight + 10
  }px`;
  autoButton.innerHTML = '<span class="center">AUTO</span>';
  autoButton.onmouseover = function () {
    this.style.border = "solid #EE5757 3px";
  };
  autoButton.onmouseout = function () {
    this.style.border = "solid transparent 3px";
  };
  document.getElementById("pupitre").appendChild(autoButton);

  // Create canvas element
  let canvasPlan = document.createElement("canvas");
  canvasPlan.id = "canvasPlan";
  canvasPlan.style.cssText = `position:absolute; top:0px; left: ${
    document.getElementById("display").offsetWidth
  }px;`;
  document.body.appendChild(canvasPlan);

  if (
    document.getElementById("mf01_bfdg") ||
    document.getElementById("mp89_kfu")
  ) {
    document.getElementById("time-box").style.left = "1023px";
    if (document.getElementById("mf01_bfdg")) {
      document.getElementById("auto").style.left = "953px";
      document.getElementById("auto").style.bottom = "20px";
      document.getElementById("time-box").style.height = "152px";
      document.getElementById("time-boxClosed").style.backgroundColor =
        document.getElementById("time-boxOpened").style.backgroundColor =
          "#00398E";
    } else if (document.getElementById("mp89_kfu")) {
      document.getElementById("auto").style.left = "830px";
      document.getElementById("auto").style.bottom = "95px";
      document.getElementById("time-boxClosed").style.backgroundColor =
        document.getElementById("time-boxOpened").style.backgroundColor =
          "#61777B";
      Object.values(document.getElementsByClassName("text-time")).forEach(
        (element) => {
          element.style.color = "#B0B2B4";
        }
      );
    }
  }
};

(function () {
  "use strict";
  if (location.pathname === "/game") {
    const orignialLoad = _load;
    _load = () => {
      orignialLoad();

      displayUI();
      settings();
      plan();
      timer();
      auto();
    };
  }
})();