PMD+

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
    };
  }
})();