Advertisement
Zeriab

[RMMZ] Unfinished code for a map stitching plugin

Jun 20th, 2022
1,009
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //=============================================================================
  2. // RPG Maker MZ - Map stitching (Zeriab_MapStitching.js)
  3. // Last Updated: 2020.XX.XX
  4. //=============================================================================
  5. // Copyright 2020 Zeriab
  6. //
  7. // This software is provided 'as-is', without any express or implied
  8. // warranty. In no event will the authors be held liable for any damages
  9. // arising from the use of this software.
  10. //
  11. // Permission is granted to anyone to use this software for any purpose,
  12. // including commercial applications, and to alter it and redistribute it
  13. // freely, subject to the following restrictions:
  14. //
  15. // 1. The origin of this software must not be misrepresented; you must not
  16. //    claim that you wrote the original software. If you use this software
  17. //    in a product, an acknowledgement in the product documentation would be
  18. //    appreciated but is not required.
  19. // 2. Altered source versions must be plainly marked as such, and must not be
  20. //    misrepresented as being the original software.
  21. // 3. This notice may not be removed or altered from any source distribution.
  22. //=============================================================================
  23.  
  24. /*:
  25.  * @target MZ
  26.  * @plugindesc Stitch maps together
  27.  * @author Zeriab
  28.  *
  29.  * @help Zeriab_MapStitching.js
  30.  *
  31.  * Stitch maps together
  32.  * TODO
  33.  *
  34.  * @param WarnOnOverlap
  35.  * @text Give warnings when maps overlap
  36.  * @desc Enable this function to get warnings in the console when stitching together maps results in some tiles overlapping
  37.  * @default false
  38.  * @type boolean
  39.  *
  40.  * @param MapDistanceLimit
  41.  * @text Determines how far away a mapsegment from the current map to load
  42.  * @type number
  43.  * @min 1
  44.  * @default 1
  45.  *
  46.  *
  47.  * @command Setup Border Anchor
  48.  * @text Anchor this event
  49.  * @desc TODO
  50.  *
  51.  * @arg mapId
  52.  * @text Map ID
  53.  * @type number
  54.  * @min 1
  55.  * @max 2000
  56.  * @default 1
  57.  *
  58.  * @arg eventId
  59.  * @text Event ID
  60.  * @type number
  61.  * @min 1
  62.  * @max 999
  63.  * @default 0
  64.  *
  65.  * @arg direction
  66.  * @text Direction
  67.  * @type select
  68.  * @default 2
  69.  * @option Down
  70.  * @value 2
  71.  * @option Left
  72.  * @value 4
  73.  * @option Right
  74.  * @value 6
  75.  * @option Up
  76.  * @value 8
  77.  * @option Center
  78.  * @value 5
  79.  */
  80. (() => {
  81.     'use strict';
  82.     const pluginName = "Zeriab_MapStitching";
  83.     const parameters = PluginManager.parameters(pluginName);
  84.     const warnOnOverlap = eval(parameters.WarnOnOverlap || 'false');
  85.     const mapDistanceLimit = eval(parameters.MapDistanceLimit || 'false');
  86.    
  87.     /*
  88.      * Plug in command
  89.      */
  90.     PluginManager.registerCommand(pluginName, "Setup Border Anchor", SetupBorderAnchor);
  91.     function SetupBorderAnchor(args) {
  92.         const fromMapId = this._mapId;
  93.         const fromEventId = this._eventId;
  94.         const toMapId = Number(args.mapId);
  95.         const toEventId = Number(args.eventId);
  96.         const direction = Number(args.direction);
  97.  
  98.         // TODO Assert direction?
  99.  
  100.         console.log("Command: Setup border anchor for [%i:%i] %i to [%i, %i]", fromMapId, fromEventId, direction, toMapId, toEventId);
  101.         $gameMap.setupBorderAnchor(fromMapId, fromEventId, toMapId, toEventId, direction);
  102.         $gameMap.refreshStitches();
  103.     }
  104.  
  105.     Game_Map.prototype.setupBorderAnchor = function(fromMapId, fromEventId, toMapId, toEventId, direction) {
  106.         console.log("Setting up border anchor for [%i:%i] %s to [%i, %i]", fromMapId, fromEventId, direction, toMapId, toEventId);
  107.         $gameSystem.mapStitches().addEventAnchor(fromMapId, fromEventId, toMapId, toEventId, direction);
  108.     }
  109.  
  110.     /*
  111.      * Lazy initialization
  112.      * Should provide compatibility to old saves
  113.      */
  114.     Game_System.prototype.mapStitches = function() {
  115.         if (!this._mapStitching) {
  116.             this._mapStitching = new MapStitches();
  117.         }
  118.         return this._mapStitching;
  119.     }
  120.  
  121.     /*
  122.      * MapManager is used as a container for any data that should not be included in the save files
  123.      */
  124.     function MapManager() {
  125.         throw new Error("This is a static class");
  126.     }
  127.    
  128.     MapManager._preloads = [];
  129.     MapManager._preloadCount = 0;
  130.     MapManager._preloadGoal = 0;
  131.     MapManager.loadMap = function(id, data) {
  132.         this._preloads[id] = data;
  133.         this._preloadCount += 1;
  134.     };
  135.  
  136.     MapManager.setMapCount = function(count) {
  137.         this._preloadGoal = count;
  138.     }
  139.  
  140.     MapManager.preloadFinished = function() {
  141.         return this._preloadGoal > 0 && this._preloadCount === this._preloadGoal;
  142.     }
  143.  
  144.     /**
  145.      * Main 'class' for managing the map stitches
  146.      * Any data belonging to this class will be included in save files
  147.      */
  148.     function MapStitches() {
  149.         this.initialize(...arguments);
  150.     }
  151.  
  152.     MapStitches.prototype.initialize = function() {
  153.         this._anchors = {};
  154.         this._preloads = {};
  155.         this._segments = {};
  156.         this.preloadMaps();
  157.     };
  158.    
  159.     MapStitches.prototype.addEventAnchor = function(fromMapId, fromEventId, toMapId, toEventId, direction) {
  160.         const fromSegment = this.fetchSegment(fromMapId);
  161.         const toSegment = this.fetchSegment(toMapId);
  162.         // TODO Use direction for anything?
  163.         fromSegment.setupAnchor(fromEventId, toSegment, toEventId, direction);
  164.     }
  165.  
  166.     MapStitches.prototype.preloadMaps = function() {
  167.         var mapCount = 0;
  168.         for (var mapInfo of $dataMapInfos) {
  169.             if (mapInfo) {
  170.                 mapCount++;
  171.                 this.loadMapData(mapInfo.id);
  172.             }
  173.         }
  174.         MapManager.setMapCount(mapCount);
  175.     }
  176.  
  177.     MapStitches.prototype.mapLoaded = function(mapId, mapData) {
  178.         console.log("Finished reading map " + mapId);
  179.         MapManager.loadMap(mapId, mapData);
  180.         this._preloads[mapId] = true;
  181.         if (MapManager.preloadFinished()) {
  182.             console.log("Preloading complete");
  183.             this.postProcess();
  184.         }
  185.     }
  186.  
  187.     MapStitches.prototype.loadMapData = function(mapId) {
  188.         const fileName = DataManager.makeMapName(mapId);
  189.         //const fileName = "fh/" + DataManager.makeMapName(mapId);
  190.         const objectName = "$tempMap%1".format(mapId.padZero(3));
  191.         console.log("Loading " + fileName + " into " + objectName);
  192.         DataManager.loadDataFileExt(objectName, fileName, mapData => {
  193.             $gameSystem.mapStitches().mapLoaded(mapId, mapData);
  194.             window[objectName] = null;
  195.         });
  196.     }
  197.  
  198.     MapStitches.prototype.hasSegment = function(mapId) {
  199.         return !!this._segments[mapId];
  200.     }
  201.  
  202.     MapStitches.prototype.fetchSegment = function(mapId) {
  203.         if (!this.hasSegment(mapId)) {
  204.             this._segments[mapId] = new MapSegment(mapId);
  205.         }
  206.         return this._segments[mapId];
  207.     }
  208.  
  209.     MapStitches.prototype.postProcess = function() {
  210.         for (const key in this._segments) {
  211.             const segment = this._segments[key];
  212.             segment.processAnchors();
  213.         }
  214.         $gameMap.refreshStitches();
  215.     }
  216.  
  217.  
  218.     /**
  219.      * Encapsulates information regarding a map segments and its neighbors
  220.      */
  221.     class MapSegment {
  222.         constructor(mapId) {
  223.             this._mapId = mapId;
  224.             this._anchors = {};
  225.         }
  226.  
  227.         setupAnchor(fromEventId, toSegment, toEventId, direction) {
  228.             if (direction === 0) {
  229.                 direction = 5;
  230.             }
  231.             this._anchors[toSegment._mapId] = {type: "event", from: fromEventId, to: toEventId, segment: toSegment, direction: direction};
  232.         }
  233.  
  234.         processAnchors() {
  235.             // Process event anchors
  236.             for (const key in this._anchors) {
  237.                 // Relevant anchor information
  238.                 const anchor = this._anchors[key];
  239.                 const toSegment = anchor.segment;
  240.                 const toSegmentData = toSegment.mapData();
  241.                 const direction = anchor.direction;
  242.  
  243.                 // Find coordinates
  244.                 const from = this.event(anchor.from);
  245.                 const to = toSegment.event(anchor.to);
  246.                 const offset = this.offset(direction);
  247.  
  248.                 // Calculate segment starting location
  249.                 anchor.minX = from.x + offset.x - to.x;
  250.                 anchor.minY = from.y + offset.y - to.y;
  251.                 anchor.maxX = anchor.minX + toSegmentData.width;
  252.                 anchor.maxY = anchor.minY + toSegmentData.height;
  253.  
  254.                 console.log("Anchored map " + key + " has top-left coordinate (" + anchor.minX + ", " + anchor.minY +")");
  255.             }
  256.         }
  257.  
  258.         offset(direction) {
  259.             // 678
  260.             // 345
  261.             // 012
  262.             const value = direction - 1;
  263.             // 012
  264.             // 012
  265.             // 012
  266.             const x = value % 3;
  267.             // 222
  268.             // 111
  269.             // 000
  270.             const y = 2 - Math.floor(value / 3);
  271.             return {x: x - 1, y: y - 1};
  272.         }
  273.  
  274.         event(eventId) {
  275.             const data = this.mapData();
  276.             return data.events[eventId];;
  277.         }
  278.  
  279.         mapData() {
  280.             return MapManager._preloads[this._mapId];
  281.         }
  282.     }
  283.  
  284.  
  285.     /*
  286.      * Core Game_Map integration
  287.      */
  288.     const _Game_Map_setup = Game_Map.prototype.setup;
  289.     Game_Map.prototype.setup = function(mapId) {
  290.         _Game_Map_setup.apply(this, arguments);
  291.         this.setupStitches();
  292.     }
  293.  
  294.     Game_Map.prototype.setupStitches = function() {
  295.         this._segmentReady = false;
  296.         this.minX = null;
  297.         this.maxX = null;
  298.         this.minY = null;
  299.         this.maxY = null;
  300.         // TODO
  301.     }
  302.  
  303.     Game_Map.prototype.refreshStitches = function() {
  304.         this.minX = 0;
  305.         this.maxX = this.width();
  306.         this.minY = 0;
  307.         this.maxY = this.height();
  308.  
  309.         console.log("Starting with map span (" + this.minX + ", " + this.minY + ") <=> (" + this.maxX + ", " + this.maxY + ")");
  310.        
  311.         this.segment = $gameMap.currentStitch();
  312.         // Process event anchors
  313.         for (const key in this.segment._anchors) {
  314.             // Relevant anchor information
  315.             const anchor = this.segment._anchors[key];
  316.             if (anchor.minX < this.minX) {
  317.                 this.minX = anchor.minX;
  318.             }
  319.             if (anchor.minY < this.minY) {
  320.                 this.minY = anchor.minY;
  321.             }
  322.             if (anchor.maxX > this.maxX) {
  323.                 this.maxX = anchor.maxX;
  324.             }
  325.             if (anchor.maxY > this.maxY) {
  326.                 this.maxY = anchor.maxY;
  327.             }
  328.         }
  329.  
  330.         this._segmentReady = true;
  331.         console.log("Ending with map span (" + this.minX + ", " + this.minY + ") <=> (" + this.maxX + ", " + this.maxY + ")");
  332.     }
  333.  
  334.     Game_Map.prototype.currentStitch = function() {
  335.         return $gameSystem.mapStitches().fetchSegment(this._mapId);
  336.     }
  337.  
  338.     // Replace
  339.     const _Game_Map_setDisplayPos = Game_Map.prototype.setDisplayPos;
  340.     Game_Map.prototype.setDisplayPos = function(x, y) {
  341.         if (!this._segmentReady) {
  342.             _Game_Map_setDisplayPos.apply(this, arguments);
  343.         } else {
  344.             setDisplayPosX(x);
  345.             setDisplayPosY(y);
  346.         }
  347.     };
  348.  
  349.     Game_Map.prototype.setDisplayPosX = function(x) {
  350.         if (this.isLoopHorizontal()) {
  351.             // TODO How would looping work?
  352.             this._displayX = x.mod(this.width());
  353.             this._parallaxX = x;
  354.         } else {
  355.             this._displayX = x.clamp(this.minX, this.maxX);
  356.             this._parallaxX = this._displayX;
  357.         }
  358.     }
  359.  
  360.     Game_Map.prototype.setDisplayPosY = function(y) {
  361.         if (this.isLoopVertical()) {
  362.             // TODO How would looping work?
  363.             this._displayY = y.mod(this.height());
  364.             this._parallaxY = y;
  365.         } else {
  366.             this._displayY = y.clamp(this.minY, this.maxY);
  367.             this._parallaxY = this._displayY;
  368.         }
  369.     }
  370.  
  371. //#region Scroll
  372.     const _Game_Map_scrollDown = Game_Map.prototype.scrollDown;
  373.     Game_Map.prototype.scrollDown = function(distance) {
  374.         if (!this._segmentReady) {
  375.             _Game_Map_scrollDown.apply(this, arguments);
  376.         } else {
  377.             if (this.isLoopVertical()) {
  378.                 // TODO Handle loops
  379.                 this._displayY += distance;
  380.                 this._displayY %= $dataMap.height;
  381.                 if (this._parallaxLoopY) {
  382.                     this._parallaxY += distance;
  383.                 }
  384.             } else if (this.maxY >= this.screenTileY()) {
  385.                 const lastY = this._displayY;
  386.                 this._displayY = Math.min(
  387.                     this._displayY + distance,
  388.                     this.maxY - this.screenTileY()
  389.                 );
  390.                 this._parallaxY += this._displayY - lastY;
  391.             }
  392.         }
  393.     };
  394.  
  395.     const _Game_Map_scrollLeft = Game_Map.prototype.scrollLeft;
  396.     Game_Map.prototype.scrollLeft = function(distance) {
  397.         if (!this._segmentReady) {
  398.             _Game_Map_scrollLeft.apply(this, arguments);
  399.         } else {
  400.             if (this.isLoopHorizontal()) {
  401.                 // TODO Handle loops
  402.                 this._displayX += $dataMap.width - distance;
  403.                 this._displayX %= $dataMap.width;
  404.                 if (this._parallaxLoopX) {
  405.                     this._parallaxX -= distance;
  406.                 }
  407.             } else if (this.maxX >= this.screenTileX()) {
  408.                 const lastX = this._displayX;
  409.                 this._displayX = Math.max(this._displayX - distance, this.minX);
  410.                 this._parallaxX += this._displayX - lastX;
  411.             }
  412.         }
  413.     };
  414.  
  415.     const _Game_Map_scrollRight = Game_Map.prototype.scrollRight;
  416.     Game_Map.prototype.scrollRight = function(distance) {
  417.         if (!this._segmentReady) {
  418.             _Game_Map_scrollRight.apply(this, arguments);
  419.         } else {
  420.             if (this.isLoopHorizontal()) {
  421.                 // TODO Handle loops
  422.                 this._displayX += distance;
  423.                 this._displayX %= $dataMap.width;
  424.                 if (this._parallaxLoopX) {
  425.                     this._parallaxX += distance;
  426.                 }
  427.             } else if (this.maxX >= this.screenTileX()) {
  428.                 const lastX = this._displayX;
  429.                 this._displayX = Math.min(
  430.                     this._displayX + distance,
  431.                     this.maxX - this.screenTileX()
  432.                 );
  433.                 this._parallaxX += this._displayX - lastX;
  434.             }
  435.         }
  436.     };
  437.  
  438.     const _Game_Map_scrollUp = Game_Map.prototype.scrollUp;
  439.     Game_Map.prototype.scrollUp = function(distance) {
  440.         if (!this._segmentReady) {
  441.             _Game_Map_scrollUp.apply(this, arguments);
  442.         } else {
  443.             if (this.isLoopVertical()) {
  444.                 // TODO Handle loops
  445.                 this._displayY += $dataMap.height - distance;
  446.                 this._displayY %= $dataMap.height;
  447.                 if (this._parallaxLoopY) {
  448.                     this._parallaxY -= distance;
  449.                 }
  450.             } else if (this.maxY >= this.screenTileY()) {
  451.                 const lastY = this._displayY;
  452.                 this._displayY = Math.max(this._displayY - distance, this.minY);
  453.                 this._parallaxY += this._displayY - lastY;
  454.             }
  455.         }
  456.     };
  457. //#endregion
  458.  
  459.     const _Game_Map_isValid = Game_Map.prototype.isValid;
  460.     Game_Map.prototype.isValid = function(x, y) {
  461.         if (!this._segmentReady) {
  462.             return _Game_Map_isValid.apply(this, arguments);
  463.         } else {
  464.             return x >= this.minX && x < this.maxX && y >= this.minY && y < this.maxY;
  465.         }
  466.     };
  467.  
  468.  
  469.     /*
  470.      * Sprites
  471.      */
  472.     Spriteset_Map.prototype.createSegmentTilemap = function(anchor, segment) {
  473.         const tilemap = new Tilemap();
  474.         tilemap.tileWidth = $gameMap.tileWidth();
  475.         tilemap.tileHeight = $gameMap.tileHeight();
  476.         const mapData = segment.mapData();
  477.         tilemap.setData(mapData.width, mapData.height, mapData.data);
  478.         tilemap.horizontalWrap = $gameMap.isLoopHorizontal();
  479.         tilemap.verticalWrap = $gameMap.isLoopVertical();
  480.         this._baseSprite.addChild(tilemap);
  481.         this._segmentTilemap = tilemap;
  482.         this._segment = segment;
  483.         this._segmentAnchor = anchor;
  484.         this.loadSegmentTileset(segment._mapId, mapData);
  485.     };
  486.    
  487.     Spriteset_Map.prototype.loadSegmentTileset = function(mapId, mapData) {
  488.         this._segmentTileset = $dataTilesets[mapData.tilesetId];
  489.         if (this._segmentTileset) {
  490.             const bitmaps = [];
  491.             const tilesetNames = this._segmentTileset.tilesetNames;
  492.             for (const name of tilesetNames) {
  493.                 bitmaps.push(ImageManager.loadTileset(name));
  494.             }
  495.             this._segmentTilemap.setBitmaps(bitmaps);
  496.             this._segmentTilemap.flags = this._segmentTileset.flags;
  497.         }
  498.     };
  499.    
  500.     const _Spriteset_Map_updateTilemap = Spriteset_Map.prototype.updateTilemap;
  501.     Spriteset_Map.prototype.updateTilemap = function() {
  502.         _Spriteset_Map_updateTilemap.apply(this, arguments);
  503.         if (this._segmentTilemap) {
  504.             this._segmentTilemap.origin.x = ($gameMap.displayX() - this._segmentAnchor.minX) * $gameMap.tileWidth();
  505.             this._segmentTilemap.origin.y = ($gameMap.displayY() - this._segmentAnchor.minY) * $gameMap.tileHeight();
  506.         } else if ($gameMap._segmentReady) {
  507.             console.log("TODO Setup tileset");
  508.             this.segment = $gameMap.currentStitch();
  509.             // Process event anchors
  510.             for (const key in this.segment._anchors) {
  511.                 // Relevant anchor information
  512.                 const anchor = this.segment._anchors[key];
  513.                 this.createSegmentTilemap(anchor, anchor.segment);
  514.                 break;
  515.             }            
  516.         }
  517.     };
  518.  
  519.  
  520.     /*
  521.      * Map preload functions
  522.      */
  523.     DataManager.loadDataFileExt = function(name, src, func) {
  524.         const xhr = new XMLHttpRequest();
  525.         const url = "data/" + src;
  526.         window[name] = null;
  527.         xhr.open("GET", url);
  528.         xhr.overrideMimeType("application/json");
  529.         xhr.onload = () => this.onXhrLoadExt(xhr, name, src, url, func);
  530.         xhr.onerror = () => this.onXhrError(name, src, url);
  531.         xhr.send();
  532.     };
  533.    
  534.     DataManager.onXhrLoadExt = function(xhr, name, src, url, func) {
  535.         if (xhr.status < 400) {
  536.             window[name] = JSON.parse(xhr.responseText);
  537.             this.onLoadExt(window[name], func);
  538.         } else {
  539.             this.onXhrError(name, src, url);
  540.         }
  541.     };
  542.    
  543.     DataManager.onLoadExt = function(object, func) {
  544.         DataManager.onLoad(object);
  545.         func(object);
  546.     };
  547.  
  548.     DataManager.makeMapName = function(mapId) {
  549.         // Old saves without any subfolder information is considered to use the default folder
  550.         return 'Map%1.json'.format(mapId.padZero(3));
  551.     };    
  552. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement