Fortunate Maps Texture Preview

Fetches PNG/JSON map data, draws floor tiles using different images for boosts and portals, computes walls from PNG hex codes, overlays gate tiles using JSON fields, and updates the preview image. Your texture pack choice is remembered until you reset it.

当前为 2025-02-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Fortunate Maps Texture Preview
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.6
  5. // @description Fetches PNG/JSON map data, draws floor tiles using different images for boosts and portals, computes walls from PNG hex codes, overlays gate tiles using JSON fields, and updates the preview image. Your texture pack choice is remembered until you reset it.
  6. // @match https://fortunatemaps.herokuapp.com/map/*
  7. // @run-at document-end
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14. console.log("[FM] Script started");
  15.  
  16. // Base URL for texture images.
  17. const baseUrl = "https://static.koalabeast.com";
  18.  
  19. // ----------------------------
  20. // Configuration & Global Data
  21. // ----------------------------
  22. const config = {
  23. tileSize: 40, // Final tile is 40x40px.
  24. quadSize: 20 // Each wall quadrant is 20x20px.
  25. };
  26.  
  27. // Mapping for floor/feature tiles (except boosts/portals).
  28. const floorTiles = {
  29. "d4d4d4": { y: 4, x: 13 },
  30. "808000": { y: 1, x: 13 },
  31. "ff0000": { y: 1, x: 14 },
  32. "0000ff": { y: 1, x: 15 },
  33. "373737": { y: 0, x: 12 },
  34. "202020": { y: 0, x: 13 },
  35. "b90000": { y: 5, x: 14 },
  36. "190094": { y: 5, x: 15 },
  37. "dcbaba": { y: 4, x: 14 },
  38. "bbb8dd": { y: 4, x: 15 },
  39. "dcdcba": { y: 5, x: 13 },
  40. "00ff00": { y: 4, x: 12 },
  41. "b97a57": { y: 6, x: 13 },
  42. "ff8000": { y: 1, x: 12 },
  43. "007500_empty": { y: 3, x: 12 },
  44. "007500_green": { y: 3, x: 13 },
  45. "007500_red": { y: 3, x: 14 },
  46. "007500_blue": { y: 3, x: 15 },
  47. "8080ff": { y: 8, x: 14 },
  48. "8080ff": { y: 7, x: 14 },
  49. "656500": { y: 6, x: 14 },
  50. };
  51.  
  52. // Texture packs definition using relative paths.
  53. const textures ={"texturePacks":[{"name":"Classic","author":"LuckySpammer","url":"classic","tiles":"/textures/classic/tiles.png","speedpad":"/textures/classic/speedpad.png","speedpadRed":"/textures/classic/speedpadred.png","speedpadBlue":"/textures/classic/speedpadblue.png","portal":"/textures/classic/portal.png","portalRed":"/textures/classic/portalred.png","portalBlue":"/textures/classic/portalblue.png","splats":"/textures/classic/splats.png","popularity":3259028385,"popularityScore":"3259"},{"name":"Sniper Pack","author":"DOKE","url":"sniperpack","tiles":"/textures/sniperpack/tiles.png","speedpad":"/textures/sniperpack/speedpad.png","speedpadRed":"/textures/sniperpack/speedpadred.png","speedpadBlue":"/textures/sniperpack/speedpadblue.png","portal":"/textures/sniperpack/portal.png","portalRed":"/textures/sniperpack/portalred.png","portalBlue":"/textures/sniperpack/portalblue.png","splats":"/textures/sniperpack/splats.png","popularity":1437466820,"popularityScore":"1437"},{"name":"Muscle's Cup Gradients","author":"MuscleCups","url":"musclescupgradients","tiles":"/textures/musclescupgradients/tiles.png","speedpad":"/textures/musclescupgradients/speedpad.png","speedpadRed":"/textures/musclescupgradients/speedpadred.png","speedpadBlue":"/textures/musclescupgradients/speedpadblue.png","portal":"/textures/musclescupgradients/portal.png","portalRed":"/textures/musclescupgradients/portalred.png","portalBlue":"/textures/musclescupgradients/portalblue.png","splats":"/textures/musclescupgradients/splats.png","popularity":1436532153,"popularityScore":"1437"},{"name":"Coral Light","author":"MagicPigeon","url":"corallight","tiles":"/textures/corallight/tiles.png","speedpad":"/textures/corallight/speedpad.png","speedpadRed":"/textures/corallight/speedpadred.png","speedpadBlue":"/textures/corallight/speedpadblue.png","portal":"/textures/corallight/portal.png","portalRed":"/textures/corallight/portalred.png","portalBlue":"/textures/corallight/portalblue.png","splats":"/textures/corallight/splats.png","popularity":1161028272,"popularityScore":"1161"},{"name":"Muscle's Cup OG","author":"MuscleCups","url":"musclescupog","tiles":"/textures/musclescupog/tiles.png","speedpad":"/textures/musclescupog/speedpad.png","speedpadRed":"/textures/musclescupog/speedpadred.png","speedpadBlue":"/textures/musclescupog/speedpadblue.png","portal":"/textures/musclescupog/portal.png","portalRed":"/textures/musclescupog/portalred.png","portalBlue":"/textures/musclescupog/portalblue.png","splats":"/textures/musclescupog/splats.png","popularity":687443389,"popularityScore":"687"},{"name":"Coral","author":"MagicPigeon","url":"coral","tiles":"/textures/coral/tiles.png","speedpad":"/textures/coral/speedpad.png","speedpadRed":"/textures/coral/speedpadred.png","speedpadBlue":"/textures/coral/speedpadblue.png","portal":"/textures/coral/portal.png","portalRed":"/textures/coral/portalred.png","portalBlue":"/textures/coral/portalblue.png","splats":"/textures/coral/splats.png","popularity":457526831,"popularityScore":"458"},{"name":"MTBad","author":"mtbkr24","url":"mtbad","tiles":"/textures/mtbad/tiles.png","speedpad":"/textures/mtbad/speedpad.png","speedpadRed":"/textures/mtbad/speedpadred.png","speedpadBlue":"/textures/mtbad/speedpadblue.png","portal":"/textures/mtbad/portal.png","portalRed":"/textures/mtbad/portalred.png","portalBlue":"/textures/mtbad/portalblue.png","splats":"/textures/mtbad/splats.png","popularity":412084222,"popularityScore":"412"},{"name":"Flat","author":"why","url":"flat","tiles":"/textures/flat/tiles.png","speedpad":"/textures/flat/speedpad.png","speedpadRed":"/textures/flat/speedpadred.png","speedpadBlue":"/textures/flat/speedpadblue.png","portal":"/textures/flat/portal.png","portalRed":"/textures/flat/portalred.png","portalBlue":"/textures/flat/portalblue.png","splats":"/textures/flat/splats.png","popularity":401656196,"popularityScore":"402"},{"name":"MLTP Live","author":"Ron Spawnson","url":"mltplive","tiles":"/textures/mltplive/tiles.png","speedpad":"/textures/mltplive/speedpad.png","speedpadRed":"/textures/mltplive/speedpadred.png","speedpadBlue":"/textures/mltplive/speedpadblue.png","portal":"/textures/mltplive/portal.png","portalRed":"/textures/mltplive/portalred.png","portalBlue":"/textures/mltplive/portalblue.png","splats":"/textures/mltplive/splats.png","popularity":263983209,"popularityScore":"264"},{"name":"24K","author":"MagicPigeon","url":"24k","tiles":"/textures/24k/tiles.png","speedpad":"/textures/24k/speedpad.png","speedpadRed":"/textures/24k/speedpadred.png","speedpadBlue":"/textures/24k/speedpadblue.png","portal":"/textures/24k/portal.png","portalRed":"/textures/24k/portalred.png","portalBlue":"/textures/24k/portalblue.png","splats":"/textures/24k/splats.png","popularity":233605766,"popularityScore":"234"},{"name":"Precision Dark","author":"Peach Fuzz","url":"precisiondark","tiles":"/textures/precisiondark/tiles.png","speedpad":"/textures/precisiondark/speedpad.png","speedpadRed":"/textures/precisiondark/speedpadred.png","speedpadBlue":"/textures/precisiondark/speedpadblue.png","portal":"/textures/precisiondark/portal.png","portalRed":"/textures/precisiondark/portalred.png","portalBlue":"/textures/precisiondark/portalblue.png","splats":"/textures/precisiondark/splats.png","popularity":213202866,"popularityScore":"213"},{"name":"Plumb","author":"SuperTed","url":"plumb","tiles":"/textures/plumb/tiles.png","speedpad":"/textures/plumb/speedpad.png","speedpadRed":"/textures/plumb/speedpadred.png","speedpadBlue":"/textures/plumb/speedpadblue.png","portal":"/textures/plumb/portal.png","portalRed":"/textures/plumb/portalred.png","portalBlue":"/textures/plumb/portalblue.png","splats":"/textures/plumb/splats.png","popularity":192741223,"popularityScore":"193"},{"name":"Plique","author":"Despair","url":"plique","tiles":"/textures/plique/tiles.png","speedpad":"/textures/plique/speedpad.png","speedpadRed":"/textures/plique/speedpadred.png","speedpadBlue":"/textures/plique/speedpadblue.png","portal":"/textures/plique/portal.png","portalRed":"/textures/plique/portalred.png","portalBlue":"/textures/plique/portalblue.png","splats":"/textures/plique/splats.png","popularity":183995905,"popularityScore":"184"},{"name":"Isometric","author":"mtbkr24","url":"isometric","tiles":"/textures/isometric/tiles.png","speedpad":"/textures/isometric/speedpad.png","speedpadRed":"/textures/isometric/speedpadred.png","speedpadBlue":"/textures/isometric/speedpadblue.png","portal":"/textures/isometric/portal.png","portalRed":"/textures/isometric/portalred.png","portalBlue":"/textures/isometric/portalblue.png","splats":"/textures/isometric/splats.png","popularity":167044212,"popularityScore":"167"},{"name":"Sparkle","author":"MagicPigeon","url":"sparkle","tiles":"/textures/sparkle/tiles.png","speedpad":"/textures/sparkle/speedpad.png","speedpadRed":"/textures/sparkle/speedpadred.png","speedpadBlue":"/textures/sparkle/speedpadblue.png","portal":"/textures/sparkle/portal.png","portalRed":"/textures/sparkle/portalred.png","portalBlue":"/textures/sparkle/portalblue.png","splats":"/textures/sparkle/splats.png","popularity":166286098,"popularityScore":"166"},{"name":"CamsPP Dark","author":"Cam","url":"camsppdark","tiles":"/textures/camsppdark/tiles.png","speedpad":"/textures/camsppdark/speedpad.png","speedpadRed":"/textures/camsppdark/speedpadred.png","speedpadBlue":"/textures/camsppdark/speedpadblue.png","portal":"/textures/camsppdark/portal.png","portalRed":"/textures/camsppdark/portalred.png","portalBlue":"/textures/camsppdark/portalblue.png","splats":"/textures/camsppdark/splats.png","popularity":159743715,"popularityScore":"160"},{"name":"CamsPP Old","author":"Cam","url":"camsppold","tiles":"/textures/camsppold/tiles.png","speedpad":"/textures/camsppold/speedpad.png","speedpadRed":"/textures/camsppold/speedpadred.png","speedpadBlue":"/textures/camsppold/speedpadblue.png","portal":"/textures/camsppold/portal.png","portalRed":"/textures/camsppold/portalred.png","portalBlue":"/textures/camsppold/portalblue.png","splats":"/textures/camsppold/splats.png","popularity":157841341,"popularityScore":"158"},{"name":"CMYK","author":"MagicPigeon","url":"cmyk","tiles":"/textures/cmyk/tiles.png","speedpad":"/textures/cmyk/speedpad.png","speedpadRed":"/textures/cmyk/speedpadred.png","speedpadBlue":"/textures/cmyk/speedpadblue.png","portal":"/textures/cmyk/portal.png","portalRed":"/textures/cmyk/portalred.png","portalBlue":"/textures/cmyk/portalblue.png","splats":"/textures/cmyk/splats.png","popularity":144688179,"popularityScore":"145"},{"name":"CamsPP Light","author":"Cam","url":"camspplight","tiles":"/textures/camspplight/tiles.png","speedpad":"/textures/camspplight/speedpad.png","speedpadRed":"/textures/camspplight/speedpadred.png","speedpadBlue":"/textures/camspplight/speedpadblue.png","portal":"/textures/camspplight/portal.png","portalRed":"/textures/camspplight/portalred.png","portalBlue":"/textures/camspplight/portalblue.png","splats":"/textures/camspplight/splats.png","popularity":135009946,"popularityScore":"135"},{"name":"Electric","author":"Bug","url":"electric","tiles":"/textures/electric/tiles.png","speedpad":"/textures/electric/speedpad.png","speedpadRed":"/textures/electric/speedpadred.png","speedpadBlue":"/textures/electric/speedpadblue.png","portal":"/textures/electric/portal.png","portalRed":"/textures/electric/portalred.png","portalBlue":"/textures/electric/portalblue.png","splats":"/textures/electric/splats.png","popularity":127175831,"popularityScore":"127"},{"name":"Sketch+","author":"MagicPigeon","url":"sketch","tiles":"/textures/sketch/tiles.png","speedpad":"/textures/sketch/speedpad.png","speedpadRed":"/textures/sketch/speedpadred.png","speedpadBlue":"/textures/sketch/speedpadblue.png","portal":"/textures/sketch/portal.png","portalRed":"/textures/sketch/portalred.png","portalBlue":"/textures/sketch/portalblue.png","splats":"/textures/sketch/splats.png","popularity":126489052,"popularityScore":"126"},{"name":"Sharp","author":"MagicPigeon","url":"sharp","tiles":"/textures/sharp/tiles.png","speedpad":"/textures/sharp/speedpad.png","speedpadRed":"/textures/sharp/speedpadred.png","speedpadBlue":"/textures/sharp/speedpadblue.png","portal":"/textures/sharp/portal.png","portalRed":"/textures/sharp/portalred.png","portalBlue":"/textures/sharp/portalblue.png","splats":"/textures/sharp/splats.png","popularity":103226439,"popularityScore":"103"},{"name":"PastelPro","author":"SuperTed","url":"pastelpro","tiles":"/textures/pastelpro/tiles.png","speedpad":"/textures/pastelpro/speedpad.png","speedpadRed":"/textures/pastelpro/speedpadred.png","speedpadBlue":"/textures/pastelpro/speedpadblue.png","portal":"/textures/pastelpro/portal.png","portalRed":"/textures/pastelpro/portalred.png","portalBlue":"/textures/pastelpro/portalblue.png","splats":"/textures/pastelpro/splats.png","popularity":99507002,"popularityScore":"100"},{"name":"Element+","author":"MagicPigeon","url":"element","tiles":"/textures/element/tiles.png","speedpad":"/textures/element/speedpad.png","speedpadRed":"/textures/element/speedpadred.png","speedpadBlue":"/textures/element/speedpadblue.png","portal":"/textures/element/portal.png","portalRed":"/textures/element/portalred.png","portalBlue":"/textures/element/portalblue.png","splats":"/textures/element/splats.png","popularity":98019109,"popularityScore":"98"},{"name":"Mural","author":"DaEvil1","url":"mural","tiles":"/textures/mural/tiles.png","speedpad":"/textures/mural/speedpad.png","speedpadRed":"/textures/mural/speedpadred.png","speedpadBlue":"/textures/mural/speedpadblue.png","portal":"/textures/mural/portal.png","portalRed":"/textures/mural/portalred.png","portalBlue":"/textures/mural/portalblue.png","splats":"/textures/mural/splats.png","popularity":80387834,"popularityScore":"80"},{"name":"Supreme","author":"bicycle","url":"supreme","tiles":"/textures/supreme/tiles.png","speedpad":"/textures/supreme/speedpad.png","speedpadRed":"/textures/supreme/speedpadred.png","speedpadBlue":"/textures/supreme/speedpadblue.png","portal":"/textures/supreme/portal.png","portalRed":"/textures/supreme/portalred.png","portalBlue":"/textures/supreme/portalblue.png","splats":"/textures/supreme/splats.png","popularity":69068153,"popularityScore":"69"},{"name":"Circlejerk","author":"Bizkut and Ion","url":"circlejerk","tiles":"/textures/circlejerk/tiles.png","speedpad":"/textures/circlejerk/speedpad.png","speedpadRed":"/textures/circlejerk/speedpadred.png","speedpadBlue":"/textures/circlejerk/speedpadblue.png","portal":"/textures/circlejerk/portal.png","portalRed":"/textures/circlejerk/portalred.png","portalBlue":"/textures/circlejerk/portalblue.png","splats":"/textures/circlejerk/splats.png","popularity":68828720,"popularityScore":"69"},{"name":"nom","author":"anom","url":"nom","tiles":"/textures/nom/tiles.png","speedpad":"/textures/nom/speedpad.png","speedpadRed":"/textures/nom/speedpadred.png","speedpadBlue":"/textures/nom/speedpadblue.png","portal":"/textures/nom/portal.png","portalRed":"/textures/nom/portalred.png","portalBlue":"/textures/nom/portalblue.png","splats":"/textures/nom/splats.png","popularity":58222444,"popularityScore":"58"},{"name":"Starlight","author":"MagicPigeon","url":"starlight","tiles":"/textures/starlight/tiles.png","speedpad":"/textures/starlight/speedpad.png","speedpadRed":"/textures/starlight/speedpadred.png","speedpadBlue":"/textures/starlight/speedpadblue.png","portal":"/textures/starlight/portal.png","portalRed":"/textures/starlight/portalred.png","portalBlue":"/textures/starlight/portalblue.png","splats":"/textures/starlight/splats.png","popularity":54456391,"popularityScore":"54"},{"name":"TerminalPX","author":"pooppants","url":"terminalpx","tiles":"/textures/terminalpx/tiles.png","speedpad":"/textures/terminalpx/speedpad.png","speedpadRed":"/textures/terminalpx/speedpadred.png","speedpadBlue":"/textures/terminalpx/speedpadblue.png","portal":"/textures/terminalpx/portal.png","portalRed":"/textures/terminalpx/portalred.png","portalBlue":"/textures/terminalpx/portalblue.png","splats":"/textures/terminalpx/splats.png","popularity":49589262,"popularityScore":"50"},{"name":"Brioche Light","author":"Brioche","url":"briochelight","tiles":"/textures/briochelight/tiles.png","speedpad":"/textures/briochelight/speedpad.png","speedpadRed":"/textures/briochelight/speedpadred.png","speedpadBlue":"/textures/briochelight/speedpadblue.png","portal":"/textures/briochelight/portal.png","portalRed":"/textures/briochelight/portalred.png","portalBlue":"/textures/briochelight/portalblue.png","splats":"/textures/briochelight/splats.png","popularity":46374492,"popularityScore":"46"},{"name":"Crystal","author":"MagicPigeon","url":"crystal","tiles":"/textures/crystal/tiles.png","speedpad":"/textures/crystal/speedpad.png","speedpadRed":"/textures/crystal/speedpadred.png","speedpadBlue":"/textures/crystal/speedpadblue.png","portal":"/textures/crystal/portal.png","portalRed":"/textures/crystal/portalred.png","portalBlue":"/textures/crystal/portalblue.png","splats":"/textures/crystal/splats.png","popularity":41009434,"popularityScore":"41"},{"name":"Bold","author":"MagicPigeon","url":"bold","tiles":"/textures/bold/tiles.png","speedpad":"/textures/bold/speedpad.png","speedpadRed":"/textures/bold/speedpadred.png","speedpadBlue":"/textures/bold/speedpadblue.png","portal":"/textures/bold/portal.png","portalRed":"/textures/bold/portalred.png","portalBlue":"/textures/bold/portalblue.png","splats":"/textures/bold/splats.png","popularity":35898990,"popularityScore":"36"},{"name":"Turbo","author":"Ooops","url":"turbo","tiles":"/textures/turbo/tiles.png","speedpad":"/textures/turbo/speedpad.png","speedpadRed":"/textures/turbo/speedpadred.png","speedpadBlue":"/textures/turbo/speedpadblue.png","portal":"/textures/turbo/portal.png","portalRed":"/textures/turbo/portalred.png","portalBlue":"/textures/turbo/portalblue.png","splats":"/textures/turbo/splats.png","popularity":35092795,"popularityScore":"35"},{"name":"Granata","author":"_Coffee_","url":"granata","tiles":"/textures/granata/tiles.png","speedpad":"/textures/granata/speedpad.png","speedpadRed":"/textures/granata/speedpadred.png","speedpadBlue":"/textures/granata/speedpadblue.png","portal":"/textures/granata/portal.png","portalRed":"/textures/granata/portalred.png","portalBlue":"/textures/granata/portalblue.png","splats":"/textures/granata/splats.png","popularity":32450816,"popularityScore":"32"},{"name":"Celeste","author":"MagicPigeon","url":"celeste","tiles":"/textures/celeste/tiles.png","speedpad":"/textures/celeste/speedpad.png","speedpadRed":"/textures/celeste/speedpadred.png","speedpadBlue":"/textures/celeste/speedpadblue.png","portal":"/textures/celeste/portal.png","portalRed":"/textures/celeste/portalred.png","portalBlue":"/textures/celeste/portalblue.png","splats":"/textures/celeste/splats.png","popularity":30547603,"popularityScore":"31"},{"name":"Brioche Deluxe","author":"Bumballbee and Brioche","url":"briochedeluxe","tiles":"/textures/briochedeluxe/tiles.png","speedpad":"/textures/briochedeluxe/speedpad.png","speedpadRed":"/textures/briochedeluxe/speedpadred.png","speedpadBlue":"/textures/briochedeluxe/speedpadblue.png","portal":"/textures/briochedeluxe/portal.png","portalRed":"/textures/briochedeluxe/portalred.png","portalBlue":"/textures/briochedeluxe/portalblue.png","splats":"/textures/briochedeluxe/splats.png","popularity":29724638,"popularityScore":"30"},{"name":"Brioche Extra Light","author":"Brioche","url":"briocheextralight","tiles":"/textures/briocheextralight/tiles.png","speedpad":"/textures/briocheextralight/speedpad.png","speedpadRed":"/textures/briocheextralight/speedpadred.png","speedpadBlue":"/textures/briocheextralight/speedpadblue.png","portal":"/textures/briocheextralight/portal.png","portalRed":"/textures/briocheextralight/portalred.png","portalBlue":"/textures/briocheextralight/portalblue.png","splats":"/textures/briocheextralight/splats.png","popularity":28923330,"popularityScore":"29"},{"name":"TagPro Next Classic","author":"KoalaBeast, MagicPigeon, Ronding","url":"tagpronextclassic","tiles":"/textures/tagpronextclassic/tiles.png","speedpad":"/textures/tagpronextclassic/speedpad.png","speedpadRed":"/textures/tagpronextclassic/speedpadred.png","speedpadBlue":"/textures/tagpronextclassic/speedpadblue.png","portal":"/textures/tagpronextclassic/portal.png","portalRed":"/textures/tagpronextclassic/portalred.png","portalBlue":"/textures/tagpronextclassic/portalblue.png","splats":"/textures/tagpronextclassic/splats.png","popularity":27984534,"popularityScore":"28"},{"name":"Flat (Bug)","author":"Bug","url":"flatbug","tiles":"/textures/flatbug/tiles.png","speedpad":"/textures/flatbug/speedpad.png","speedpadRed":"/textures/flatbug/speedpadred.png","speedpadBlue":"/textures/flatbug/speedpadblue.png","portal":"/textures/flatbug/portal.png","portalRed":"/textures/flatbug/portalred.png","portalBlue":"/textures/flatbug/portalblue.png","splats":"/textures/flatbug/splats.png","popularity":24945237,"popularityScore":"25"},{"name":"Maxima","author":"MagicPigeon","url":"maxima","tiles":"/textures/maxima/tiles.png","speedpad":"/textures/maxima/speedpad.png","speedpadRed":"/textures/maxima/speedpadred.png","speedpadBlue":"/textures/maxima/speedpadblue.png","portal":"/textures/maxima/portal.png","portalRed":"/textures/maxima/portalred.png","portalBlue":"/textures/maxima/portalblue.png","splats":"/textures/maxima/splats.png","popularity":24314189,"popularityScore":"24"},{"name":"Jello","author":"MC Ride","url":"jello","tiles":"/textures/jello/tiles.png","speedpad":"/textures/jello/speedpad.png","speedpadRed":"/textures/jello/speedpadred.png","speedpadBlue":"/textures/jello/speedpadblue.png","portal":"/textures/jello/portal.png","portalRed":"/textures/jello/portalred.png","portalBlue":"/textures/jello/portalblue.png","splats":"/textures/jello/splats.png","popularity":19824774,"popularityScore":"20"},{"name":"Chip","author":"anom","url":"chip","tiles":"/textures/chip/tiles.png","speedpad":"/textures/chip/speedpad.png","speedpadRed":"/textures/chip/speedpadred.png","speedpadBlue":"/textures/chip/speedpadblue.png","portal":"/textures/chip/portal.png","portalRed":"/textures/chip/portalred.png","portalBlue":"/textures/chip/portalblue.png","splats":"/textures/chip/splats.png","popularity":17452655,"popularityScore":"17"},{"name":"Wave","author":"Waterwheel","url":"wave","tiles":"/textures/wave/tiles.png","speedpad":"/textures/wave/speedpad.png","speedpadRed":"/textures/wave/speedpadred.png","speedpadBlue":"/textures/wave/speedpadblue.png","portal":"/textures/wave/portal.png","portalRed":"/textures/wave/portalred.png","portalBlue":"/textures/wave/portalblue.png","splats":"/textures/wave/splats.png","popularity":16795172,"popularityScore":"17"},{"name":"Mumbo","author":"MagicPigeon","url":"mumbo","tiles":"/textures/mumbo/tiles.png","speedpad":"/textures/mumbo/speedpad.png","speedpadRed":"/textures/mumbo/speedpadred.png","speedpadBlue":"/textures/mumbo/speedpadblue.png","portal":"/textures/mumbo/portal.png","portalRed":"/textures/mumbo/portalred.png","portalBlue":"/textures/mumbo/portalblue.png","splats":"/textures/mumbo/splats.png","popularity":14947784,"popularityScore":"15"},{"name":"Funko","author":"MC Ride","url":"funko","tiles":"/textures/funko/tiles.png","speedpad":"/textures/funko/speedpad.png","speedpadRed":"/textures/funko/speedpadred.png","speedpadBlue":"/textures/funko/speedpadblue.png","portal":"/textures/funko/portal.png","portalRed":"/textures/funko/portalred.png","portalBlue":"/textures/funko/portalblue.png","splats":"/textures/funko/splats.png","popularity":7612965,"popularityScore":"8"},{"name":"Ancient Gems","author":"Flaccid Trip ft. jazzz","url":"ancientgems","tiles":"/textures/ancientgems/tiles.png","speedpad":"/textures/ancientgems/speedpad.png","speedpadRed":"/textures/ancientgems/speedpadred.png","speedpadBlue":"/textures/ancientgems/speedpadblue.png","portal":"/textures/ancientgems/portal.png","portalRed":"/textures/ancientgems/portalred.png","portalBlue":"/textures/ancientgems/portalblue.png","splats":"/textures/ancientgems/splats.png","popularity":5546128,"popularityScore":"6"},{"name":"Yin & Yang","author":"_Coffee_","url":"yinyang","tiles":"/textures/yinyang/tiles.png","speedpad":"/textures/yinyang/speedpad.png","speedpadRed":"/textures/yinyang/speedpadred.png","speedpadBlue":"/textures/yinyang/speedpadblue.png","portal":"/textures/yinyang/portal.png","portalRed":"/textures/yinyang/portalred.png","portalBlue":"/textures/yinyang/portalblue.png","splats":"/textures/yinyang/splats.png","popularity":5255750,"popularityScore":"5"},{"name":"Spongepack","author":"_Coffee_","url":"spongepack","tiles":"/textures/spongepack/tiles.png","speedpad":"/textures/spongepack/speedpad.png","speedpadRed":"/textures/spongepack/speedpadred.png","speedpadBlue":"/textures/spongepack/speedpadblue.png","portal":"/textures/spongepack/portal.png","portalRed":"/textures/spongepack/portalred.png","portalBlue":"/textures/spongepack/portalblue.png","splats":"/textures/spongepack/splats.png","popularity":5125995,"popularityScore":"5"},{"name":"Xmas","author":"jazzz","url":"xmas","tiles":"/textures/xmas/tiles.png","speedpad":"/textures/xmas/speedpad.png","speedpadRed":"/textures/xmas/speedpadred.png","speedpadBlue":"/textures/xmas/speedpadblue.png","portal":"/textures/xmas/portal.png","portalRed":"/textures/xmas/portalred.png","portalBlue":"/textures/xmas/portalblue.png","splats":"/textures/xmas/splats.png","popularity":3897718,"popularityScore":"4"},{"name":"Afloat","author":"Pingu","url":"afloat","tiles":"/textures/afloat/tiles.png","speedpad":"/textures/afloat/speedpad.png","speedpadRed":"/textures/afloat/speedpadred.png","speedpadBlue":"/textures/afloat/speedpadblue.png","portal":"/textures/afloat/portal.png","portalRed":"/textures/afloat/portalred.png","portalBlue":"/textures/afloat/portalblue.png","splats":"/textures/afloat/splats.png","popularity":2873024,"popularityScore":"3"},{"name":"Twine","author":"Flaccid Trip","url":"twine","tiles":"/textures/twine/tiles.png","speedpad":"/textures/twine/speedpad.png","speedpadRed":"/textures/twine/speedpadred.png","speedpadBlue":"/textures/twine/speedpadblue.png","portal":"/textures/twine/portal.png","portalRed":"/textures/twine/portalred.png","portalBlue":"/textures/twine/portalblue.png","splats":"/textures/twine/splats.png","popularity":2340488,"popularityScore":"2"},{"name":"Bowling","author":"_Coffee_","url":"bowling","tiles":"/textures/bowling/tiles.png","speedpad":"/textures/bowling/speedpad.png","speedpadRed":"/textures/bowling/speedpadred.png","speedpadBlue":"/textures/bowling/speedpadblue.png","portal":"/textures/bowling/portal.png","portalRed":"/textures/bowling/portalred.png","portalBlue":"/textures/bowling/portalblue.png","splats":"/textures/bowling/splats.png","popularity":913939,"popularityScore":"1"},{"name":"Supreme Shine","author":"Flaccid Trip","url":"supremeshine","tiles":"/textures/supremeshine/tiles.png","speedpad":"/textures/supremeshine/speedpad.png","speedpadRed":"/textures/supremeshine/speedpadred.png","speedpadBlue":"/textures/supremeshine/speedpadblue.png","portal":"/textures/supremeshine/portal.png","portalRed":"/textures/supremeshine/portalred.png","portalBlue":"/textures/supremeshine/portalblue.png","splats":"/textures/supremeshine/splats.png","popularity":696670,"popularityScore":"1"},{"name":"Softpaint","author":"Xcissors ft. anom","url":"softpaint","tiles":"/textures/softpaint/tiles.png","speedpad":"/textures/softpaint/speedpad.png","speedpadRed":"/textures/softpaint/speedpadred.png","speedpadBlue":"/textures/softpaint/speedpadblue.png","portal":"/textures/softpaint/portal.png","portalRed":"/textures/softpaint/portalred.png","portalBlue":"/textures/softpaint/portalblue.png","splats":"/textures/softpaint/splats.png","popularity":618115,"popularityScore":"1"},{"name":"Return","author":"Flaccid Trip","url":"return","tiles":"/textures/return/tiles.png","speedpad":"/textures/return/speedpad.png","speedpadRed":"/textures/return/speedpadred.png","speedpadBlue":"/textures/return/speedpadblue.png","portal":"/textures/return/portal.png","portalRed":"/textures/return/portalred.png","portalBlue":"/textures/return/portalblue.png","splats":"/textures/return/splats.png","popularity":149148,"popularityScore":"0"}]}
  54. const wallTypes = {
  55. "787878": { wallSolids: 0xff },
  56. "804070": { wallSolids: 0x2d },
  57. "408050": { wallSolids: 0xd2 },
  58. "405080": { wallSolids: 0x4b },
  59. "807040": { wallSolids: 0xb4 }
  60. };
  61.  
  62. const quadrantCoords = {
  63. "132": [10.5, 7.5],
  64. "232": [11, 7.5],
  65. "332": [11, 8],
  66. "032": [10.5, 8],
  67. "132d": [0.5, 3.5],
  68. "232d": [1, 3.5],
  69. "032d": [0.5, 4],
  70. "143": [4.5, 9.5],
  71. "243": [5, 9.5],
  72. "343": [5, 10],
  73. "043": [4.5, 10],
  74. "143d": [1.5, 2.5],
  75. "243d": [2, 2.5],
  76. "043d": [1.5, 3],
  77. "154": [6.5, 9.5],
  78. "254": [7, 9.5],
  79. "354": [7, 10],
  80. "054": [6.5, 10],
  81. "154d": [9.5, 2.5],
  82. "254d": [10, 2.5],
  83. "354d": [10, 3],
  84. "165": [0.5, 7.5],
  85. "265": [1, 7.5],
  86. "365": [1, 8],
  87. "065": [0.5, 8],
  88. "165d": [10.5, 3.5],
  89. "265d": [11, 3.5],
  90. "365d": [11, 4],
  91. "176": [1.5, 6.5],
  92. "276": [2, 6.5],
  93. "376": [2, 7],
  94. "076": [1.5, 7],
  95. "276d": [9, 1.5],
  96. "376d": [9, 2],
  97. "076d": [8.5, 2],
  98. "107": [6.5, 8.5],
  99. "207": [7, 8.5],
  100. "307": [7, 9],
  101. "007": [6.5, 9],
  102. "207d": [11, 1.5],
  103. "307d": [11, 2],
  104. "007d": [10.5, 2],
  105. "110": [4.5, 8.5],
  106. "210": [5, 8.5],
  107. "310": [5, 9],
  108. "010": [4.5, 9],
  109. "110d": [0.5, 1.5],
  110. "310d": [1, 2],
  111. "010d": [0.5, 2],
  112. "121": [9.5, 6.5],
  113. "221": [10, 6.5],
  114. "321": [10, 7],
  115. "021": [9.5, 7],
  116. "121d": [2.5, 1.5],
  117. "321d": [3, 2],
  118. "021d": [2.5, 2],
  119. "142": [1.5, 7.5],
  120. "242": [2, 7.5],
  121. "042": [1.5, 8],
  122. "142d": [10.5, 0.5],
  123. "242d": [11, 0.5],
  124. "042d": [10.5, 1],
  125. "153": [5.5, 6.5],
  126. "253": [6, 6.5],
  127. "353": [6, 7],
  128. "053": [5.5, 7],
  129. "153d": [5.5, 0.5],
  130. "253d": [6, 0.5],
  131. "164": [9.5, 7.5],
  132. "264": [10, 7.5],
  133. "364": [10, 8],
  134. "164d": [0.5, 0.5],
  135. "264d": [1, 0.5],
  136. "364d": [1, 1],
  137. "175": [4.5, 5.5],
  138. "275": [5, 5.5],
  139. "375": [5, 6],
  140. "075": [4.5, 6],
  141. "275d": [7, 1.5],
  142. "375d": [7, 2],
  143. "206": [4.5, 9.5],
  144. "306": [4.5, 10],
  145. "006": [3.5, 10],
  146. "206d": [2, 3.5],
  147. "306d": [2, 4],
  148. "006d": [1.5, 4],
  149. "117": [5.5, 2.5],
  150. "217": [6, 2.5],
  151. "317": [6, 4],
  152. "017": [5.5, 4],
  153. "317d": [6, 3],
  154. "017d": [5.5, 3],
  155. "120": [7.5, 9.5],
  156. "320": [8, 10],
  157. "020": [7.5, 10],
  158. "120d": [9.5, 3.5],
  159. "320d": [10, 4],
  160. "020d": [9.5, 4],
  161. "131": [6.5, 5.5],
  162. "231": [7, 5.5],
  163. "331": [7, 6],
  164. "031": [6.5, 6],
  165. "131d": [4.5, 1.5],
  166. "031d": [4.5, 2],
  167. "141": [7.5, 8.5],
  168. "241": [8, 8.5],
  169. "323": [4, 5],
  170. "041": [7.5, 9],
  171. "141d": [8.5, 3.5],
  172. "041d": [8.5, 4],
  173. "152": [8.5, 7.5],
  174. "252": [9, 7.5],
  175. "334": [2, 0],
  176. "052": [8.5, 8],
  177. "152d": [3.5, 0.5],
  178. "252d": [4, 0.5],
  179. "163": [2, 7.5],
  180. "263": [3, 7.5],
  181. "363": [3, 8],
  182. "045": [9.5, 0],
  183. "163d": [7.5, 0.5],
  184. "263d": [8, 0.5],
  185. "174": [3.5, 8.5],
  186. "274": [4, 8.5],
  187. "374": [4, 9],
  188. "056": [7.5, 5],
  189. "274d": [3, 3.5],
  190. "374d": [3, 4],
  191. "167": [7.5, 6.5],
  192. "205": [10, 8.5],
  193. "305": [10, 9],
  194. "005": [9.5, 9],
  195. "205d": [2, 0.5],
  196. "305d": [2, 1],
  197. "170": [6.5, 7.5],
  198. "216": [9, 9.5],
  199. "316": [9, 10],
  200. "016": [8.5, 10],
  201. "316d": [10, 5],
  202. "016d": [9.5, 5],
  203. "127": [2.5, 9.5],
  204. "201": [5, 7.5],
  205. "327": [3, 10],
  206. "027": [2.5, 10],
  207. "327d": [2, 5],
  208. "027d": [1.5, 5],
  209. "130": [1.5, 8.5],
  210. "212": [4, 6.5],
  211. "330": [2, 9],
  212. "030": [1.5, 9],
  213. "130d": [9.5, 0.5],
  214. "030d": [9.5, 1],
  215. "151": [10.5, 9.5],
  216. "251": [11, 9.5],
  217. "324": [0, 7],
  218. "051": [10.5, 10],
  219. "151d": [10.5, 4.5],
  220. "324d": [0, 0],
  221. "162": [8.5, 10.5],
  222. "262": [9, 10.5],
  223. "335": [6, 8],
  224. "035": [5.5, 8],
  225. "162d": [3.5, 2.5],
  226. "262d": [8, 2.5],
  227. "173": [0.5, 9.5],
  228. "273": [1, 9.5],
  229. "373": [1, 10],
  230. "046": [11.5, 7],
  231. "046d": [11.5, 0],
  232. "273d": [1, 4.5],
  233. "157": [11.5, 8.5],
  234. "204": [0, 5.5],
  235. "304": [0, 5],
  236. "057": [11.5, 9],
  237. "204d": [0, 4.5],
  238. "304d": [0, 6],
  239. "160": [11.5, 7.5],
  240. "215": [8, 6.5],
  241. "315": [8, 7],
  242. "015": [7.5, 7],
  243. "160d": [2.5, 4.5],
  244. "315d": [9, 3],
  245. "171": [5.5, 10.5],
  246. "271": [6, 10.5],
  247. "326": [6, 5],
  248. "026": [5.5, 5],
  249. "326d": [7, 5],
  250. "026d": [4.5, 5],
  251. "137": [3.5, 6.5],
  252. "202": [0, 7.5],
  253. "337": [4, 7],
  254. "037": [3.5, 7],
  255. "202d": [9, 4.5],
  256. "037d": [2.5, 3],
  257. "140": [11.5, 5.5],
  258. "213": [0, 8.5],
  259. "313": [0, 9],
  260. "040": [11.5, 5],
  261. "140d": [11.5, 4.5],
  262. "040d": [11.5, 6],
  263. "161": [9.5, 10.5],
  264. "261": [10, 10.5],
  265. "325": [9, 6],
  266. "025": [8.5, 6],
  267. "161d": [3.5, 1.5],
  268. "325d": [4, 1],
  269. "172": [1.5, 10.5],
  270. "272": [2, 10.5],
  271. "336": [3, 6],
  272. "036": [2.5, 6],
  273. "036d": [7.5, 1],
  274. "272d": [8, 1.5],
  275. "147": [4.5, 7.5],
  276. "203": [4, 3.5],
  277. "303": [4, 4],
  278. "047": [4.5, 8],
  279. "047d": [8.5, 5],
  280. "203d": [8, 4.5],
  281. "150": [7.5, 3.5],
  282. "214": [7, 7.5],
  283. "314": [7, 8],
  284. "050": [7.5, 4],
  285. "150d": [3.5, 4.5],
  286. "314d": [3, 5],
  287. "100": [5.5, 5.5],
  288. "200": [6, 5.5],
  289. "300": [6, 6],
  290. "000": [5.5, 6],
  291. "100d": [5.5, 8.5],
  292. "200d": [6, 8.5],
  293. "300d": [6, 10],
  294. "000d": [5.5, 10]
  295. };
  296.  
  297. // ----------------------------
  298. // Global Image Variables
  299. // ----------------------------
  300. let images = {
  301. tile: null,
  302. speedpad: null,
  303. speedpadRed: null,
  304. speedpadBlue: null,
  305. portal: null,
  306. portalRed: null,
  307. portalBlue: null
  308. };
  309.  
  310. // ----------------------------
  311. // JSON Gate Data Explanation
  312. // ----------------------------
  313. // (See description above)
  314. // ----------------------------
  315. let originalPNGImage = null;
  316. let mapJSONData = null;
  317. let wallMap = [];
  318. // Set currentTexturePack from localStorage if available.
  319. let currentTexturePack = textures.texturePacks[0];
  320. const savedTexture = localStorage.getItem('fm_selected_texturePack');
  321. if (savedTexture) {
  322. const found = textures.texturePacks.find(tp => tp.url === savedTexture);
  323. if (found) {
  324. currentTexturePack = found;
  325. console.log("[FM] Loaded saved texture pack:", currentTexturePack.name);
  326. }
  327. }
  328.  
  329. // ----------------------------
  330. // Utility: waitForElement
  331. // ----------------------------
  332. function waitForElement(selector, callback, timeout = 10000) {
  333. const start = Date.now();
  334. (function check() {
  335. const el = document.querySelector(selector);
  336. if (el) {
  337. console.log(`[FM] Found element for selector "${selector}"`);
  338. callback(el);
  339. } else if (Date.now() - start > timeout) {
  340. console.warn(`[FM] Timeout waiting for element: ${selector}`);
  341. } else {
  342. setTimeout(check, 200);
  343. }
  344. })();
  345. }
  346.  
  347. // ----------------------------
  348. // Utility Functions
  349. // ----------------------------
  350. function rgbToHex(r, g, b) {
  351. return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  352. }
  353.  
  354. // loadImage prepends baseUrl if needed.
  355. function loadImage(url) {
  356. if (url.startsWith("/")) {
  357. url = baseUrl + url;
  358. }
  359. return new Promise((resolve, reject) => {
  360. const img = new Image();
  361. img.crossOrigin = "anonymous";
  362. img.onload = () => {
  363. console.log(`[FM] Loaded image: ${url}`);
  364. resolve(img);
  365. };
  366. img.onerror = (e) => {
  367. console.error(`[FM] Error loading image: ${url}`, e);
  368. reject(e);
  369. };
  370. img.src = url;
  371. });
  372. }
  373.  
  374. function getFloorTile(key) {
  375. if (key === "ffff00") {
  376. return { image: images.speedpad, y: 0, x: 0 };
  377. } else if (key === "ff7373") {
  378. return { image: images.speedpadRed, y: 0, x: 0 };
  379. } else if (key === "7373ff") {
  380. return { image: images.speedpadBlue, y: 0, x: 0 };
  381. } else if (key === "cac000") {
  382. return { image: images.portal, x: 0, y: 0 };
  383. } else if (key === "cc3300") {
  384. return { image: images.portalRed, x: 0, y: 0 };
  385. } else if (key === "0066cc") {
  386. return { image: images.portalBlue, x: 0, y: 0 };
  387. } else if (floorTiles.hasOwnProperty(key)) {
  388. return { image: images.tile, x: floorTiles[key].x, y: floorTiles[key].y };
  389. } else {
  390. return null;
  391. }
  392. }
  393.  
  394. function wallSolidsAt(col, row) {
  395. if (col < 0 || row < 0 || row >= wallMap.length || col >= wallMap[0].length) return 0;
  396. return wallMap[row][col];
  397. }
  398.  
  399. function drawWallTile(ctx, col, row) {
  400. const solids = wallMap[row][col];
  401. if (!solids) return;
  402. for (let q = 0; q < 4; q++) {
  403. const mask = (solids >> (q << 1)) & 3;
  404. if (mask === 0) continue;
  405. const cornerX = col + ((q & 2) === 0 ? 1 : 0);
  406. const cornerY = row + (((q + 1) & 2) === 0 ? 0 : 1);
  407. let aroundCorner =
  408. (wallSolidsAt(cornerX, cornerY) & 0xc0) |
  409. (wallSolidsAt(cornerX - 1, cornerY) & 0x03) |
  410. (wallSolidsAt(cornerX - 1, cornerY - 1) & 0x0c) |
  411. (wallSolidsAt(cornerX, cornerY - 1) & 0x30);
  412. aroundCorner |= (aroundCorner << 8);
  413. const startDirection = q * 2 + 1;
  414. let cwSteps = 0;
  415. while (cwSteps < 8 && (aroundCorner & (1 << (startDirection + cwSteps)))) { cwSteps++; }
  416. let ccwSteps = 0;
  417. while (ccwSteps < 8 && (aroundCorner & (1 << (startDirection + 7 - ccwSteps)))) { ccwSteps++; }
  418. const hasChip = (mask === 3 && (((solids | (solids << 8)) >> ((q + 2) << 1)) & 3) === 0);
  419. let solidStart, solidEnd;
  420. if (cwSteps === 8) {
  421. solidStart = solidEnd = 0;
  422. } else {
  423. solidEnd = (startDirection + cwSteps + 4) % 8;
  424. solidStart = (startDirection - ccwSteps + 12) % 8;
  425. }
  426. const key = `${q}${solidStart}${solidEnd}${hasChip ? "d" : ""}`;
  427. const coords = quadrantCoords[key] || [5.5, 5.5];
  428. let destX = col * config.tileSize;
  429. let destY = row * config.tileSize;
  430. if (q === 0) destX += config.quadSize;
  431. else if (q === 1) { destX += config.quadSize; destY += config.quadSize; }
  432. else if (q === 2) destY += config.quadSize;
  433. const srcX = coords[0] * 40;
  434. const srcY = coords[1] * 40;
  435. ctx.drawImage(images.tile, srcX, srcY, config.quadSize, config.quadSize,
  436. destX, destY, config.quadSize, config.quadSize);
  437. }
  438. }
  439.  
  440. function fetchMapData(mapCode) {
  441. const pngURL = `https://fortunatemaps.herokuapp.com/png/${mapCode}.png`;
  442. const jsonURL = `https://fortunatemaps.herokuapp.com/json/${mapCode}.json`;
  443. console.log(`[FM] Fetching map data for code: ${mapCode}`);
  444. return Promise.all([
  445. fetch(pngURL)
  446. .then(res => res.blob())
  447. .then(blob => {
  448. console.log("[FM] PNG fetched successfully");
  449. return URL.createObjectURL(blob);
  450. }),
  451. fetch(jsonURL)
  452. .then(res => res.json())
  453. .then(json => {
  454. console.log("[FM] JSON fetched successfully:", json);
  455. return json;
  456. })
  457. ]);
  458. }
  459.  
  460. async function processMap() {
  461. if (!originalPNGImage) return;
  462. console.log("[FM] Starting map processing...");
  463.  
  464. try {
  465. images.tile = await loadImage(currentTexturePack.tiles);
  466. images.speedpad = await loadImage(currentTexturePack.speedpad);
  467. images.speedpadRed = await loadImage(currentTexturePack.speedpadRed);
  468. images.speedpadBlue = await loadImage(currentTexturePack.speedpadBlue);
  469. images.portal = await loadImage(currentTexturePack.portal);
  470. images.portalRed = await loadImage(currentTexturePack.portalRed);
  471. images.portalBlue = await loadImage(currentTexturePack.portalBlue);
  472. } catch(e) {
  473. console.error("[FM] Error loading texture images", e);
  474. return;
  475. }
  476. console.log("[FM] All texture images loaded");
  477.  
  478. const offCanvas = document.createElement('canvas');
  479. offCanvas.width = originalPNGImage.width;
  480. offCanvas.height = originalPNGImage.height;
  481. const offCtx = offCanvas.getContext('2d');
  482. offCtx.drawImage(originalPNGImage, 0, 0);
  483. console.log("[FM] Offscreen canvas drawn");
  484. const imageData = offCtx.getImageData(0, 0, offCanvas.width, offCanvas.height);
  485. const data = imageData.data;
  486.  
  487. wallMap = [];
  488. for (let y = 0; y < offCanvas.height; y++) {
  489. wallMap[y] = [];
  490. for (let x = 0; x < offCanvas.width; x++) {
  491. const idx = (y * offCanvas.width + x) * 4;
  492. const r = data[idx], g = data[idx + 1], b = data[idx + 2], a = data[idx + 3];
  493. const hex = rgbToHex(r, g, b).toLowerCase();
  494. wallMap[y][x] = (a === 0) ? 0 : (wallTypes.hasOwnProperty(hex) ? wallTypes[hex].wallSolids : 0);
  495. }
  496. }
  497. console.log("[FM] wallMap built:", wallMap);
  498.  
  499. const finalCanvas = document.createElement('canvas');
  500. finalCanvas.width = offCanvas.width * config.tileSize;
  501. finalCanvas.height = offCanvas.height * config.tileSize;
  502. const finalCtx = finalCanvas.getContext('2d');
  503. finalCtx.imageSmoothingEnabled = false;
  504. console.log("[FM] Final canvas created");
  505.  
  506. const defaultFloorTile = getFloorTile("d4d4d4");
  507. if (defaultFloorTile) {
  508. const sx = defaultFloorTile.x * config.tileSize;
  509. const sy = defaultFloorTile.y * config.tileSize;
  510. for (let y = 0; y < offCanvas.height; y++) {
  511. for (let x = 0; x < offCanvas.width; x++) {
  512. finalCtx.drawImage(defaultFloorTile.image, sx, sy, config.tileSize, config.tileSize,
  513. x * config.tileSize, y * config.tileSize, config.tileSize, config.tileSize);
  514. }
  515. }
  516. }
  517.  
  518. for (let y = 0; y < offCanvas.height; y++) {
  519. for (let x = 0; x < offCanvas.width; x++) {
  520. const idx = (y * offCanvas.width + x) * 4;
  521. const r = data[idx], g = data[idx + 1], b = data[idx + 2], a = data[idx + 3];
  522. if (a === 0) continue;
  523. const hex = rgbToHex(r, g, b).toLowerCase();
  524. if (wallTypes.hasOwnProperty(hex)) continue;
  525. let tileSource = null;
  526. if (hex === "007500") {
  527. const fieldKey = `${x},${y}`;
  528. let state = "empty"; // Default state remains "empty"
  529. if (mapJSONData && mapJSONData.fields && mapJSONData.fields[fieldKey]) {
  530. let ds = mapJSONData.fields[fieldKey].defaultState;
  531. if (ds === "on") {
  532. state = "green";
  533. } else if (ds === "off") {
  534. state = "empty"; // Change "off" to "empty"
  535. } else {
  536. state = ds;
  537. }
  538. }
  539.  
  540. const tileKey = "007500_" + state;
  541. tileSource = getFloorTile(tileKey);
  542. } else {
  543. tileSource = getFloorTile(hex);
  544. }
  545. if (tileSource) {
  546. const sx = tileSource.x * config.tileSize;
  547. const sy = tileSource.y * config.tileSize;
  548. finalCtx.drawImage(tileSource.image, sx, sy, config.tileSize, config.tileSize,
  549. x * config.tileSize, y * config.tileSize, config.tileSize, config.tileSize);
  550. } else {
  551. finalCtx.fillStyle = "#000000";
  552. finalCtx.fillRect(x * config.tileSize, y * config.tileSize, config.tileSize, config.tileSize);
  553. }
  554. }
  555. }
  556. console.log("[FM] Floor and gate tiles drawn");
  557.  
  558. for (let y = 0; y < wallMap.length; y++) {
  559. for (let x = 0; x < wallMap[0].length; x++) {
  560. if (wallMap[y][x] !== 0) {
  561. drawWallTile(finalCtx, x, y);
  562. }
  563. }
  564. }
  565. console.log("[FM] Wall tiles overlaid");
  566.  
  567. // Marsball rendering:
  568. // For each marsball in the JSON, draw an 80x80 image from the tiles texture (source: 12*40, 9*40)
  569. // Now centered over the tile instead of having its top-left corner at the tile's top-left.
  570. if (mapJSONData && mapJSONData.marsballs) {
  571. mapJSONData.marsballs.forEach(mars => {
  572. // Calculate destination so the marsball is centered over the tile.
  573. const destX = mars.x * config.tileSize - config.tileSize/2;
  574. const destY = mars.y * config.tileSize - config.tileSize/2;
  575. finalCtx.drawImage(
  576. images.tile,
  577. 12 * config.tileSize, 9 * config.tileSize, 2 * config.tileSize, 2 * config.tileSize,
  578. destX, destY, 2 * config.tileSize, 2 * config.tileSize
  579. );
  580. });
  581. console.log("[FM] Marsballs drawn");
  582. }
  583.  
  584. waitForElement('img.card-img-top', function(previewImg) {
  585. previewImg.src = finalCanvas.toDataURL();
  586. console.log("[FM] Preview image updated");
  587. });
  588. }
  589.  
  590. // ----------------------------
  591. // UI: Texture Pack Dropdown with Search & Scrollbar
  592. // ----------------------------
  593. function addTextureDropdown() {
  594. const referenceDiv = document.querySelector('main.container div.row.mb-2 div.col-md-4 div.card.stats-card div.card-body div.dropdown.w-100.mt-2');
  595. if (!referenceDiv) {
  596. console.error("[FM] Reference dropdown not found");
  597. return;
  598. }
  599. const textureContainerHTML = `
  600. <div id="fm-texture-container">
  601. <div class="dropdown w-100 mt-2" id="fm-texture-dropdown">
  602. <button class="btn btn-primary w-100 dropdown-toggle" type="button" id="fm-dropdown-button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  603. ${currentTexturePack.name}
  604. </button>
  605. <div class="dropdown-menu" aria-labelledby="fm-dropdown-button" style="max-height: 200px; overflow-y: auto; padding: 0.5rem;">
  606. <div class="px-2">
  607. <input type="text" id="fm-dropdown-search" class="form-control form-control-sm" placeholder="Search Texture Packs...">
  608. </div>
  609. <div id="fm-dropdown-items">
  610. ${textures.texturePacks.map(tp => `<a class="dropdown-item" href="#" data-value="${tp.url}">${tp.name}</a>`).join('')}
  611. </div>
  612. </div>
  613. </div>
  614. </div>
  615. `;
  616. referenceDiv.insertAdjacentHTML('afterend', textureContainerHTML);
  617. console.log("[FM] Texture pack dropdown container inserted directly below the reference dropdown");
  618.  
  619. function filterItems() {
  620. const query = document.getElementById('fm-dropdown-search').value.toLowerCase();
  621. const items = document.querySelectorAll('#fm-dropdown-items .dropdown-item');
  622. items.forEach(item => {
  623. const text = item.textContent.toLowerCase();
  624. item.style.display = text.indexOf(query) > -1 ? "" : "none";
  625. });
  626. }
  627.  
  628. const searchInput = document.getElementById('fm-dropdown-search');
  629. searchInput.addEventListener('input', filterItems);
  630.  
  631. const items = document.querySelectorAll('#fm-dropdown-items .dropdown-item');
  632. items.forEach(item => {
  633. item.addEventListener('click', function(e) {
  634. e.preventDefault();
  635. const selectedUrl = this.getAttribute('data-value');
  636. const selectedTexture = textures.texturePacks.find(tp => tp.url === selectedUrl);
  637. if (selectedTexture && selectedTexture.url !== currentTexturePack.url) {
  638. currentTexturePack = selectedTexture;
  639. localStorage.setItem('fm_selected_texturePack', selectedTexture.url);
  640. console.log("[FM] Texture pack changed to:", selectedTexture.name);
  641. processMap();
  642. document.getElementById('fm-dropdown-button').textContent = selectedTexture.name;
  643. }
  644. });
  645. });
  646. }
  647.  
  648. // ----------------------------
  649. // Main Execution
  650. // ----------------------------
  651. function main() {
  652. console.log("[FM] Main execution started");
  653. const mapCodeMatch = window.location.href.match(/\/map\/(\d+)/);
  654. if (!mapCodeMatch) {
  655. console.error("[FM] No map code found in URL");
  656. return;
  657. }
  658. const mapCode = mapCodeMatch[1];
  659. console.log("[FM] Map code:", mapCode);
  660.  
  661. fetchMapData(mapCode)
  662. .then(([pngObjectUrl, jsonData]) => {
  663. mapJSONData = jsonData;
  664. console.log("[FM] Map JSON data stored");
  665. return loadImage(pngObjectUrl);
  666. })
  667. .then(img => {
  668. originalPNGImage = img;
  669. console.log("[FM] Original PNG loaded");
  670. processMap();
  671. })
  672. .catch(err => console.error("[FM] Error fetching or processing map data:", err));
  673.  
  674. addTextureDropdown();
  675. }
  676.  
  677. if (document.readyState === "loading") {
  678. document.addEventListener("DOMContentLoaded", main);
  679. } else {
  680. main();
  681. }
  682. })();