查看: 14012|回复: 10

[搬运汉化] RandomMapGenerator-随机地图生成

[复制链接]
梦石
0
星屑
2306
在线时间
1010 小时
注册时间
2012-4-30
回帖
1455

开拓者

发表于 2017-6-17 13:11:23 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 铅笔描绘的思念 于 2017-6-17 14:20 编辑

本来昨天在研究roguelike地图的生成算法
但晚上找到了一个插件能实现随机生成地图。。生成的是房间加通道的地牢类型
简单翻译了一些分享给大家
其实还有些调整的空间可以给插件设置参数的。。但原作者貌似没有想到那些参数不设置会很怪(其实那些最好是根据地图大小来设置)
本体
[pre lang="javascript"]//=============================================================================
// SAN_MapGenerator.js
//=============================================================================
// Copyright (c) 2015 Sanshiro
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
//=============================================================================

/*:
* @plugindesc SAN_MapGenerator ver1.00a
* Generate map and allocate events automatically.
* @author Sanshiro https://twitter.com/rev2nym
*
* @help
* When the plugin command is called, the map is generated
* and the player moves to position of start(entrance) event.
*
* Set following tiles and events in a position on the base map.
*
* -Tiles
*   space :{x:0, y:0}
*   room  :{x:0, y:1}
*   pass  :{x:0, y:2}
*   roof  :{x:0, y:3}
*   wall  :{x:0, y:4}
*   rubble:{x:0, y:5}
*
* -Events
*   start:{x:1, y:0}
*   goal :{x:1, y:1}
*   other:other position than above
*
* Appearance rate can be set as events besides the start and the goal.
* Set as the following on a memo space of events.
* The event which has no setting of the appearance rate isn't generated.
*
* -Appearance rate
*   each map :<RateMap: [Positive small number of less than 1.0]>
*   each room:<RateRoom:[Positive small number of less than 1.0]>
*
* -Plugin Command
*   MapGenerator RoomAndPass # Generate map consists of rooms and passes.
*   MapGenerator FillRoom    # Generate map consists of a room whole the map.
*   
* There is no plugin parameter.
*
*
* It's possible to commercial use, distribute, and modify under the MIT license.
* But, don't eliminate and don't alter a comment of the beginning.
* If it's good, please indicate an author name on credit.
*
* Author doesn't shoulder any responsibility in all kind of damage by using this.
* And please don't expect support. X(
*/

/*:ja
* @plugindesc 自動マップ生成 ver1.00a
* 自動的にマップを生成しイベントを配置します。
* @author サンシロ https://twitter.com/rev2nym
* @version 1.00a 2015/12/19 ヘルプ文書のイベントメモ欄設定の誤りを修正
* 1.00 2015/11/29 公開
*
* @help
* プラグインコマンドを実行するとマップが生成され
* プレイヤーが入口イベントの地点に移動します。
*
* ベースとなるマップの座標に下記のタイルとイベントをそれぞれ配置して下さい。
* ・タイル
*  空白:{x:0, y:0}
*  部屋:{x:0, y:1}
*  通路:{x:0, y:2}
*  天井:{x:0, y:3}
*  壁 :{x:0, y:4}
*  瓦礫:{x:0, y:5}
*
* ・イベント
*  入口:{x:1, y:0}
*  出口:{x:1, y:1}
*  他 :上記以外の座標
*
* 入口と出口以外のイベントには出現率を設定できます。
* イベントのメモ欄に下記を記載して下さい。
* 出現率設定がないイベント生成されません。
*
* ・イベント出現率
*  マップ毎の出現率:<RateMap: [1.0以下の正の小数]>
*  部屋毎の出現率 :<RateRoom:[1.0以下の正の小数]>
*
* ・プラグインコマンド
* MapGenerator RoomAndPass # 部屋と通路から構成されるマップを生成します。
* MapGenerator FillRoom    # マップ全体に及ぶ一つの部屋を生成します。
*
* プラグインパラメータはありません。
*
* MITライセンスのもと、商用利用、改変、再配布が可能です。
* ただし冒頭のコメントは削除や改変をしないでください。
* よかったらクレジットに作者名を記載してください。
*
* これを利用したことによるいかなる損害にも作者は責任を負いません。
* サポートは期待しないでください><。
*/

/*:zh
* @plugindesc SAN_MapGenerator ver1.00a
* 自动生成地图已经分配事件
* @author Sanshiro https://twitter.com/rev2nym
*
* @help
* 当使用插件指令后会生成一个自动生成的地图
* 然后玩家会被移动到入口
*
* 把图块和事件放在地图的对应位置
*
* -图块
*   空白  :{x:0, y:0}
*   房间地面  :{x:0, y:1}
*   道路  :{x:0, y:2}
*   屋顶  :{x:0, y:3}
*   墙   :{x:0, y:4}
*   屋瓦  :{x:0, y:5}
*
* -事件
*   开始(入口) :{x:1, y:0}
*   目标(出口) :{x:1, y:1}
*   其他 :之后的其他位置
*
* 除了开始和目标之外,可以为事件设置出现率。
* 在事件的备注内设置如下。
* 不会生成不具有出现率设置的事件。
*
* -出现几率
*   每个地图 :<RateMap: [Positive small number of less than 1.0]>
*   每个房间 :<RateRoom:[Positive small number of less than 1.0]>
*
* -插件指令
*   MapGenerator RoomAndPass # 生成有道路和房间的地图(地牢)
*   MapGenerator FillRoom    # 生成一个用房间设置填充满的房间
*
* 没有插件参数
*
*
* 使用MIT许可协议 XP
* 但是不要删除一开始的注释
* 如果可以保留原作者姓名
*
* 作者不承担使用该脚本后的任何责任
* 请不要期待支持.X(
*/

var Imported = Imported || {};
Imported.SAN_MapGenerator = true;

var Sanshiro = Sanshiro || {};
Sanshiro.Game_MapGenerator = Sanshiro.Game_MapGenerator || {};

//-----------------------------------------------------------------------------
// Game_MapGenerator
//
// マップジェネレーター(大部屋)

function Game_MapGenerator() {
    this.initialize();
}

// オートタイル解析タイルidリスト
//   tileIdsFloor : 床
//   tileIdsWall  : 壁
//   candidate    : 候補タイル ID
//   connect      : 接続タイル ID
//   noConnect    : 非接続タイル ID
Game_MapGenerator.tileIdsFloor = {};

Game_MapGenerator.tileIdsFloor.candidate =
    [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
     12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
     24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
     36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47];

Game_MapGenerator.tileIdsFloor.connect = {
    1:[ 0,  1,  2,  3,  4,  5,  6,  7, 16, 17, 18, 19,
       20, 21, 24, 26, 28, 29, 30, 31, 32, 33, 34, 35,
       36, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],         // 1:左下
    2:[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
       12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
       24, 25, 26, 27, 32, 34, 35, 36, 37, 42, 47],         // 2:下
    3:[ 0,  1,  2,  3,  8,  9, 10, 11, 16, 17, 20, 22,
       24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 36,
       37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],         // 3:右下
     4:[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
        12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27,
        28, 29, 30, 31, 33, 36, 37, 38, 39, 45, 47],         // 4:左
    6:[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
       12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
       28, 29, 30, 31, 33, 34, 35, 40, 41, 43, 47],         // 6:右
    7:[ 0,  2,  4,  6,  8, 10, 12, 14, 16, 17, 18, 19,
       20, 21, 22, 23, 24, 25, 28, 30, 32, 33, 34, 35,
       36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47],         // 7:左上
    8:[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
       12, 13, 14, 15, 16, 17, 18, 19, 24, 25, 26, 27,
       28, 29, 30, 31, 32, 38, 39, 40, 41, 44, 47],         // 8:上
    9:[ 0,  1,  4,  5,  8,  9, 12, 13, 16, 18, 20, 21,
       22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35,
       36, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47]          // 9:右上
};

Game_MapGenerator.tileIdsFloor.noConnect = {
    1:[ 8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
       22, 23, 25, 27, 28, 29, 30, 31, 32, 33, 34, 35,
       37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],         // 1:左下
    2:[28, 29, 30, 31, 33, 38, 39, 40, 41, 43, 44, 45, 46], // 2:下
    3:[ 4,  5,  6,  7, 12, 13, 14, 15, 18, 19, 21, 23,
       24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36,
       37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],         // 3:右下
    4:[16, 17, 18, 19, 32, 34, 35, 40, 41, 42, 43, 44, 46], // 4:左
    6:[24, 25, 26, 27, 32, 36, 37, 38, 39, 42, 44, 45, 46], // 6:右
    7:[ 1,  3,  5,  7,  9, 11, 13, 15, 16, 17, 18, 19,
       20, 21, 22, 23, 26, 27, 29, 31, 32, 33, 34, 35,
       36, 37, 39, 40, 41, 42, 43, 44, 45, 46, 47],         // 7:左上
    8:[20, 21, 22, 23, 33, 34, 35, 36, 37, 42, 43, 45, 46], // 8:上
    9:[ 2,  3,  6,  7, 10, 11, 14, 15, 17, 19, 20, 21,
       22, 23, 24, 25, 26, 27, 30, 31, 32, 33, 34, 35,
       36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47]          // 9:右上
};

Game_MapGenerator.tileIdsWall = {};

Game_MapGenerator.tileIdsWall.candidate =
    [ 0,  1,  2,  3,  4,  5,  6,  7,
      8,  9, 10, 11, 12, 13, 14, 15];

Game_MapGenerator.tileIdsWall.connect = {
    2:[ 0,  1,  2,  3,  4,  5,  6,  7],  // 2:下
    4:[ 0,  2,  4,  6,  8, 10, 12, 14],  // 4:左
    6:[ 0,  1,  2,  3,  8,  9, 10, 11],  // 6:右
    8:[ 0,  1,  4,  5,  8,  9, 12, 13]   // 8:上
};

Game_MapGenerator.tileIdsWall.noConnect = {
    2:[ 8,  9, 10, 11, 12, 13, 14, 15],  // 2:下
    4:[ 1,  3,  5,  7,  9, 11, 13, 15],  // 4:左
    6:[ 4,  5,  6,  7, 12, 13, 14, 15],  // 6:右
    8:[ 2,  3,  6,  7, 10, 11, 14, 15]   // 8:上
};

// 初期化
Game_MapGenerator.prototype.initialize = function() {
    this._wallHeight = 2;
    this._startXY = {x:0, y:0};
    this._goalXY  = {x:0, y:0};
    this._blocks  = [];
    this._rooms   = [];
    this._passes  = [];
    this._data    = [];
    this._isReady = false;
};

// マップ生成
Game_MapGenerator.prototype.setup = function() {
    $gameMap._events = [];
    for (key in $gameSelfSwitches._data) {
        if (key.split(",")[0] === String($gameMap.mapId())) {
            delete $gameSelfSwitches._data[key];
        }
    }
    this._isReady = false;
    this._blocks  = [];
    this._rooms   = [];
    this._passes  = [];
    this._startXY = {x:0, y:0};
    this._goalXY  = {x:0, y:0};
    this._data    = [];
    this.initSymbolTable();
    this.initSymbolMap();
    this.generateMap();
    this.refreshWallAndRoof();
    this.makeData();
    this.setStart();
    this.setGoal();
    this.setRateEvents();
    SceneManager._scene.createDisplayObjects();
    this._isReady = true;
}

// シンボル定義表の初期化
Game_MapGenerator.prototype.initSymbolTable = function() {
    // シンボル定義
    //  refXY      : シンボルに対応するタイルのツクールのマップ上の座標
    //  baseTileId : シンボルに対応するタイル ID
    //  dispChar   : 生成したマップを文字列として表示する際の文字
    this._symbolTable = {
        player: {refXY:{x:0, y:0}, baseTileId:[], dispChar:'@'},
        space:  {refXY:{x:0, y:0}, baseTileId:[], dispChar:' '},
        room:   {refXY:{x:0, y:1}, baseTileId:[], dispChar:'□'},
        pass:   {refXY:{x:0, y:2}, baseTileId:[], dispChar:'■'},
        roof:   {refXY:{x:0, y:3}, baseTileId:[], dispChar:'#'},
        wall:   {refXY:{x:0, y:4}, baseTileId:[], dispChar:'='},
        rubble: {refXY:{x:0, y:5}, baseTileId:[], dispChar:'*'},
        start:  {refXY:{x:1, y:0}, baseTileId:[], dispChar:'△'},
        goal:   {refXY:{x:1, y:1}, baseTileId:[], dispChar:'▽'}//,
    //  fence:  {refXY:{x:0, y:6}, baseTileId:[], dispChar:'只'},
    //  pond:   {refXY:{x:0, y:7}, baseTileId:[], dispChar:'○'},
    //  hole:   {refXY:{x:0, y:8}, baseTileId:[], dispChar:'●'},
    //  brink:  {refXY:{x:0, y:9}, baseTileId:[], dispChar:'^'},
    //  enemy:  {refXY:{x:1, y:2}, baseTileId:[], dispChar:'$'},
    //  crawler:{refXY:{x:0, y:1}, baseTileId:[], dispChar:'&'}
    };
    for (symbol in this._symbolTable) {
        var x = this._symbolTable[symbol].refXY.x;
        var y = this._symbolTable[symbol].refXY.y;
        for (var z = 0; z < 6; z ++) {
            // z0:タイルA下層, z1:タイルA上層, z2:タイルB下層, z3:タイルB上層, z4:影, z5:リージョン
            this._symbolTable[symbol].baseTileId[z] = this.baseAutoTileId(x, y, z);
        }
    }
};

// オートタイルタイルの基点タイルID
Game_MapGenerator.prototype.baseAutoTileId = function(x, y, z) {
    if ($gameMap.tileId(x, y, z) >= Tilemap.TILE_ID_A1) {
        return (Math.floor(($gameMap.tileId(x, y, z) - Tilemap.TILE_ID_A1) / 48)) * 48 + Tilemap.TILE_ID_A1;
    } else {
        return $gameMap.tileId(x, y, z);
    }
};

// シンボルで表現されるマップの初期化(初期化時はスペースで埋める)
Game_MapGenerator.prototype.initSymbolMap = function() {
    this._symbolMap = new Array($gameMap.width());
    for (var x = 0; x < $gameMap.width(); x++) {
        this._symbolMap[x] = new Array($gameMap.height());
        for (var y = 0; y < $gameMap.height(); y++) {
            this._symbolMap[x][y] = 'space';
        }
    }
};

// シンボルによる通行可能判定(通行可否定義)
Game_MapGenerator.prototype.isPassable = function(x, y) {
    if (!this._symbolMap[x] || !this._symbolMap[x][y]) {
        return false;
    }
    return ['room', 'pass', 'start', 'goal', 'crawler'].indexOf(this._symbolMap[x][y]) !== -1;
};

// マップ上における通行可能タイルの割合
Game_MapGenerator.prototype.passableRatio = function() {
    var passableCount = 0;
    for(var x = 0; x < $gameMap.width(); x++) {
        for(var y = 0; y < $gameMap.height(); y++) {
            if (this.isPassable(x, y)) {
                passableCount++;
            }
        }
    }
    return passableCount / ($gameMap.width() * $gameMap.height());
};

// シンボルマップ生成
Game_MapGenerator.prototype.generateMap = function() {
    var room = {x:0, y:0, w:0, h:0};
    room.x = 1;
    room.y = 1 + this._wallHeight;
    room.w = $dataMap.width - 4;
    room.h = $dataMap.height - (this._wallHeight + 1) * 4;
    for (var oX = 0; oX < room.w; oX++) {
        for (var oY = 0; oY < room.h; oY++) {
            this._symbolMap[oX + room.x][oY + room.y] = 'room';
        }
    }
    this._rooms.push(room);
}

// イベントの設置
Game_MapGenerator.prototype.setEvent = function(event, targetSymbols, targetArea) {
    targetSymbols = targetSymbols || ['room'];
    targetArea = targetArea || {x:0, y:0, w:$dataMap.width, h:$dataMap.height};
    var canSet = false;
    for (var x = targetArea.x; x < targetArea.x + targetArea.w && !canSet; x++) {
        for (var y = targetArea.y; y < targetArea.y + targetArea.h && !canSet; y ++) {
            canSet = (targetSymbols.indexOf(this._symbolMap[x][y]) !== -1);
        }
    }
    if (canSet) {
        for (var i = 0; i < Math.pow(targetArea.w * targetArea.h, 2); i++) {
            var x = targetArea.x + Math.randomInt(targetArea.w);
            var y = targetArea.y + Math.randomInt(targetArea.h);
            if ($gameMap.eventsXy(x, y).length === 0 &&
                targetSymbols.indexOf(this._symbolMap[x][y]) !== -1)
            {
                break;
            }
        }
        $gameMap._events.push(event);
        event._eventId = $gameMap._events.indexOf(event);
        event.setPosition(x, y);
        return {x:x, y:y}
    } else {
        return undefined;
    }
};

// 座標によるイベントデータの配列
Game_MapGenerator.prototype.dataMapEventsXy = function(x, y) {
    return $dataMap.events.filter(function(event) {
        return (!!event && event.x === x && event.y === y);
    }, this);
};

// スタート地点イベントの設置
Game_MapGenerator.prototype.setStart = function() {
    var refXY = this._symbolTable['start'].refXY;
    var event = new Game_Event($gameMap.mapId(), this.dataMapEventsXy(refXY.x, refXY.y)[0].id);
    this._startXY = this.setEvent(event);
    $gamePlayer.locate(this._startXY.x, this._startXY.y);
    $gamePlayer.reserveTransfer($gameMap.mapId(), this._startXY.x, this._startXY.y);
    $gameMap._interpreter.setWaitMode('transfer')
};

// ゴール地点イベントの設置
Game_MapGenerator.prototype.setGoal = function() {
    var refXY = this._symbolTable['goal'].refXY;
    var event = new Game_Event($gameMap.mapId(), this.dataMapEventsXy(refXY.x, refXY.y)[0].id);
    this._goalXY = this.setEvent(event);
};

// 確率イベントの設置
Game_MapGenerator.prototype.setRateEvents = function() {
    var mapDataRateMapEvents = $dataMap.events.filter(function(event) {
        return !!event && !!event.meta.RateMap;
    });
    mapDataRateMapEvents.forEach(function(mapDataEvent) {
        if (this.randBool(parseFloat(mapDataEvent.meta.RateMap))) {
            var event = new Game_Event($gameMap.mapId(), mapDataEvent.id);
            this.setEvent(event);
        }
    }, this);
    var mapDataRateRoomEvents = $dataMap.events.filter(function(event) {
        return !!event && !!event.meta.RateRoom;
    });
    mapDataRateRoomEvents.forEach(function(mapDataEvent) {
        this._rooms.forEach(function(room) {
            if (this.randBool(parseFloat(mapDataEvent.meta.RateRoom))) {
                var event = new Game_Event($gameMap.mapId(), mapDataEvent.id);
                this.setEvent(event, 'room', room);
            }
        }, this);
    }, this);
};

//ランダムブール
//probability : true が返る確立
Game_MapGenerator.prototype.randBool = function(probability) {
    return Math.random() < probability;
};

// シンボルマップの壁と天井を設置:マップ全体
// 床と通路だけのシンボルマップに壁と天井を追加する
Game_MapGenerator.prototype.refreshWallAndRoof = function() {
    for (var x = 0; x < this._symbolMap.length; x++) {
        for (var y = 0; y < this._symbolMap[x].length; y++) {
            if (!this.isPassable(x, y)) {
                continue;
            }
            this.refreshWallAndRoofUpperSide(x - 1, y - 1);  // 左上
            this.refreshWallAndRoofUpper(x, y - 1);          // 上
            this.refreshWallAndRoofUpperSide(x + 1, y - 1);  // 右上
            this.refreshWallAndRoofSide(x - 1, y);           // 左
            this.refreshWallAndRoofSide(x + 1, y);           // 右
            this.refreshWallAndRoofDowner(x - 1, y + 1);     // 左下
            this.refreshWallAndRoofDowner(x, y + 1);         // 下
            this.refreshWallAndRoofDowner(x + 1, y + 1);     // 右下
        }
    }
    for (var x = this._symbolMap.length - 1; x >= this._symbolMap.length; x--) {
        for (var y = this._symbolMap[x].length - 1; y >= this._symbolMap[x].length; y--) {
            if (this._symbolMap[x][y] === 'roof' && this._symbolMap[x][y - 1] === 'wall') {
                this._symbolMap[x][y - 1] = 'roof';
            }
        }
    }
};

// シンボルマップの壁と天井を設置:上
Game_MapGenerator.prototype.refreshWallAndRoofUpper = function(x, y) {
    if (!this._symbolMap[x] || !this._symbolMap[x][y] || this.isPassable(x, y)) {
        return;
    }
    for (var h = 0; h < y && !this.isPassable(x, y - h); h++);
    if (h > this._wallHeight) {
        for (var wH = 0; wH < this._wallHeight; wH++) {
            this._symbolMap[x][y - wH] = 'wall';
        }
        this._symbolMap[x][y - this._wallHeight] = 'roof';
    } else {
        for (var wH = 0; wH < h; wH++) {
            if (this._symbolMap[x][y - wH] === 'space') {
                this._symbolMap[x][y - wH] = 'rubble';
            }
        }
    }
};

// シンボルマップの壁と天井を設置:下
Game_MapGenerator.prototype.refreshWallAndRoofDowner = function(x, y) {
    if (!this._symbolMap[x] || !this._symbolMap[x][y] || this.isPassable(x, y)) {
        return;
    }
    for (var h = 0; h + y < $gameMap.height() && !this.isPassable(x, y + h); h++);
    if (h > this._wallHeight) {
        this._symbolMap[x][y] = 'roof';
        for (var wH = 0; wH < this._wallHeight; wH++) {
            if (this._symbolMap[x][y + wH + 1] !== 'roof') {
                this._symbolMap[x][y + wH + 1] = 'wall';
            }
        }
    } else {
        for (var wH = 0; wH < h; wH++) {
            if (this._symbolMap[x][y + wH] === 'space') {
                this._symbolMap[x][y + wH] = 'rubble';
            }
        }
    }
};

//シンボルマップの壁と天井を設置:横
Game_MapGenerator.prototype.refreshWallAndRoofSide = function(x, y) {
    if (!this._symbolMap[x] || !this._symbolMap[x][y] || this.isPassable(x, y)) {
        return;
    }
    if (this.isPassable(x, y + 1)) {
        this.refreshWallAndRoofUpper(x, y);
    } else {
        this.refreshWallAndRoofDowner(x, y);
    }
};

// シンボルマップの壁と天井を設置:斜め上
Game_MapGenerator.prototype.refreshWallAndRoofUpperSide = function(x, y) {
    if (!this._symbolMap[x] || !this._symbolMap[x][y] || this.isPassable(x, y)) {
        return;
    }
    this.refreshWallAndRoofDowner(x, y - this._wallHeight);
};

// オートタイルを考慮したタイルID
Game_MapGenerator.prototype.autoTileId = function(x, y, z) {
    if ((x < 0 || x >= $dataMap.width) || (y < 0 || y >= $dataMap.height)) {
        return 0;
    } else if (z === 4) {
        return this.shadow(x, y);
    } else if ((z !== 0 && z !== 1) || this._symbolTable[this._symbolMap[x][y]].baseTileId[z] === 0) {
        return this._symbolTable[this._symbolMap[x][y]].baseTileId[z];
    }
    var candidateTileIds = [];
    if (this._symbolMap[x][y] !== 'wall') {
        // 壁以外の場合
        candidateTileIds = Game_MapGenerator.tileIdsFloor.candidate.concat();
        [1, 2, 3, 4, 6, 7, 8, 9].forEach (function(direction) {
            var dx = x + Math.floor((direction - 1) % 3) - 1;
            var dy = y - Math.floor((direction - 1) / 3) + 1;
            if ((dx < 0 || dx >= $dataMap.width) || (dy < 0 || dy >= $dataMap.height)) {
                return; // マップ範囲外なら判定しない
            }
            if (this._symbolMap[x][y] === this._symbolMap[dx][dy]) {
                candidateTileIds = candidateTileIds.filter(function(Id) {
                    return Game_MapGenerator.tileIdsFloor.connect[direction].indexOf(Id) !== -1;
                }); // 同種シンボルの場合候補タイルIDから接続タイルIDを選択
            } else {
                candidateTileIds = candidateTileIds.filter(function(Id) {
                    return Game_MapGenerator.tileIdsFloor.noConnect[direction].indexOf(Id) !== -1;
                }); // 異種シンボルの場合候補タイルIDから非接続タイルIDを選択
            }
        }, this);
    } else {
        // 壁の場合
        candidateTileIds = Game_MapGenerator.tileIdsWall.candidate.concat();
        for (var by = y; this._symbolMap[x][y] === this._symbolMap[x][by + 1]; by++);  // 壁の下端
        for (var ty = y; this._symbolMap[x][y] === this._symbolMap[x][ty - 1]; ty--);  // 壁の上端
        // 上下の処理
        [2, 8].forEach(function(direction) {
            var dx = x + Math.floor((direction - 1) % 3) - 1;
            var dy = y - Math.floor((direction - 1) / 3) + 1;
            if ((dx < 0 || dx >= $dataMap.width) || (dy < 0 || dy >= $dataMap.height)) {
                return; // マップ範囲外なら判定しない
            }
            if (this._symbolMap[x][y] === this._symbolMap[dx][dy]) {
                candidateTileIds = candidateTileIds.filter(function(Id) {
                    return Game_MapGenerator.tileIdsWall.connect[direction].indexOf(Id) !== -1;
                }); // 同種シンボルの場合候補タイルIDから接続タイルIDを選択
            } else {
                candidateTileIds = candidateTileIds.filter(function(Id) {
                    return Game_MapGenerator.tileIdsWall.noConnect[direction].indexOf(Id) !== -1;
                }); // 異種シンボルの場合候補タイルIDから非接続タイルIDを選択
            }
        }, this);
        // 左右の処理
        [4, 6].forEach(function(direction) {
            var dx = x + Math.floor((direction - 1) % 3) - 1;
            var dy = y - Math.floor((direction - 1) / 3) + 1;
            if ((dx < 0 || dx >= $dataMap.width) || (dy < 0 || dy >= $dataMap.height)) {
                return; // マップ範囲外なら判定しない
            }
            if ((this._symbolMap[dx][ty] === 'wall' || this._symbolMap[dx][ty] === 'roof') &&
                (this._symbolMap[dx][by] === 'wall' || this._symbolMap[dx][by] === 'roof'))
            {
                candidateTileIds = candidateTileIds.filter(function(Id) {
                    return Game_MapGenerator.tileIdsWall.connect[direction].indexOf(Id) !== -1;
                }); // 壁の下端の両横隣が壁または天井でかつ上端の両横隣が壁または天井でなければ接続タイルIDを選択
            } else {
                candidateTileIds = candidateTileIds.filter(function(Id) {
                    return Game_MapGenerator.tileIdsWall.noConnect[direction].indexOf(Id) !== -1;
                }); // 非接続タイルIDを選択
            }
        }, this);
    }
    return this._symbolTable[this._symbolMap[x][y]].baseTileId[z] + candidateTileIds[0];
};

// 影の算出
Game_MapGenerator.prototype.shadow = function(x, y) {
    if (!this._symbolMap[x - 1] ||
        this._symbolMap[x][y] === 'space'||
        this._symbolMap[x][y] === 'roof' ||
        this._symbolMap[x][y] === 'wall')
    {
        return 0;
    } else if (this._symbolMap[x - 1][y] === 'roof') {
        if (this._symbolMap[x - 1][y - 1] === 'roof' ||
            this._symbolMap[x - 1][y - 1] === 'wall')
        {
            return 5;
        }
    }  else if (this._symbolMap[x - 1][y] === 'wall') {
        return 5;
    }
    return 0;
};

//マップデータ作成
Game_MapGenerator.prototype.makeData = function() {
    var width = $dataMap.width;
    var height = $dataMap.height;
    for (var x = 0; x < this._symbolMap.length; x++) {
        for (var y = 0; y < this._symbolMap[x].length; y++) {
            for (var z = 0; z < 6; z++) {
                this._data[(z * height + y) * width + x] = this.autoTileId(x, y, z);
            }
        }
    }
};

// マップデータ
Game_MapGenerator.prototype.data = function() {
    return this._data;
};

// タイルID
Game_MapGenerator.prototype.tileId = function(x, y, z) {
    return this._data[(z * $dataMap.height + y) * $dataMap.width + x];
};

// 準備完了判定
Game_MapGenerator.prototype.isReady = function() {
    return this._isReady;
};

// マップのコンソール表示(デバッグ用)
Game_MapGenerator.prototype.printMap = function() {
    var dispMap = "";
    for (var y = 0; y < this._symbolMap[0].length; y++) {
        for (var x = 0; x < this._symbolMap.length; x++) {
            dispMap += this._symbolTable[this._symbolMap[x][y]].dispChar;
        }
        dispMap += "\r\n";
    }
    console.log(dispMap);
    // this.fPrintMap(dispMap);
};

// テキスト出力(デバッグ用)
Game_MapGenerator.prototype.fPrintMap = function(mapString) {
    var data = LZString.compressToBase64(mapString);
    var fs = require('fs');
    var dirPath = StorageManager.localFileDirectoryPath();
    var filePath = StorageManager.localFileDirectoryPath() + 'mapData.txt';
    if (!fs.existsSync(dirPath)) {
        fs.mkdirSync(dirPath);
    }
    fs.writeFileSync(filePath, mapString);
};

//-----------------------------------------------------------------------------
// Game_MapGeneratorRoomAndPass
//
// マップジェネレーター(部屋と通路)

function Game_MapGeneratorRoomAndPass() {
    this.initialize.apply(this, arguments);
}

Game_MapGeneratorRoomAndPass.prototype = Object.create(Game_MapGenerator.prototype);
Game_MapGeneratorRoomAndPass.prototype.constructor = Game_MapGeneratorRoomAndPass;

// 初期化
Game_MapGeneratorRoomAndPass.prototype.initialize = function() {
    Game_MapGenerator.prototype.initialize.call(this);
};

// マップ(ダンジョン)自動生成
Game_MapGeneratorRoomAndPass.prototype.generateMap = function() {
    this._minRoomSize = 4;
    this._minBlockSize = this._minRoomSize;
    this._minRooms = 2;
    this._maxRooms = 25;
    this._adjacentBlockIndexList = [];
    var block = {
        x:1,
        y:1 + this._wallHeight,
        w:$dataMap.width - 2,
        h:$dataMap.height - (this._wallHeight + 1) * 2
    };
    this._blocks.push(block);
    this.splitBlock(this._blocks[0]);
    this.makeAdjacentBlockIndexList();
    this.makeRooms();
    this.makePasses();
};

// 隣り合うブロックのリスト作成
Game_MapGeneratorRoomAndPass.prototype.makeAdjacentBlockIndexList = function() {
    for (var crntIndex = 0; crntIndex < this._blocks.length; crntIndex++) {
        var crntBlock = this._blocks[crntIndex];
        this._adjacentBlockIndexList[crntIndex] = {t:[], b:[], l:[], r:[]};
        for (var tgetIndex = 0; tgetIndex < this._blocks.length; tgetIndex++) {
            var tgetBlock = this._blocks[tgetIndex];
            if (crntBlock === tgetBlock) {
                continue;
            }
            var adjacentT = (crntBlock.y === tgetBlock.y + tgetBlock.h + 1);
            var adjacentB = (tgetBlock.y === crntBlock.y + crntBlock.h + 1);
            var adjacentL = (crntBlock.x === tgetBlock.x + tgetBlock.w + 1);
            var adjacentR = (tgetBlock.x === crntBlock.x + crntBlock.w + 1);
            if (!adjacentT && !adjacentB && !adjacentL && !adjacentR) {
                continue;
            }
            var matchH =
                (tgetBlock.x <= crntBlock.x + crntBlock.w && tgetBlock.x >= crntBlock.x) ||
                (tgetBlock.x + tgetBlock.w <= crntBlock.x + crntBlock.w && tgetBlock.x + tgetBlock.w >= crntBlock.x) ||
                (crntBlock.x <= tgetBlock.x + tgetBlock.w && crntBlock.x >= tgetBlock.x) ||
                (crntBlock.x + crntBlock.w <= tgetBlock.x + tgetBlock.w && crntBlock.x + crntBlock.w >= tgetBlock.x);
            var matchV =
                (tgetBlock.y <= crntBlock.y + crntBlock.h && tgetBlock.y >= crntBlock.y) ||
                (tgetBlock.y + tgetBlock.h <= crntBlock.y + crntBlock.h && tgetBlock.y + tgetBlock.h >= crntBlock.y) ||
                (crntBlock.y <= tgetBlock.y + tgetBlock.h && crntBlock.y >= tgetBlock.y) ||
                (crntBlock.y + crntBlock.h <= tgetBlock.y + tgetBlock.h && crntBlock.y + crntBlock.h >= tgetBlock.y);
            if (adjacentT && matchH) {
                this._adjacentBlockIndexList[crntIndex].t.push(tgetIndex);
                continue;
            } else if (adjacentB && matchH) {
                this._adjacentBlockIndexList[crntIndex].b.push(tgetIndex);
                continue;
            }
            if (adjacentL && matchV) {
                this._adjacentBlockIndexList[crntIndex].l.push(tgetIndex);
                continue;
            } else if (adjacentR && matchV) {
                this._adjacentBlockIndexList[crntIndex].r.push(tgetIndex);
                continue;
            }
        }
    }
};

// 部屋作成
Game_MapGeneratorRoomAndPass.prototype.makeRooms = function() {
    this._blocks.forEach(function(block) {
        var roomW = this._minRoomSize + Math.randomInt(block.w - this._minRoomSize - 2);
        var roomH = this._minRoomSize + Math.randomInt(block.h - this._minRoomSize - 2);
        var roomX = block.x + 1 + Math.randomInt(block.w - roomW - 2);     
        var roomY = block.y + 1 + Math.randomInt(block.h - roomH - 2);     
        var room = {x:roomX, y:roomY, w:roomW, h:roomH};
        this._rooms.push(room);
    }, this);
    this._rooms.forEach(function(room) {
        for (var y = 0; y < room.h; y++) {
            for (var x = 0 ; x < room.w; x++) {
                this._symbolMap[room.x + x][room.y + y] = 'room';
            }
        }
    }, this);
};

// 通路作成
Game_MapGeneratorRoomAndPass.prototype.makePasses = function() {
    var cache = {};
    for (var crntIndex = 0; crntIndex < this._adjacentBlockIndexList.length; crntIndex++ ) {
        cache[crntIndex] = [];
        var crngBlock = this._blocks[crntIndex];
        for(var direction in this._adjacentBlockIndexList[crntIndex]) {
            var tgetIndexList = this._adjacentBlockIndexList[crntIndex][direction];
            tgetIndexList.forEach(function(tgetIndex) {
                if (cache[tgetIndex] !== undefined && cache[tgetIndex].indexOf(crntIndex) !== -1)
                {
                    return;
                }
                cache[crntIndex].push(tgetIndex);
                var tgetBlock = this._blocks[tgetIndex];
                var crntRoom = this._rooms[crntIndex];
                var tgetRoom = this._rooms[tgetIndex];
                var crntPass = {};
                var tgetPass = {};
                var bordPass = {};
                switch (direction) {
                case 't':
                    crntPass.x = crntRoom.x + 1 + Math.randomInt(crntRoom.w - 2);
                    crntPass.y = crngBlock.y;
                    crntPass.w = 1;
                    crntPass.h = crntRoom.y - crngBlock.y;
                    tgetPass.x = tgetRoom.x + 1 + Math.randomInt(tgetRoom.w - 2);
                    tgetPass.y = tgetRoom.y + tgetRoom.h;
                    tgetPass.w = 1;
                    tgetPass.h = crngBlock.y - tgetPass.y;
                    bordPass.x = Math.min(crntPass.x, tgetPass.x);
                    bordPass.y = crngBlock.y - 1;
                    bordPass.w = Math.max(crntPass.x, tgetPass.x) - bordPass.x + 1;
                    bordPass.h = 1;
                    break;
                case 'b':
                    crntPass.x = crntRoom.x + 1 + Math.randomInt(crntRoom.w - 2);
                    crntPass.y = crntRoom.y + crntRoom.h;
                    crntPass.w = 1;
                    crntPass.h = tgetBlock.y - crntPass.y;
                    tgetPass.x = tgetRoom.x + 1 + Math.randomInt(tgetRoom.w - 2);
                    tgetPass.y = tgetBlock.y;
                    tgetPass.w = 1;
                    tgetPass.h = tgetRoom.y - tgetBlock.y;
                    bordPass.x = Math.min(crntPass.x, tgetPass.x);
                    bordPass.y = tgetBlock.y - 1;
                    bordPass.w = Math.max(crntPass.x, tgetPass.x) - bordPass.x + 1;
                    bordPass.h = 1;
                    break;
                case 'l':
                    crntPass.x = crngBlock.x - 1;
                    crntPass.y = crntRoom.y + 1 + Math.randomInt(crntRoom.h - 2);
                    crntPass.w = crntRoom.x - crntPass.x;
                    crntPass.h = 1;
                    tgetPass.x = tgetRoom.x + tgetRoom.w;
                    tgetPass.y = tgetRoom.y + 1 + Math.randomInt(tgetRoom.h - 2);
                    tgetPass.w = crntPass.x - tgetRoom.x - tgetRoom.w;
                    tgetPass.h = 1;
                    bordPass.x = crngBlock.x - 1;
                    bordPass.y = Math.min(crntPass.y, tgetPass.y);
                    bordPass.w = 1;
                    bordPass.h = Math.max(crntPass.y, tgetPass.y) - bordPass.y + 1;
                    break;
                case 'r':
                    crntPass.x = crntRoom.x + crntRoom.w
                    crntPass.w = tgetBlock.x - 1 - crntRoom.x - crntRoom.w
                    crntPass.y = crntRoom.y + 1 + Math.randomInt(crntRoom.h - 2);
                    crntPass.h = 1;
                    tgetPass.x = tgetBlock.x - 1;
                    tgetPass.y = tgetRoom.y + 1 + Math.randomInt(tgetRoom.h - 2);
                    tgetPass.w = tgetRoom.x - tgetPass.x;
                    tgetPass.h = 1;
                    bordPass.x = tgetBlock.x - 1;
                    bordPass.y = Math.min(crntPass.y, tgetPass.y);
                    bordPass.w = 1;
                    bordPass.h = Math.max(crntPass.y, tgetPass.y) - bordPass.y + 1;
                    break;
                }
                this._passes.push(crntPass);
                this._passes.push(tgetPass);
                this._passes.push(bordPass);
            }, this);
        }
    }
    this._passes.forEach(function(pass) {
        for (var y = 0; y < pass.h; y++) {
            for (var x = 0; x < pass.w; x++) {
                this._symbolMap[pass.x + x][pass.y + y] = 'pass';
            }
        }
    }, this);
};

// ブロック分割:ランダム
Game_MapGeneratorRoomAndPass.prototype.splitBlock = function(block) {
    if (this.randBool(0.5)) {
        if (this.isSplitableH(block)) {
            this.splitBlockH(block);
        }
        if (this.isSplitableV(block)) {
            this.splitBlockV(block);
        }
    } else {
        if (this.isSplitableV(block)) {
            this.splitBlockV(block);
        }
        if (this.isSplitableH(block)) {
            this.splitBlockH(block);
        }
    }
};

//ブロック分割:横分割
Game_MapGeneratorRoomAndPass.prototype.splitBlockH = function(block) {
    var width1 = 0;
    var width2 = 0;
    while (width1 < this._minBlockSize || width2 < this._minBlockSize) {
        width1 = Math.floor(block.w / 4 + block.w * Math.random() / 2);
        width2 = block.w - width1 - 1;
    }
    block.w = width1;
    var newBlock = {x:block.x + width1 + 1, y:block.y, w:width2, h:block.h};
    this._blocks.push(newBlock);
    this.splitBlock(block);
    this.splitBlock(newBlock);
};

// ブロック分割:縦分割
Game_MapGeneratorRoomAndPass.prototype.splitBlockV = function(block) {
    var height1 = 0;
    var height2 = 0;
    while (height1 < this._minBlockSize || height2 < this._minBlockSize) {
        height1 = Math.floor(block.h / 4 + block.h * Math.random() / 2);
        height2 = block.h - height1 - 1;
    }
    block.h = height1;
    var newBlock = {x:block.x, y:block.y + height1 + 1, w:block.w, h:height2};
    this._blocks.push(newBlock);
    this.splitBlock(block);
    this.splitBlock(newBlock);
};

// ブロック分割可能判定:部屋数
Game_MapGeneratorRoomAndPass.prototype.isSplitableByRoomNum = function() {
    if (this._blocks.length >= this._maxRooms) {
        return false;
    }
    if (this._blocks.length >= this._minRooms &&
        this.randBool((this._blocks.length - this._minRooms + 1) / (this._maxRooms - this.minRooms + 1)))
    {
        return false;
    }
    return true;
};

// ブロック分割可能判定:横分割
Game_MapGeneratorRoomAndPass.prototype.isSplitableV = function(block) {
    return (block.h > (this._minRoomSize + 2) * 2 + 1) && this.isSplitableByRoomNum();
};

// ブロック分割可能判定:縦分割
Game_MapGeneratorRoomAndPass.prototype.isSplitableH = function(block) {
    return (block.w > (this._minRoomSize + 2) * 2 + 1) && this.isSplitableByRoomNum();
};

// 座標によって部屋を取得
Game_MapGeneratorRoomAndPass.prototype.roomByXY = function(x, y) {
    this._rooms.forEach(function(room) {
        if (room.x <= x && x <= room.x + room.w && room.y <= y && y <= room.y + room.h) {
            return result = room;
        }
    }, this);
    return undefined;
};

//-----------------------------------------------------------------------------
// Game_Map
//
// マップクラス

// マップクラスの初期化
Sanshiro.Game_MapGenerator.Game_Map_initialize = Game_Map.prototype.initialize;
Game_Map.prototype.initialize = function() {
    Sanshiro.Game_MapGenerator.Game_Map_initialize.call(this);
};

// マップクラスのセットアップ
Sanshiro.Game_MapGenerator.Game_Map_setup = Game_Map.prototype.setup;
Game_Map.prototype.setup = function(mapId) {
    Sanshiro.Game_MapGenerator.Game_Map_setup.call(this, mapId)
    this._mapGenerator = null;
};

// マップクラスのタイルID
Sanshiro.Game_MapGenerator.Game_Map_tileId = Game_Map.prototype.tileId
Game_Map.prototype.tileId = function(x, y, z) {
    if (!!this._mapGenerator && this._mapGenerator.isReady()) {
        return this._mapGenerator.tileId(x, y, z);
    } else {
        return Sanshiro.Game_MapGenerator.Game_Map_tileId.call(this, x, y, z);
    }
};

// マップクラスのマップデータ
Sanshiro.Game_MapGenerator.Game_Map_data = Game_Map.prototype.data
Game_Map.prototype.data = function() {
    if (!!this._mapGenerator && this._mapGenerator.isReady()) {
        return this._mapGenerator.data();
    } else {
        return Sanshiro.Game_MapGenerator.Game_Map_data.call(this);
    }
};

// マップクラスのマップ自動生成
Game_Map.prototype.generateMap = function(mapType) {
    mapType = mapType || 'FillRoom';
    switch (mapType) {
    case 'RoomAndPass':
        this._mapGenerator = new Game_MapGeneratorRoomAndPass();
        break;
    case 'FillRoom':
        this._mapGenerator = new Game_MapGenerator();
        break;
    }
    this._mapGenerator.setup();
    if (Imported.SAN_AnalogMove) {
        Game_CollideMap.setup();
    }
};

//-----------------------------------------------------------------------------
// Game_Event
//
// イベントクラス

// イベントクラスの初期化
Sanshiro.Game_MapGenerator.Game_Event_initialize = Game_Event.prototype.initialize;
Game_Event.prototype.initialize = function(mapId, eventId) {
    this._dataEventId = eventId;
    Sanshiro.Game_MapGenerator.Game_Event_initialize.call(this, mapId, eventId);
};

// イベントクラスのデータベースのイベントデータ
Game_Event.prototype.event = function() {
    return $dataMap.events[this._dataEventId];
};

//-----------------------------------------------------------------------------
// Game_Interpreter
//
// インタープリタークラス

// プラグインコマンド
Sanshiro.Game_MapGenerator.Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args) {
    Sanshiro.Game_MapGenerator.Game_Interpreter_pluginCommand.call(this, command, args);
    if (command === 'MapGenerator') {
        $gameMap.generateMap(args[0]);
    }
};
[/pre]

一个我修改过后生成的效果
GenerateBaseMap1-3.png 因为最多房间数和最小房间数都是固定设置。所以就很拥挤但小了就很空。。这就是应该用地图长宽来设置的原因

SANMapGenerator.zip (317.64 KB, 下载次数: 767)


其实这个生成的算法还是有点差强人意的= =b 房间和道路的链接实在是怪。。

评分

参与人数 2星屑 +200 收起 理由
king + 80 迷宫最佳,加点事件机关即可
garfeng + 120 塞糖

查看全部评分

梦石
2
星屑
5550
在线时间
2566 小时
注册时间
2012-2-9
回帖
947

开拓者

发表于 2017-6-17 14:32:32 | 显示全部楼层
支持,自己再修饰一下就可以了。用来偷懒简直太棒
回复

使用道具 举报

梦石
0
星屑
50
在线时间
12 小时
注册时间
2016-11-5
回帖
4
发表于 2017-7-10 14:41:31 | 显示全部楼层
这个不错的 自己在按照意愿修改下就行了
回复

使用道具 举报

梦石
0
星屑
3922
在线时间
1198 小时
注册时间
2016-8-9
回帖
2355

开拓者

发表于 2017-7-10 17:51:45 | 显示全部楼层
弱弱的问一下这个插件和软件自带的地图生成器的区别,谢谢大大

点评

楼主那个生成出来可能会比默认的复杂一些吧  发表于 2017-7-19 15:10
我用了,真感觉不出有什么区别,都是地下城,都是光秃秃的路和墙,你知道区别,请大神不吝赐教!  发表于 2017-7-17 23:42
系统自带的自己用用就能知道区别了= =  发表于 2017-7-15 20:23
酸酸甜甜就④哇噢
回复

使用道具 举报

梦石
0
星屑
25359
在线时间
1136 小时
注册时间
2019-3-5
回帖
1303
发表于 2019-12-1 21:25:04 | 显示全部楼层
用的压缩包里的文件,测试后发现生成会出现没有设定的图块。而且遇到一些地方无法左右移动的情况,不知道问题出在哪里。
回复

使用道具 举报

梦石
0
星屑
1285
在线时间
121 小时
注册时间
2019-11-16
回帖
121
发表于 2019-12-3 10:44:48 | 显示全部楼层
多谢分享
回复

使用道具 举报

梦石
0
星屑
2184
在线时间
124 小时
注册时间
2019-8-12
回帖
25
发表于 2020-5-9 20:24:36 | 显示全部楼层
白嫩白嫩的 发表于 2017-7-10 17:51
弱弱的问一下这个插件和软件自带的地图生成器的区别,谢谢大大

系统自带的不是在游戏内随机生成的呀,这个插件主要是可以实现在游戏内的随机生成吧。虽然你用系统自带的做多个模板也是OK的,但是这个插件确实能省不少时间,而且还可以设置事件的随机生成,可以做简单的随机地下迷宫……

点评

感谢大神三年后解惑233333  发表于 2020-5-10 21:16

评分

参与人数 1赞 +1 收起 理由
白嫩白嫩的 + 1 666666

查看全部评分

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部