// Generated by CoffeeScript 1.12.7
(function() {
  var CLIENT_get_authorize_key, CLIENT_get_kick_reconnect_target, CLIENT_heartbeat_register, CLIENT_heartbeat_unregister, CLIENT_import_data, CLIENT_is_able_to_kick_reconnect, CLIENT_is_able_to_reconnect, CLIENT_is_banned_by_mc, CLIENT_is_player, CLIENT_kick, CLIENT_kick_reconnect, CLIENT_pre_reconnect, CLIENT_reconnect, CLIENT_reconnect_register, CLIENT_reconnect_unregister, CLIENT_send_pre_reconnect_info, CLIENT_send_reconnect_info, CLIENT_send_replays, Cloud_replay_ids, ROOM_all, ROOM_bad_ip, ROOM_ban_player, ROOM_clear_disconnect, ROOM_connected_ip, ROOM_find_by_name, ROOM_find_by_pid, ROOM_find_by_port, ROOM_find_by_title, ROOM_find_or_create_ai, ROOM_find_or_create_by_name, ROOM_find_or_create_random, ROOM_player_flee, ROOM_player_get_score, ROOM_player_lose, ROOM_player_win, ROOM_players_banned, ROOM_players_oppentlist, ROOM_players_scores, ROOM_unwelcome, ROOM_validate, Room, SERVER_clear_disconnect, SOCKET_flush_data, _, addCallback, auth, badwords, ban_user, bunyan, challonge, challonge_cache, challonge_module_name, challonge_queue_callbacks, chat_color, config, cppversion, crypto, date, default_config, default_data, dialogues, disconnect_list, dns, duel_log, e, exec, execFile, fs, geoip, get_callback, get_memory_usage, http, http_server, https, https_server, import_datas, imported, is_requesting, j, l, len, len1, len2, lflists, list, loadJSON, load_dialogues, load_tips, log, long_resolve_cards, m, memory_usage, merge, moment, net, oldbadwords, oldconfig, olddialogues, oldduellog, oldtips, options, os, path, pgClient, pg_client, pg_query, plugin_filename, plugin_list, plugin_path, real_windbot_server_ip, rebooted, redis, redisdb, ref, ref1, refresh_challonge_cache, release_disconnect, report_to_big_brother, request, requestListener, roomlist, setting_change, setting_save, settings, spawn, spawnSync, spawn_windbot, tips, url, users_cache, wait_room_start, wait_room_start_arena, windbot_looplimit, windbot_process, windbots, ygopro, zlib;

  net = require('net');

  http = require('http');

  url = require('url');

  path = require('path');

  fs = require('fs');

  os = require('os');

  crypto = require('crypto');

  exec = require('child_process').exec;

  execFile = require('child_process').execFile;

  spawn = require('child_process').spawn;

  spawnSync = require('child_process').spawnSync;

  _ = require('underscore');

  _.str = require('underscore.string');

  _.mixin(_.str.exports());

  request = require('request');

  bunyan = require('bunyan');

  log = bunyan.createLogger({
    name: "mycard"
  });

  moment = require('moment');

  moment.updateLocale('zh-cn', {
    relativeTime: {
      future: '%s内',
      past: '%s前',
      s: '%d秒',
      m: '1分钟',
      mm: '%d分钟',
      h: '1小时',
      hh: '%d小时',
      d: '1天',
      dd: '%d天',
      M: '1个月',
      MM: '%d个月',
      y: '1年',
      yy: '%d年'
    }
  });

  import_datas = ["abuse_count", "ban_mc", "vpass", "rag", "rid", "is_post_watcher", "retry_count", "name", "pass", "name_vpass", "is_first", "lp", "card_count", "is_host", "pos", "surrend_confirm", "kick_count", "deck_saved", "main", "side", "side_interval", "side_tcount", "selected_preduel", "last_game_msg", "last_game_msg_title", "last_hint_msg", "start_deckbuf", "challonge_info", "ready_trap", "join_time", "arena_quit_free", "replays_sent"];

  merge = require('deepmerge');

  loadJSON = require('load-json-file').sync;

  if (!fs.existsSync('./config')) {
    fs.mkdirSync('./config');
  }

  try {
    oldconfig = loadJSON('./config.user.json');
    if (oldconfig.tips) {
      oldtips = {};
      oldtips.file = './config/tips.json';
      oldtips.tips = oldconfig.tips;
      fs.writeFileSync(oldtips.file, JSON.stringify(oldtips, null, 2));
      delete oldconfig.tips;
    }
    if (oldconfig.dialogues) {
      olddialogues = {};
      olddialogues.file = './config/dialogues.json';
      olddialogues.dialogues = oldconfig.dialogues;
      fs.writeFileSync(olddialogues.file, JSON.stringify(olddialogues, null, 2));
      delete oldconfig.dialogues;
    }
    if (oldconfig.modules) {
      if (oldconfig.modules.tournament_mode && oldconfig.modules.tournament_mode.duel_log) {
        oldduellog = {};
        oldduellog.file = './config/duel_log.json';
        oldduellog.duel_log = oldconfig.modules.tournament_mode.duel_log;
        fs.writeFileSync(oldduellog.file, JSON.stringify(oldduellog, null, 2));
        delete oldconfig.oldduellog;
      }
    }
    oldbadwords = {};
    if (oldconfig.ban) {
      if (oldconfig.ban.badword_level0) {
        oldbadwords.level0 = oldconfig.ban.badword_level0;
      }
      if (oldconfig.ban.badword_level1) {
        oldbadwords.level1 = oldconfig.ban.badword_level1;
      }
      if (oldconfig.ban.badword_level2) {
        oldbadwords.level2 = oldconfig.ban.badword_level2;
      }
      if (oldconfig.ban.badword_level3) {
        oldbadwords.level3 = oldconfig.ban.badword_level3;
      }
    }
    if (!_.isEmpty(oldbadwords)) {
      oldbadwords.file = './config/badwords.json';
      fs.writeFileSync(oldbadwords.file, JSON.stringify(oldbadwords, null, 2));
      delete oldconfig.ban.badword_level0;
      delete oldconfig.ban.badword_level1;
      delete oldconfig.ban.badword_level2;
      delete oldconfig.ban.badword_level3;
    }
    if (!_.isEmpty(oldconfig)) {
      fs.writeFileSync('./config/config.json', JSON.stringify(oldconfig, null, 2));
      log.info('imported old config from config.user.json');
    }
    fs.renameSync('./config.user.json', './config.user.bak');
  } catch (error1) {
    e = error1;
    if (e.code !== 'ENOENT') {
      log.info(e);
    }
  }

  setting_save = global.setting_save = function(settings) {
    fs.writeFileSync(settings.file, JSON.stringify(settings, null, 2));
  };

  setting_change = global.setting_change = function(settings, path, val) {
    var key, target;
    if (_.isString(val)) {
      log.info("setting changed", path, val);
    }
    path = path.split(':');
    if (path.length === 0) {
      settings[path[0]] = val;
    } else {
      target = settings;
      while (path.length > 1) {
        key = path.shift();
        target = target[key];
      }
      key = path.shift();
      target[key] = val;
    }
    setting_save(settings);
  };

  default_config = loadJSON('./data/default_config.json');

  try {
    config = loadJSON('./config/config.json');
  } catch (error1) {
    config = {};
  }

  settings = global.settings = merge(default_config, config, {
    arrayMerge: function(destination, source) {
      return source;
    }
  });

  auth = global.auth = require('./ygopro-auth.js');

  imported = false;

  if (settings.modules.http.quick_death_rule === true) {
    settings.modules.http.quick_death_rule = 1;
    imported = true;
  }

  if (settings.modules.cloud_replay.redis_port) {
    settings.modules.cloud_replay.redis.port = settings.modules.cloud_replay.redis_port;
    delete settings.modules.cloud_replay.redis_port;
    imported = true;
  }

  if (settings.modules.http.password) {
    auth.add_user("olduser", settings.modules.http.password, true, {
      "get_rooms": true,
      "shout": true,
      "stop": true,
      "change_settings": true,
      "ban_user": true,
      "kick_user": true,
      "start_death": true
    });
    delete settings.modules.http.password;
    imported = true;
  }

  if (settings.modules.tournament_mode.password) {
    auth.add_user("tournament", settings.modules.tournament_mode.password, true, {
      "duel_log": true,
      "download_replay": true,
      "clear_duel_log": true,
      "deck_dashboard_read": true,
      "deck_dashboard_write": true
    });
    delete settings.modules.tournament_mode.password;
    imported = true;
  }

  if (settings.modules.pre_util.password) {
    auth.add_user("pre", settings.modules.pre_util.password, true, {
      "pre_dashboard": true
    });
    delete settings.modules.pre_util.password;
    imported = true;
  }

  if (settings.modules.update_util.password) {
    auth.add_user("update", settings.modules.update_util.password, true, {
      "update_dashboard": true
    });
    delete settings.modules.update_util.password;
    imported = true;
  }

  if (settings.hostinfo.enable_priority || settings.hostinfo.enable_priority === false) {
    if (settings.hostinfo.enable_priority) {
      settings.hostinfo.duel_rule = 3;
    } else {
      settings.hostinfo.duel_rule = 4;
    }
    delete settings.hostinfo.enable_priority;
    imported = true;
  }

  if (settings.modules.challonge.api_key) {
    settings.modules.challonge.options.apiKey = settings.modules.challonge.api_key;
    delete settings.modules.challonge.api_key;
    imported = true;
  }

  if (imported) {
    setting_save(settings);
  }

  default_data = loadJSON('./data/default_data.json');

  try {
    tips = global.tips = loadJSON('./config/tips.json');
  } catch (error1) {
    tips = global.tips = default_data.tips;
    setting_save(tips);
  }

  try {
    dialogues = global.dialogues = loadJSON('./config/dialogues.json');
  } catch (error1) {
    dialogues = global.dialogues = default_data.dialogues;
    setting_save(dialogues);
  }

  try {
    badwords = global.badwords = loadJSON('./config/badwords.json');
  } catch (error1) {
    badwords = global.badwords = default_data.badwords;
    setting_save(badwords);
  }

  try {
    duel_log = global.duel_log = loadJSON('./config/duel_log.json');
  } catch (error1) {
    duel_log = global.duel_log = default_data.duel_log;
    setting_save(duel_log);
  }

  try {
    chat_color = global.chat_color = loadJSON('./config/chat_color.json');
  } catch (error1) {
    chat_color = global.chat_color = default_data.chat_color;
    setting_save(chat_color);
  }

  try {
    cppversion = parseInt(fs.readFileSync('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16');
    setting_change(settings, "version", cppversion);
    log.info("ygopro version 0x" + settings.version.toString(16), "(from source code)");
  } catch (error1) {
    log.info("ygopro version 0x" + settings.version.toString(16), "(from config)");
  }

  lflists = global.lflists = [];

  try {
    ref = fs.readFileSync('ygopro/expansions/lflist.conf', 'utf8').match(/!.*/g);
    for (j = 0, len = ref.length; j < len; j++) {
      list = ref[j];
      date = list.match(/!([\d\.]+)/);
      if (!date) {
        continue;
      }
      lflists.push({
        date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"),
        tcg: list.indexOf('TCG') !== -1
      });
    }
  } catch (error1) {

  }

  try {
    ref1 = fs.readFileSync('ygopro/lflist.conf', 'utf8').match(/!.*/g);
    for (l = 0, len1 = ref1.length; l < len1; l++) {
      list = ref1[l];
      date = list.match(/!([\d\.]+)/);
      if (!date) {
        continue;
      }
      lflists.push({
        date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"),
        tcg: list.indexOf('TCG') !== -1
      });
    }
  } catch (error1) {

  }

  if (settings.modules.cloud_replay.enabled) {
    redis = require('redis');
    zlib = require('zlib');
    redisdb = redis.createClient(settings.modules.cloud_replay.redis);
    redisdb.on('error', function(err) {
      log.warn(err);
    });
  }

  if (settings.modules.windbot.enabled) {
    windbots = loadJSON(settings.modules.windbot.botlist).windbots;
    real_windbot_server_ip = settings.modules.windbot.server_ip;
    if (!settings.modules.windbot.server_ip.includes("127.0.0.1")) {
      dns = require('dns');
      dns.lookup(settings.modules.windbot.server_ip, function(err, addr) {
        if (!err) {
          return real_windbot_server_ip = addr;
        }
      });
    }
  }

  if (settings.modules.heartbeat_detection.enabled) {
    long_resolve_cards = global.long_resolve_cards = loadJSON('./data/long_resolve_cards.json');
  }

  ygopro = global.ygopro = require('./ygopro.js');

  if (settings.modules.http.websocket_roomlist) {
    roomlist = global.roomlist = require('./roomlist.js');
  }

  if (settings.modules.i18n.auto_pick) {
    geoip = require('geoip-country-lite');
  }

  users_cache = {};

  if (settings.modules.mycard.enabled) {
    pgClient = require('pg').Client;
    pg_client = new pgClient(settings.modules.mycard.auth_database);
    pg_client.on('error', function(err) {
      log.warn("PostgreSQL ERROR: ", err);
    });
    pg_query = pg_client.query('SELECT username, id from users');
    pg_query.on('error', function(err) {
      log.warn("PostgreSQL Query ERROR: ", err);
    });
    pg_query.on('row', function(row) {
      users_cache[row.username] = row.id;
    });
    pg_query.on('end', function(result) {
      log.info("users loaded", result.rowCount);
    });
    pg_client.on('drain', pg_client.end.bind(pg_client));
    log.info("loading mycard user...");
    pg_client.connect();
    if (settings.modules.arena_mode.enabled && settings.modules.arena_mode.init_post.enabled) {
      request.post({
        url: settings.modules.arena_mode.init_post.url,
        qs: {
          ak: settings.modules.arena_mode.init_post.accesskey,
          arena: settings.modules.arena_mode.mode
        }
      }, (function(_this) {
        return function(error, response, body) {
          if (error) {
            log.warn('ARENA INIT POST ERROR', error);
          } else {
            if (response.statusCode >= 400) {
              log.warn('ARENA INIT POST FAIL', response.statusCode, response.statusMessage, body);
            }
          }
        };
      })(this));
    }
  }

  if (settings.modules.challonge.enabled) {
    challonge_module_name = 'challonge';
    if (settings.modules.challonge.use_custom_module) {
      challonge_module_name = settings.modules.challonge.use_custom_module;
    }
    challonge = require(challonge_module_name).createClient(settings.modules.challonge.options);
    if (settings.modules.challonge.cache_ttl) {
      challonge_cache = [];
    }
    challonge_queue_callbacks = [[], []];
    is_requesting = [null, null];
    get_callback = function(challonge_type, _callback) {
      return (function(err, data) {
        var cur_callback;
        if (settings.modules.challonge.cache_ttl && !err && data) {
          challonge_cache[challonge_type] = data;
        }
        is_requesting[challonge_type] = null;
        _callback(err, data);
        while (challonge_queue_callbacks[challonge_type].length) {
          cur_callback = challonge_queue_callbacks[challonge_type].splice(0, 1)[0];
          cur_callback(err, data);
        }
      });
    };
    challonge.participants._index = function(_data) {
      var err;
      if (settings.modules.challonge.cache_ttl && challonge_cache[0]) {
        _data.callback(null, challonge_cache[0]);
      } else if (is_requesting[0] && moment() - is_requesting[0] <= 5000) {
        challonge_queue_callbacks[0].push(_data.callback);
      } else {
        _data.callback = get_callback(0, _data.callback);
        is_requesting[0] = moment();
        try {
          challonge.participants.index(_data);
        } catch (error1) {
          err = error1;
          _data.callback(err, null);
        }
      }
    };
    challonge.matches._index = function(_data) {
      var err;
      if (settings.modules.challonge.cache_ttl && challonge_cache[1]) {
        _data.callback(null, challonge_cache[1]);
      } else if (is_requesting[1] && moment() - is_requesting[1] <= 5000) {
        challonge_queue_callbacks[1].push(_data.callback);
      } else {
        _data.callback = get_callback(1, _data.callback);
        is_requesting[1] = moment();
        try {
          challonge.matches.index(_data);
        } catch (error1) {
          err = error1;
          _data.callback(err, null);
        }
      }
    };
    challonge.matches._update = function(_data) {
      var err;
      try {
        challonge.matches.update(_data);
      } catch (error1) {
        err = error1;
        log.warn("Errored pushing scores to Challonge.", err);
      }
    };
    refresh_challonge_cache = function() {
      if (settings.modules.challonge.cache_ttl) {
        challonge_cache[0] = null;
        challonge_cache[1] = null;
      }
    };
    refresh_challonge_cache();
    if (settings.modules.challonge.cache_ttl) {
      setInterval(refresh_challonge_cache, settings.modules.challonge.cache_ttl);
    }
  }

  memory_usage = global.memory_usage = 0;

  get_memory_usage = get_memory_usage = function() {
    var prc_free;
    prc_free = exec("free");
    prc_free.stdout.on('data', function(data) {
      var actualFree, buffers, cached, free, line, lines, new_free, percentUsed, total;
      lines = data.toString().split(/\n/g);
      line = lines[0].split(/\s+/);
      new_free = line[6] === 'available' ? true : false;
      line = lines[1].split(/\s+/);
      total = parseInt(line[1], 10);
      free = parseInt(line[3], 10);
      buffers = parseInt(line[5], 10);
      if (new_free) {
        actualFree = parseInt(line[6], 10);
      } else {
        cached = parseInt(line[6], 10);
        actualFree = free + buffers + cached;
      }
      percentUsed = parseFloat(((1 - (actualFree / total)) * 100).toFixed(2));
      memory_usage = percentUsed;
    });
  };

  get_memory_usage();

  setInterval(get_memory_usage, 3000);

  Cloud_replay_ids = global.Cloud_replay_ids = [];

  ROOM_all = global.ROOM_all = [];

  ROOM_players_oppentlist = global.ROOM_players_oppentlist = {};

  ROOM_players_banned = global.ROOM_players_banned = [];

  ROOM_players_scores = global.ROOM_players_scores = {};

  ROOM_connected_ip = global.ROOM_connected_ip = {};

  ROOM_bad_ip = global.ROOM_bad_ip = {};

  ban_user = global.ban_user = function(name) {
    var bad_ip, len2, len3, m, n, player, ref2, room;
    settings.ban.banned_user.push(name);
    setting_save(settings);
    bad_ip = 0;
    for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
      room = ROOM_all[m];
      if (room && room.established) {
        ref2 = room.players;
        for (n = 0, len3 = ref2.length; n < len3; n++) {
          player = ref2[n];
          if (player && (player.name === name || player.ip === bad_ip)) {
            bad_ip = player.ip;
            ROOM_bad_ip[bad_ip] = 99;
            settings.ban.banned_ip.push(player.ip);
            ygopro.stoc_send_chat_to_room(room, player.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
            CLIENT_send_replays(player, room);
            CLIENT_kick(player);
            continue;
          }
        }
      }
    }
  };

  ROOM_ban_player = global.ROOM_ban_player = function(name, ip, reason, countadd) {
    var bannedplayer, bantime;
    if (countadd == null) {
      countadd = 1;
    }
    if (settings.modules.test_mode.no_ban_player) {
      return;
    }
    bannedplayer = _.find(ROOM_players_banned, function(bannedplayer) {
      return ip === bannedplayer.ip;
    });
    if (bannedplayer) {
      bannedplayer.count = bannedplayer.count + countadd;
      bantime = bannedplayer.count > 3 ? Math.pow(2, bannedplayer.count - 3) * 2 : 0;
      bannedplayer.time = moment() < bannedplayer.time ? moment(bannedplayer.time).add(bantime, 'm') : moment().add(bantime, 'm');
      if (!_.find(bannedplayer.reasons, function(bannedreason) {
        return bannedreason === reason;
      })) {
        bannedplayer.reasons.push(reason);
      }
      bannedplayer.need_tip = true;
    } else {
      bannedplayer = {
        "ip": ip,
        "time": moment(),
        "count": countadd,
        "reasons": [reason],
        "need_tip": true
      };
      ROOM_players_banned.push(bannedplayer);
    }
  };

  ROOM_player_win = global.ROOM_player_win = function(name) {
    if (!ROOM_players_scores[name]) {
      ROOM_players_scores[name] = {
        win: 0,
        lose: 0,
        flee: 0,
        combo: 0
      };
    }
    ROOM_players_scores[name].win = ROOM_players_scores[name].win + 1;
    ROOM_players_scores[name].combo = ROOM_players_scores[name].combo + 1;
  };

  ROOM_player_lose = global.ROOM_player_lose = function(name) {
    if (!ROOM_players_scores[name]) {
      ROOM_players_scores[name] = {
        win: 0,
        lose: 0,
        flee: 0,
        combo: 0
      };
    }
    ROOM_players_scores[name].lose = ROOM_players_scores[name].lose + 1;
    ROOM_players_scores[name].combo = 0;
  };

  ROOM_player_flee = global.ROOM_player_flee = function(name) {
    if (!ROOM_players_scores[name]) {
      ROOM_players_scores[name] = {
        win: 0,
        lose: 0,
        flee: 0,
        combo: 0
      };
    }
    ROOM_players_scores[name].flee = ROOM_players_scores[name].flee + 1;
    ROOM_players_scores[name].combo = 0;
  };

  ROOM_player_get_score = global.ROOM_player_get_score = function(player) {
    var name, score, total;
    name = player.name_vpass;
    score = ROOM_players_scores[name];
    if (!score) {
      return player.name + " ${random_score_blank}";
    }
    total = score.win + score.lose;
    if (score.win < 2 && total < 3) {
      return player.name + " ${random_score_not_enough}";
    }
    if (score.combo >= 2) {
      return "${random_score_part1}" + player.name + " ${random_score_part2} " + (Math.ceil(score.win / total * 100)) + "${random_score_part3} " + (Math.ceil(score.flee / total * 100)) + "${random_score_part4_combo}" + score.combo + "${random_score_part5_combo}";
    } else {
      return "${random_score_part1}" + player.name + " ${random_score_part2} " + (Math.ceil(score.win / total * 100)) + "${random_score_part3} " + (Math.ceil(score.flee / total * 100)) + "${random_score_part4}";
    }
  };

  if (settings.modules.random_duel.post_match_scores) {
    setInterval(function() {
      var scores, scores_by_lose, scores_by_win, scores_pair;
      scores_pair = _.pairs(ROOM_players_scores);
      scores_by_lose = _.sortBy(scores_pair, function(score) {
        return score[1].lose;
      }).reverse();
      scores_by_win = _.sortBy(scores_by_lose, function(score) {
        return score[1].win;
      }).reverse();
      scores = _.first(scores_by_win, 10);
      request.post({
        url: settings.modules.random_duel.post_match_scores,
        form: {
          accesskey: settings.modules.random_duel.post_match_accesskey,
          rank: JSON.stringify(scores)
        }
      }, (function(_this) {
        return function(error, response, body) {
          if (error) {
            log.warn('RANDOM SCORE POST ERROR', error);
          } else {
            if (response.statusCode !== 204 && response.statusCode !== 200) {
              log.warn('RANDOM SCORE POST FAIL', response.statusCode, response.statusMessage, body);
            }
          }
        };
      })(this));
    }, 60000);
  }

  ROOM_find_or_create_by_name = global.ROOM_find_or_create_by_name = function(name, player_ip) {
    var room, uname;
    uname = name.toUpperCase();
    if (settings.modules.windbot.enabled && (uname.slice(0, 2) === 'AI' || (!settings.modules.random_duel.enabled && uname === ''))) {
      return ROOM_find_or_create_ai(name);
    }
    if (settings.modules.random_duel.enabled && (uname === '' || uname === 'S' || uname === 'M' || uname === 'T')) {
      return ROOM_find_or_create_random(uname, player_ip);
    }
    if (room = ROOM_find_by_name(name)) {
      return room;
    } else if (memory_usage >= 90) {
      return null;
    } else {
      return new Room(name);
    }
  };

  ROOM_find_or_create_random = global.ROOM_find_or_create_random = function(type, player_ip) {
    var bannedplayer, max_player, name, playerbanned, result;
    bannedplayer = _.find(ROOM_players_banned, function(bannedplayer) {
      return player_ip === bannedplayer.ip;
    });
    if (bannedplayer) {
      if (bannedplayer.count > 6 && moment() < bannedplayer.time) {
        return {
          "error": "${random_banned_part1}" + (bannedplayer.reasons.join('${random_ban_reason_separator}')) + "${random_banned_part2}" + (moment(bannedplayer.time).fromNow(true)) + "${random_banned_part3}"
        };
      }
      if (bannedplayer.count > 3 && moment() < bannedplayer.time && bannedplayer.need_tip && type !== 'T') {
        bannedplayer.need_tip = false;
        return {
          "error": "${random_deprecated_part1}" + (bannedplayer.reasons.join('${random_ban_reason_separator}')) + "${random_deprecated_part2}" + (moment(bannedplayer.time).fromNow(true)) + "${random_deprecated_part3}"
        };
      } else if (bannedplayer.need_tip) {
        bannedplayer.need_tip = false;
        return {
          "error": "${random_warn_part1}" + (bannedplayer.reasons.join('${random_ban_reason_separator}')) + "${random_warn_part2}"
        };
      } else if (bannedplayer.count > 2) {
        bannedplayer.need_tip = true;
      }
    }
    max_player = type === 'T' ? 4 : 2;
    playerbanned = bannedplayer && bannedplayer.count > 3 && moment() < bannedplayer.time;
    result = _.find(ROOM_all, function(room) {
      return room && room.random_type !== '' && room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && !room.windbot && ((type === '' && (room.random_type === 'S' || (settings.modules.random_duel.blank_pass_match && room.random_type !== 'T'))) || room.random_type === type) && room.get_playing_player().length < max_player && (settings.modules.random_duel.no_rematch_check || room.get_host() === null || room.get_host().ip !== ROOM_players_oppentlist[player_ip]) && (playerbanned === room.deprecated || type === 'T');
    });
    if (result) {
      result.welcome = '${random_duel_enter_room_waiting}';
    } else if (memory_usage < 90) {
      type = type ? type : 'S';
      name = type + ',RANDOM#' + Math.floor(Math.random() * 100000);
      result = new Room(name);
      result.random_type = type;
      result.max_player = max_player;
      result.welcome = '${random_duel_enter_room_new}';
      result.deprecated = playerbanned;
    } else {
      return null;
    }
    if (result.random_type === 'M') {
      result.welcome = result.welcome + '\n${random_duel_enter_room_match}';
    }
    return result;
  };

  ROOM_find_or_create_ai = global.ROOM_find_or_create_ai = function(name) {
    var ainame, namea, result, room, uname, windbot;
    if (name === '') {
      name = 'AI';
    }
    namea = name.split('#');
    uname = name.toUpperCase();
    if (room = ROOM_find_by_name(name)) {
      return room;
    } else if (uname === 'AI') {
      windbot = _.sample(windbots);
      name = 'AI#' + Math.floor(Math.random() * 100000);
    } else if (namea.length > 1) {
      ainame = namea[namea.length - 1];
      windbot = _.sample(_.filter(windbots, function(w) {
        return w.name === ainame || w.deck === ainame;
      }));
      if (!windbot) {
        return {
          "error": "${windbot_deck_not_found}"
        };
      }
      name = name + ',' + Math.floor(Math.random() * 100000);
    } else {
      windbot = _.sample(windbots);
      name = name + '#' + Math.floor(Math.random() * 100000);
    }
    if (name.replace(/[^\x00-\xff]/g, "00").length > 20) {
      log.info("long ai name", name);
      return {
        "error": "${windbot_name_too_long}"
      };
    }
    result = new Room(name);
    result.windbot = windbot;
    result["private"] = true;
    return result;
  };

  ROOM_find_by_name = global.ROOM_find_by_name = function(name) {
    var result;
    result = _.find(ROOM_all, function(room) {
      return room && room.name === name;
    });
    return result;
  };

  ROOM_find_by_title = global.ROOM_find_by_title = function(title) {
    var result;
    result = _.find(ROOM_all, function(room) {
      return room && room.title === title;
    });
    return result;
  };

  ROOM_find_by_port = global.ROOM_find_by_port = function(port) {
    return _.find(ROOM_all, function(room) {
      return room && room.port === port;
    });
  };

  ROOM_find_by_pid = global.ROOM_find_by_pid = function(pid) {
    return _.find(ROOM_all, function(room) {
      return room && room.process_pid === pid;
    });
  };

  ROOM_validate = global.ROOM_validate = function(name) {
    var client_name, client_name_and_pass, client_pass;
    client_name_and_pass = name.split('$', 2);
    client_name = client_name_and_pass[0];
    client_pass = client_name_and_pass[1];
    if (!client_pass) {
      return true;
    }
    return !_.find(ROOM_all, function(room) {
      var room_name, room_name_and_pass, room_pass;
      if (!room) {
        return false;
      }
      room_name_and_pass = room.name.split('$', 2);
      room_name = room_name_and_pass[0];
      room_pass = room_name_and_pass[1];
      return client_name === room_name && client_pass !== room_pass;
    });
  };

  ROOM_unwelcome = global.ROOM_unwelcome = function(room, bad_player, reason) {
    var len2, m, player, ref2;
    if (!room) {
      return;
    }
    ref2 = room.players;
    for (m = 0, len2 = ref2.length; m < len2; m++) {
      player = ref2[m];
      if (player && player === bad_player) {
        ygopro.stoc_send_chat(player, "${unwelcome_warn_part1}" + reason + "${unwelcome_warn_part2}", ygopro.constants.COLORS.RED);
      } else if (player && player.pos !== 7 && player !== bad_player) {
        player.flee_free = true;
        ygopro.stoc_send_chat(player, "${unwelcome_tip_part1}" + reason + "${unwelcome_tip_part2}", ygopro.constants.COLORS.BABYBLUE);
      }
    }
  };

  CLIENT_kick = global.CLIENT_kick = function(client) {
    if (!client) {
      return false;
    }
    client.system_kicked = true;
    if (settings.modules.reconnect.enabled && client.closed) {
      if (client.server && !client.had_new_reconnection) {
        client.server.destroy();
      }
    } else {
      client.destroy();
    }
    return true;
  };

  release_disconnect = global.release_disconnect = function(dinfo, reconnected) {
    if (dinfo.old_client && !reconnected) {
      dinfo.old_client.destroy();
    }
    if (dinfo.old_server && !reconnected) {
      dinfo.old_server.destroy();
    }
    clearTimeout(dinfo.timeout);
  };

  CLIENT_get_authorize_key = global.CLIENT_get_authorize_key = function(client) {
    if (!settings.modules.mycard.enabled && client.vpass) {
      return client.name_vpass;
    } else if (settings.modules.mycard.enabled || settings.modules.tournament_mode.enabled || settings.modules.challonge.enabled || client.is_local) {
      return client.name;
    } else {
      return client.ip + ":" + client.name;
    }
  };

  CLIENT_reconnect_unregister = global.CLIENT_reconnect_unregister = function(client, reconnected, exact) {
    if (!settings.modules.reconnect.enabled) {
      return false;
    }
    if (disconnect_list[CLIENT_get_authorize_key(client)]) {
      if (exact && disconnect_list[CLIENT_get_authorize_key(client)].old_client !== client) {
        return false;
      }
      release_disconnect(disconnect_list[CLIENT_get_authorize_key(client)], reconnected);
      delete disconnect_list[CLIENT_get_authorize_key(client)];
      return true;
    }
    return false;
  };

  CLIENT_reconnect_register = global.CLIENT_reconnect_register = function(client, room_id, error) {
    var dinfo, room, tmot;
    room = ROOM_all[room_id];
    if (client.had_new_reconnection) {
      return false;
    }
    if (!settings.modules.reconnect.enabled || !room || client.system_kicked || client.flee_free || disconnect_list[CLIENT_get_authorize_key(client)] || client.is_post_watcher || !CLIENT_is_player(client, room) || room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN || room.windbot || (settings.modules.reconnect.auto_surrender_after_disconnect && room.hostinfo.mode !== 1) || (room.random_type && room.get_disconnected_count() > 1)) {
      return false;
    }
    dinfo = {
      room_id: room_id,
      old_client: client,
      old_server: client.server,
      deckbuf: client.start_deckbuf
    };
    tmot = setTimeout(function() {
      room.disconnect(client, error);
      dinfo.old_server.destroy();
    }, settings.modules.reconnect.wait_time);
    dinfo.timeout = tmot;
    disconnect_list[CLIENT_get_authorize_key(client)] = dinfo;
    ygopro.stoc_send_chat_to_room(room, (client.name + " ${disconnect_from_game}") + (error ? ": " + error : ''));
    if (client.time_confirm_required) {
      client.time_confirm_required = false;
      ygopro.ctos_send(client.server, 'TIME_CONFIRM');
    }
    if (settings.modules.reconnect.auto_surrender_after_disconnect && room.duel_stage === ygopro.constants.DUEL_STAGE.DUELING) {
      ygopro.ctos_send(client.server, 'SURRENDER');
    }
    return true;
  };

  CLIENT_import_data = global.CLIENT_import_data = function(client, old_client, room) {
    var index, key, len2, len3, m, n, player, ref2;
    ref2 = room.players;
    for (index = m = 0, len2 = ref2.length; m < len2; index = ++m) {
      player = ref2[index];
      if (player === old_client) {
        room.players[index] = client;
        break;
      }
    }
    room.dueling_players[old_client.pos] = client;
    if (room.waiting_for_player === old_client) {
      room.waiting_for_player = client;
    }
    if (room.waiting_for_player2 === old_client) {
      room.waiting_for_player2 = client;
    }
    if (room.selecting_tp === old_client) {
      room.selecting_tp = client;
    }
    for (n = 0, len3 = import_datas.length; n < len3; n++) {
      key = import_datas[n];
      client[key] = old_client[key];
    }
    old_client.had_new_reconnection = true;
  };

  SERVER_clear_disconnect = global.SERVER_clear_disconnect = function(server) {
    var k, v;
    if (!settings.modules.reconnect.enabled) {
      return false;
    }
    for (k in disconnect_list) {
      v = disconnect_list[k];
      if (v && server === v.old_server) {
        release_disconnect(v);
        delete disconnect_list[k];
        return true;
      }
    }
    return false;
  };

  ROOM_clear_disconnect = global.ROOM_clear_disconnect = function(room_id) {
    var k, v;
    if (!settings.modules.reconnect.enabled) {
      return false;
    }
    for (k in disconnect_list) {
      v = disconnect_list[k];
      if (v && room_id === v.room_id) {
        release_disconnect(v);
        delete disconnect_list[k];
        return true;
      }
    }
    return false;
  };

  CLIENT_is_player = global.CLIENT_is_player = function(client, room) {
    var is_player, len2, m, player, ref2;
    is_player = false;
    ref2 = room.players;
    for (m = 0, len2 = ref2.length; m < len2; m++) {
      player = ref2[m];
      if (client === player) {
        is_player = true;
        break;
      }
    }
    return is_player && client.pos <= 3;
  };

  CLIENT_is_able_to_reconnect = global.CLIENT_is_able_to_reconnect = function(client, deckbuf) {
    var disconnect_info, room;
    if (!settings.modules.reconnect.enabled) {
      return false;
    }
    if (client.system_kicked) {
      return false;
    }
    disconnect_info = disconnect_list[CLIENT_get_authorize_key(client)];
    if (!disconnect_info) {
      return false;
    }
    room = ROOM_all[disconnect_info.room_id];
    if (!room) {
      CLIENT_reconnect_unregister(client);
      return false;
    }
    if (deckbuf && !_.isEqual(deckbuf, disconnect_info.deckbuf)) {
      return false;
    }
    return true;
  };

  CLIENT_get_kick_reconnect_target = global.CLIENT_get_kick_reconnect_target = function(client, deckbuf) {
    var len2, len3, m, n, player, ref2, room;
    for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
      room = ROOM_all[m];
      if (room && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && !room.windbot) {
        ref2 = room.get_playing_player();
        for (n = 0, len3 = ref2.length; n < len3; n++) {
          player = ref2[n];
          if (!player.closed && player.name === client.name && (settings.modules.challonge.enabled || player.pass === client.pass) && (settings.modules.mycard.enabled || settings.modules.tournament_mode.enabled || player.ip === client.ip || (client.vpass && client.vpass === player.vpass)) && (!deckbuf || _.isEqual(player.start_deckbuf, deckbuf))) {
            return player;
          }
        }
      }
    }
    return null;
  };

  CLIENT_is_able_to_kick_reconnect = global.CLIENT_is_able_to_kick_reconnect = function(client, deckbuf) {
    if (!(settings.modules.reconnect.enabled && settings.modules.reconnect.allow_kick_reconnect)) {
      return false;
    }
    if (!CLIENT_get_kick_reconnect_target(client, deckbuf)) {
      return false;
    }
    return true;
  };

  CLIENT_send_pre_reconnect_info = global.CLIENT_send_pre_reconnect_info = function(client, room, old_client) {
    var len2, m, player, ref2, req_pos;
    ygopro.stoc_send_chat(client, "${pre_reconnecting_to_room}", ygopro.constants.COLORS.BABYBLUE);
    ygopro.stoc_send(client, 'JOIN_GAME', room.join_game_buffer);
    req_pos = old_client.pos;
    if (old_client.is_host) {
      req_pos += 0x10;
    }
    ygopro.stoc_send(client, 'TYPE_CHANGE', {
      type: req_pos
    });
    ref2 = room.players;
    for (m = 0, len2 = ref2.length; m < len2; m++) {
      player = ref2[m];
      ygopro.stoc_send(client, 'HS_PLAYER_ENTER', {
        name: player.name,
        pos: player.pos
      });
    }
  };

  CLIENT_send_reconnect_info = global.CLIENT_send_reconnect_info = function(client, server, room) {
    client.reconnecting = true;
    ygopro.stoc_send_chat(client, "${reconnecting_to_room}", ygopro.constants.COLORS.BABYBLUE);
    switch (room.duel_stage) {
      case ygopro.constants.DUEL_STAGE.FINGER:
        ygopro.stoc_send(client, 'DUEL_START');
        if ((room.hostinfo.mode !== 2 || client.pos === 0 || client.pos === 2) && !client.selected_preduel) {
          ygopro.stoc_send(client, 'SELECT_HAND');
        }
        client.reconnecting = false;
        break;
      case ygopro.constants.DUEL_STAGE.FIRSTGO:
        ygopro.stoc_send(client, 'DUEL_START');
        if (client === room.selecting_tp) {
          ygopro.stoc_send(client, 'SELECT_TP');
        }
        client.reconnecting = false;
        break;
      case ygopro.constants.DUEL_STAGE.SIDING:
        ygopro.stoc_send(client, 'DUEL_START');
        if (!client.selected_preduel) {
          ygopro.stoc_send(client, 'CHANGE_SIDE');
        }
        client.reconnecting = false;
        break;
      default:
        ygopro.ctos_send(server, 'REQUEST_FIELD');
        break;
    }
  };

  CLIENT_pre_reconnect = global.CLIENT_pre_reconnect = function(client) {
    var dinfo, player;
    if (CLIENT_is_able_to_reconnect(client)) {
      dinfo = disconnect_list[CLIENT_get_authorize_key(client)];
      client.pre_reconnecting = true;
      client.pos = dinfo.old_client.pos;
      client.setTimeout(300000);
      CLIENT_send_pre_reconnect_info(client, ROOM_all[dinfo.room_id], dinfo.old_client);
    } else if (CLIENT_is_able_to_kick_reconnect(client)) {
      player = CLIENT_get_kick_reconnect_target(client);
      client.pre_reconnecting = true;
      client.pos = player.pos;
      client.setTimeout(300000);
      CLIENT_send_pre_reconnect_info(client, ROOM_all[player.rid], player);
    }
  };

  CLIENT_reconnect = global.CLIENT_reconnect = function(client) {
    var current_old_server, dinfo, room;
    if (!CLIENT_is_able_to_reconnect(client)) {
      ygopro.stoc_send_chat(client, "${reconnect_failed}", ygopro.constants.COLORS.RED);
      CLIENT_kick(client);
      return;
    }
    client.pre_reconnecting = false;
    dinfo = disconnect_list[CLIENT_get_authorize_key(client)];
    room = ROOM_all[dinfo.room_id];
    current_old_server = client.server;
    client.server = dinfo.old_server;
    client.server.client = client;
    dinfo.old_client.server = null;
    current_old_server.client = null;
    current_old_server.had_new_reconnection = true;
    current_old_server.destroy();
    client.established = true;
    client.pre_establish_buffers = [];
    if (room.random_type || room.arena) {
      room.last_active_time = moment();
    }
    CLIENT_import_data(client, dinfo.old_client, room);
    CLIENT_send_reconnect_info(client, client.server, room);
    ygopro.stoc_send_chat_to_room(room, client.name + " ${reconnect_to_game}");
    CLIENT_reconnect_unregister(client, true);
  };

  CLIENT_kick_reconnect = global.CLIENT_kick_reconnect = function(client, deckbuf) {
    var current_old_server, player, room;
    if (!CLIENT_is_able_to_kick_reconnect(client)) {
      ygopro.stoc_send_chat(client, "${reconnect_failed}", ygopro.constants.COLORS.RED);
      CLIENT_kick(client);
      return;
    }
    client.pre_reconnecting = false;
    player = CLIENT_get_kick_reconnect_target(client, deckbuf);
    room = ROOM_all[player.rid];
    current_old_server = client.server;
    client.server = player.server;
    client.server.client = client;
    ygopro.stoc_send_chat(player, "${reconnect_kicked}", ygopro.constants.COLORS.RED);
    player.server = null;
    player.had_new_reconnection = true;
    CLIENT_kick(player);
    current_old_server.client = null;
    current_old_server.had_new_reconnection = true;
    current_old_server.destroy();
    client.established = true;
    client.pre_establish_buffers = [];
    if (room.random_type || room.arena) {
      room.last_active_time = moment();
    }
    CLIENT_import_data(client, player, room);
    CLIENT_send_reconnect_info(client, client.server, room);
    ygopro.stoc_send_chat_to_room(room, client.name + " ${reconnect_to_game}");
    CLIENT_reconnect_unregister(client, true);
  };

  if (settings.modules.reconnect.enabled) {
    disconnect_list = {};
  }

  CLIENT_heartbeat_unregister = global.CLIENT_heartbeat_unregister = function(client) {
    if (!settings.modules.heartbeat_detection.enabled || !client.heartbeat_timeout) {
      return false;
    }
    clearTimeout(client.heartbeat_timeout);
    delete client.heartbeat_timeout;
    return true;
  };

  CLIENT_heartbeat_register = global.CLIENT_heartbeat_register = function(client, send) {
    if (!settings.modules.heartbeat_detection.enabled || client.closed || client.is_post_watcher || client.pre_reconnecting || client.reconnecting || client.waiting_for_last || client.pos > 3 || client.heartbeat_protected) {
      return false;
    }
    if (client.heartbeat_timeout) {
      CLIENT_heartbeat_unregister(client);
    }
    client.heartbeat_responsed = false;
    if (send) {
      ygopro.stoc_send(client, "TIME_LIMIT", {
        player: 0,
        left_time: 0
      });
      ygopro.stoc_send(client, "TIME_LIMIT", {
        player: 1,
        left_time: 0
      });
    }
    client.heartbeat_timeout = setTimeout(function() {
      CLIENT_heartbeat_unregister(client);
      if (!(client.closed || client.heartbeat_responsed)) {
        client.destroy();
      }
    }, settings.modules.heartbeat_detection.wait_time);
    return true;
  };

  CLIENT_is_banned_by_mc = global.CLIENT_is_banned_by_mc = function(client) {
    return client.ban_mc && client.ban_mc.banned && moment().isBefore(client.ban_mc.until);
  };

  CLIENT_send_replays = global.CLIENT_send_replays = function(client, room) {
    var buffer, i, len2, m, ref2;
    if (!(settings.modules.replay_delay && !(settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.replay_safe && settings.modules.tournament_mode.block_replay_to_player) && room.replays.length && room.hostinfo.mode === 1 && !client.replays_sent && !client.closed)) {
      return false;
    }
    client.replays_sent = true;
    i = 0;
    ref2 = room.replays;
    for (m = 0, len2 = ref2.length; m < len2; m++) {
      buffer = ref2[m];
      ++i;
      if (buffer) {
        ygopro.stoc_send_chat(client, "${replay_hint_part1}" + i + "${replay_hint_part2}", ygopro.constants.COLORS.BABYBLUE);
        ygopro.stoc_send(client, "REPLAY", buffer);
      }
    }
    return true;
  };

  SOCKET_flush_data = global.SOCKET_flush_data = function(sk, datas) {
    var buffer, len2, m;
    if (!sk || sk.closed) {
      return false;
    }
    for (m = 0, len2 = datas.length; m < len2; m++) {
      buffer = datas[m];
      sk.write(buffer);
    }
    datas.splice(0, datas.length);
    return true;
  };

  Room = (function() {
    function Room(name, hostinfo) {
      var death_time, draw_count, duel_rule, lflist, param, rule, start_hand, start_lp, time_limit;
      this.hostinfo = hostinfo;
      this.name = name;
      this.players = [];
      this.player_datas = [];
      this.status = 'starting';
      this.established = false;
      this.watcher_buffers = [];
      this.recorder_buffers = [];
      this.cloud_replay_id = Math.floor(Math.random() * 100000000);
      this.watchers = [];
      this.random_type = '';
      this.welcome = '';
      this.scores = {};
      this.decks = {};
      this.duel_count = 0;
      this.death = 0;
      this.turn = 0;
      this.duel_stage = ygopro.constants.DUEL_STAGE.BEGIN;
      this.replays = [];
      ROOM_all.push(this);
      this.hostinfo || (this.hostinfo = JSON.parse(JSON.stringify(settings.hostinfo)));
      delete this.hostinfo.comment;
      if (lflists.length) {
        if (this.hostinfo.rule === 1 && this.hostinfo.lflist === 0) {
          this.hostinfo.lflist = _.findIndex(lflists, function(list) {
            return list.tcg;
          });
        }
      } else {
        this.hostinfo.lflist = -1;
      }
      if (name.slice(0, 2) === 'M#') {
        this.hostinfo.mode = 1;
      } else if (name.slice(0, 2) === 'T#') {
        this.hostinfo.mode = 2;
        this.hostinfo.start_lp = 16000;
      } else if (name.slice(0, 3) === 'AI#') {
        this.hostinfo.rule = 2;
        this.hostinfo.lflist = -1;
        this.hostinfo.time_limit = 999;
      } else if ((param = name.match(/^(\d)(\d)(T|F)(T|F)(T|F)(\d+),(\d+),(\d+)/i))) {
        this.hostinfo.rule = parseInt(param[1]);
        this.hostinfo.mode = parseInt(param[2]);
        this.hostinfo.duel_rule = (param[3] === 'T' ? 3 : 4);
        this.hostinfo.no_check_deck = param[4] === 'T';
        this.hostinfo.no_shuffle_deck = param[5] === 'T';
        this.hostinfo.start_lp = parseInt(param[6]);
        this.hostinfo.start_hand = parseInt(param[7]);
        this.hostinfo.draw_count = parseInt(param[8]);
      } else if ((param = name.match(/(.+)#/)) !== null) {
        rule = param[1].toUpperCase();
        if (rule.match(/(^|,|,)(M|MATCH)(,|,|$)/)) {
          this.hostinfo.mode = 1;
        }
        if (rule.match(/(^|,|,)(T|TAG)(,|,|$)/)) {
          this.hostinfo.mode = 2;
          this.hostinfo.start_lp = 16000;
        }
        if (rule.match(/(^|,|,)(TCGONLY|TO)(,|,|$)/)) {
          this.hostinfo.rule = 1;
          this.hostinfo.lflist = _.findIndex(lflists, function(list) {
            return list.tcg;
          });
        }
        if (rule.match(/(^|,|,)(OCGONLY|OO)(,|,|$)/)) {
          this.hostinfo.rule = 0;
          this.hostinfo.lflist = 0;
        }
        if (rule.match(/(^|,|,)(OT|TCG)(,|,|$)/)) {
          this.hostinfo.rule = 2;
        }
        if ((param = rule.match(/(^|,|,)LP(\d+)(,|,|$)/))) {
          start_lp = parseInt(param[2]);
          if (start_lp <= 0) {
            start_lp = 1;
          }
          if (start_lp >= 99999) {
            start_lp = 99999;
          }
          this.hostinfo.start_lp = start_lp;
        }
        if ((param = rule.match(/(^|,|,)(TIME|TM|TI)(\d+)(,|,|$)/))) {
          time_limit = parseInt(param[3]);
          if (time_limit < 0) {
            time_limit = 180;
          }
          if (time_limit >= 1 && time_limit <= 60) {
            time_limit = time_limit * 60;
          }
          if (time_limit >= 999) {
            time_limit = 999;
          }
          this.hostinfo.time_limit = time_limit;
        }
        if ((param = rule.match(/(^|,|,)(START|ST)(\d+)(,|,|$)/))) {
          start_hand = parseInt(param[3]);
          if (start_hand <= 0) {
            start_hand = 1;
          }
          if (start_hand >= 40) {
            start_hand = 40;
          }
          this.hostinfo.start_hand = start_hand;
        }
        if ((param = rule.match(/(^|,|,)(DRAW|DR)(\d+)(,|,|$)/))) {
          draw_count = parseInt(param[3]);
          if (draw_count >= 35) {
            draw_count = 35;
          }
          this.hostinfo.draw_count = draw_count;
        }
        if ((param = rule.match(/(^|,|,)(LFLIST|LF)(\d+)(,|,|$)/))) {
          lflist = parseInt(param[3]) - 1;
          this.hostinfo.lflist = lflist;
        }
        if (rule.match(/(^|,|,)(NOLFLIST|NF)(,|,|$)/)) {
          this.hostinfo.lflist = -1;
        }
        if (rule.match(/(^|,|,)(NOUNIQUE|NU)(,|,|$)/)) {
          this.hostinfo.rule = 3;
        }
        if (rule.match(/(^|,|,)(NOCHECK|NC)(,|,|$)/)) {
          this.hostinfo.no_check_deck = true;
        }
        if (rule.match(/(^|,|,)(NOSHUFFLE|NS)(,|,|$)/)) {
          this.hostinfo.no_shuffle_deck = true;
        }
        if (rule.match(/(^|,|,)(IGPRIORITY|PR)(,|,|$)/)) {
          this.hostinfo.duel_rule = 3;
        }
        if ((param = rule.match(/(^|,|,)(DUELRULE|MR)(\d+)(,|,|$)/))) {
          duel_rule = parseInt(param[3]);
          if (duel_rule && duel_rule > 0 && duel_rule <= 4) {
            this.hostinfo.duel_rule = duel_rule;
          }
        }
        if (rule.match(/(^|,|,)(NOWATCH|NW)(,|,|$)/)) {
          this.hostinfo.no_watch = true;
        }
        if ((param = rule.match(/(^|,|,)(DEATH|DH)(\d*)(,|,|$)/))) {
          death_time = parseInt(param[3]);
          if (death_time && death_time > 0) {
            this.hostinfo.auto_death = death_time;
          } else {
            this.hostinfo.auto_death = 40;
          }
        }
      }
      this.hostinfo.replay_mode = 0;
      if (settings.modules.tournament_mode.enabled) {
        this.hostinfo.replay_mode |= 0x1;
      }
      if ((settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.replay_safe) || (this.hostinfo.mode === 1 && settings.modules.replay_delay)) {
        this.hostinfo.replay_mode |= 0x2;
      }
      param = [0, this.hostinfo.lflist, this.hostinfo.rule, this.hostinfo.mode, this.hostinfo.duel_rule, (this.hostinfo.no_check_deck ? 'T' : 'F'), (this.hostinfo.no_shuffle_deck ? 'T' : 'F'), this.hostinfo.start_lp, this.hostinfo.start_hand, this.hostinfo.draw_count, this.hostinfo.time_limit, this.hostinfo.replay_mode];
      try {
        this.process = spawn('./ygopro', param, {
          cwd: 'ygopro'
        });
        this.process_pid = this.process.pid;
        this.process.on('error', (function(_this) {
          return function(err) {
            _.each(_this.players, function(player) {
              return ygopro.stoc_die(player, "${create_room_failed}");
            });
            _this["delete"]();
          };
        })(this));
        this.process.on('exit', (function(_this) {
          return function(code) {
            if (!_this.disconnector) {
              _this.disconnector = 'server';
            }
            _this["delete"]();
          };
        })(this));
        this.process.stdout.setEncoding('utf8');
        this.process.stdout.once('data', (function(_this) {
          return function(data) {
            _this.established = true;
            if (!_this.windbot && settings.modules.http.websocket_roomlist) {
              roomlist.create(_this);
            }
            _this.port = parseInt(data);
            _.each(_this.players, function(player) {
              player.server.connect(_this.port, '127.0.0.1', function() {
                var buffer, len2, m, ref2;
                ref2 = player.pre_establish_buffers;
                for (m = 0, len2 = ref2.length; m < len2; m++) {
                  buffer = ref2[m];
                  player.server.write(buffer);
                }
                player.established = true;
                player.pre_establish_buffers = [];
              });
            });
            if (_this.windbot) {
              setTimeout(function() {
                return _this.add_windbot(_this.windbot);
              }, 200);
            }
          };
        })(this));
        this.process.stderr.on('data', (function(_this) {
          return function(data) {
            data = "Debug: " + data;
            data = data.replace(/\n$/, "");
            log.info("YGOPRO " + data);
            ygopro.stoc_send_chat_to_room(_this, data, ygopro.constants.COLORS.RED);
            _this.has_ygopro_error = true;
            _this.ygopro_error_length = _this.ygopro_error_length ? _this.ygopro_error_length + data.length : data.length;
            if (_this.ygopro_error_length > 10000) {
              _this.send_replays();
              _this.process.kill();
            }
          };
        })(this));
      } catch (error1) {
        this.error = "${create_room_failed}";
      }
    }

    Room.prototype["delete"] = function() {
      var end_time, formatted_replays, index, len2, log_rep_id, m, name, player_ips, player_names, recorder_buffer, ref2, ref3, repbuf, replay_id, room_name, score, score_array, score_form;
      if (this.deleted) {
        return;
      }
      score_array = [];
      ref2 = this.scores;
      for (name in ref2) {
        score = ref2[name];
        score_form = {
          name: name.split('$')[0],
          score: score,
          deck: null,
          name_vpass: name
        };
        if (this.decks[name]) {
          score_form.deck = this.decks[name];
        }
        score_array.push(score_form);
      }
      if (settings.modules.random_duel.record_match_scores && this.random_type === 'M') {
        if (score_array.length === 2) {
          if (score_array[0].score !== score_array[1].score) {
            if (score_array[0].score > score_array[1].score) {
              ROOM_player_win(score_array[0].name_vpass);
              ROOM_player_lose(score_array[1].name_vpass);
            } else {
              ROOM_player_win(score_array[1].name_vpass);
              ROOM_player_lose(score_array[0].name_vpass);
            }
          }
        }
        if (score_array.length === 1) {
          ROOM_player_win(score_array[0].name_vpass);
          ROOM_player_lose(score_array[0].name_vpass);
        }
      }
      if (settings.modules.arena_mode.enabled && this.arena) {
        end_time = moment().format();
        if (!this.start_time) {
          this.start_time = end_time;
        }
        if (score_array.length !== 2) {
          if (!score_array[0]) {
            score_array[0] = {
              name: null,
              score: -5,
              deck: null
            };
          }
          if (!score_array[1]) {
            score_array[1] = {
              name: null,
              score: -5,
              deck: null
            };
          }
          score_array[0].score = -5;
          score_array[1].score = -5;
        }
        formatted_replays = [];
        ref3 = this.replays;
        for (m = 0, len2 = ref3.length; m < len2; m++) {
          repbuf = ref3[m];
          if (repbuf) {
            formatted_replays.push(repbuf.toString("base64"));
          }
        }
        request.post({
          url: settings.modules.arena_mode.post_score,
          form: {
            accesskey: settings.modules.arena_mode.accesskey,
            usernameA: score_array[0].name,
            usernameB: score_array[1].name,
            userscoreA: score_array[0].score,
            userscoreB: score_array[1].score,
            userdeckA: score_array[0].deck,
            userdeckB: score_array[1].deck,
            replays: formatted_replays,
            start: this.start_time,
            end: end_time,
            arena: this.arena
          }
        }, (function(_this) {
          return function(error, response, body) {
            if (error) {
              log.warn('SCORE POST ERROR', error);
            } else {
              if (response.statusCode !== 204 && response.statusCode !== 200) {
                log.warn('SCORE POST FAIL', response.statusCode, response.statusMessage, _this.name, body);
              }
            }
          };
        })(this));
      }
      if (settings.modules.challonge.enabled && this.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && this.hostinfo.mode !== 2 && !this.kicked) {
        room_name = this.name;
        challonge.matches._update({
          id: settings.modules.challonge.tournament_id,
          matchId: this.challonge_info.id,
          match: this.get_challonge_score(),
          callback: function(err, data) {
            if (err) {
              log.warn("Errored pushing scores to Challonge.", room_name, err);
            } else {
              refresh_challonge_cache();
            }
          }
        });
      }
      if (this.player_datas.length && settings.modules.cloud_replay.enabled) {
        replay_id = this.cloud_replay_id;
        if (this.has_ygopro_error) {
          log_rep_id = true;
        }
        player_names = this.player_datas[0].name + (this.player_datas[2] ? "+" + this.player_datas[2].name : "") + " VS " + (this.player_datas[1] ? this.player_datas[1].name : "AI") + (this.player_datas[3] ? "+" + this.player_datas[3].name : "");
        player_ips = [];
        _.each(this.player_datas, function(player) {
          player_ips.push(player.key);
        });
        recorder_buffer = Buffer.concat(this.recorder_buffers);
        zlib.deflate(recorder_buffer, function(err, replay_buffer) {
          var date_time, recorded_ip;
          replay_buffer = replay_buffer.toString('binary');
          date_time = moment().format('YYYY-MM-DD HH:mm:ss');
          redisdb.hmset("replay:" + replay_id, "replay_id", replay_id, "replay_buffer", replay_buffer, "player_names", player_names, "date_time", date_time);
          if (!log_rep_id && !settings.modules.cloud_replay.never_expire) {
            redisdb.expire("replay:" + replay_id, 60 * 60 * 24);
          }
          recorded_ip = [];
          _.each(player_ips, function(player_ip) {
            if (_.contains(recorded_ip, player_ip)) {
              return;
            }
            recorded_ip.push(player_ip);
            redisdb.lpush(player_ip + ":replays", replay_id);
          });
          if (log_rep_id) {
            log.info("error replay: R#" + replay_id);
          }
        });
      }
      this.watcher_buffers = [];
      this.recorder_buffers = [];
      this.players = [];
      if (this.watcher) {
        this.watcher.destroy();
      }
      if (this.recorder) {
        this.recorder.destroy();
      }
      this.deleted = true;
      index = _.indexOf(ROOM_all, this);
      if (settings.modules.reconnect.enabled) {
        ROOM_clear_disconnect(index);
      }
      if (index !== -1) {
        ROOM_all[index] = null;
      }
      if (!this.windbot && this.established && settings.modules.http.websocket_roomlist) {
        roomlist["delete"](this);
      }
    };

    Room.prototype.get_playing_player = function() {
      var playing_player;
      playing_player = [];
      _.each(this.players, function(player) {
        if (player.pos < 4) {
          playing_player.push(player);
        }
      });
      return playing_player;
    };

    Room.prototype.get_host = function() {
      var host_player;
      host_player = null;
      _.each(this.players, function(player) {
        if (player.is_host) {
          host_player = player;
        }
      });
      return host_player;
    };

    Room.prototype.get_disconnected_count = function() {
      var found, len2, m, player, ref2;
      if (!settings.modules.reconnect.enabled) {
        return 0;
      }
      found = 0;
      ref2 = this.get_playing_player();
      for (m = 0, len2 = ref2.length; m < len2; m++) {
        player = ref2[m];
        if (player.closed) {
          found++;
        }
      }
      return found;
    };

    Room.prototype.get_challonge_score = function() {
      var challonge_duel_log;
      if (!settings.modules.challonge.enabled || this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN || this.hostinfo.mode === 2) {
        return null;
      }
      challonge_duel_log = {};
      if (this.scores[this.dueling_players[0].name_vpass] > this.scores[this.dueling_players[1].name_vpass]) {
        challonge_duel_log.winnerId = this.dueling_players[0].challonge_info.id;
      } else if (this.scores[this.dueling_players[0].name_vpass] < this.scores[this.dueling_players[1].name_vpass]) {
        challonge_duel_log.winnerId = this.dueling_players[1].challonge_info.id;
      } else {
        challonge_duel_log.winnerId = "tie";
      }
      if (settings.modules.challonge.post_detailed_score) {
        if (this.dueling_players[0].challonge_info.id === this.challonge_info.player1Id && this.dueling_players[1].challonge_info.id === this.challonge_info.player2Id) {
          challonge_duel_log.scoresCsv = this.scores[this.dueling_players[0].name_vpass] + "-" + this.scores[this.dueling_players[1].name_vpass];
        } else if (this.dueling_players[1].challonge_info.id === this.challonge_info.player1Id && this.dueling_players[0].challonge_info.id === this.challonge_info.player2Id) {
          challonge_duel_log.scoresCsv = this.scores[this.dueling_players[1].name_vpass] + "-" + this.scores[this.dueling_players[0].name_vpass];
        } else {
          challonge_duel_log.scoresCsv = "0-0";
          log.warn("Score mismatch.", this.name);
        }
      } else {
        if (challonge_duel_log.winnerId === this.challonge_info.player1Id) {
          challonge_duel_log.scoresCsv = "1-0";
        } else if (challonge_duel_log.winnerId === this.challonge_info.player2Id) {
          challonge_duel_log.scoresCsv = "0-1";
        } else {
          challonge_duel_log.scoresCsv = "0-0";
        }
      }
      return challonge_duel_log;
    };

    Room.prototype.send_replays = function() {
      var len2, len3, m, n, player, ref2, ref3;
      if (!(settings.modules.replay_delay && this.replays.length && this.hostinfo.mode === 1)) {
        return false;
      }
      ref2 = this.players;
      for (m = 0, len2 = ref2.length; m < len2; m++) {
        player = ref2[m];
        CLIENT_send_replays(player, this);
      }
      ref3 = this.watchers;
      for (n = 0, len3 = ref3.length; n < len3; n++) {
        player = ref3[n];
        CLIENT_send_replays(player, this);
      }
      return true;
    };

    Room.prototype.add_windbot = function(botdata) {
      this.windbot = botdata;
      request({
        url: "http://" + settings.modules.windbot.server_ip + ":" + settings.modules.windbot.port + "/?name=" + (encodeURIComponent(botdata.name)) + "&deck=" + (encodeURIComponent(botdata.deck)) + "&host=" + settings.modules.windbot.my_ip + "&port=" + settings.port + "&dialog=" + (encodeURIComponent(botdata.dialog)) + "&version=" + settings.version + "&password=" + (encodeURIComponent(this.name))
      }, (function(_this) {
        return function(error, response, body) {
          if (error) {
            log.warn('windbot add error', error, _this.name);
            ygopro.stoc_send_chat_to_room(_this, "${add_windbot_failed}", ygopro.constants.COLORS.RED);
          }
        };
      })(this));
    };

    Room.prototype.connect = function(client) {
      var host_player;
      this.players.push(client);
      client.join_time = moment();
      if (this.random_type) {
        client.abuse_count = 0;
        host_player = this.get_host();
        if (host_player && (host_player !== client)) {
          ROOM_players_oppentlist[host_player.ip] = client.ip;
          ROOM_players_oppentlist[client.ip] = host_player.ip;
        } else {
          ROOM_players_oppentlist[client.ip] = null;
        }
      }
      if (this.established) {
        if (!this.windbot && this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && settings.modules.http.websocket_roomlist) {
          roomlist.update(this);
        }
        client.server.connect(this.port, '127.0.0.1', function() {
          var buffer, len2, m, ref2;
          ref2 = client.pre_establish_buffers;
          for (m = 0, len2 = ref2.length; m < len2; m++) {
            buffer = ref2[m];
            client.server.write(buffer);
          }
          client.established = true;
          client.pre_establish_buffers = [];
        });
      }
    };

    Room.prototype.disconnect = function(client, error) {
      var index, left_name, len2, len3, m, n, player, ref2, ref3;
      if (client.had_new_reconnection) {
        return;
      }
      if (client.is_post_watcher) {
        ygopro.stoc_send_chat_to_room(this, (client.name + " ${quit_watch}") + (error ? ": " + error : ''));
        index = _.indexOf(this.watchers, client);
        if (index !== -1) {
          this.watchers.splice(index, 1);
        }
        client.server.destroy();
      } else {
        if (this.arena && this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && this.disconnector !== 'server' && !this.arena_score_handled) {
          if (settings.modules.arena_mode.punish_quit_before_match && this.players.length === 2 && !client.arena_quit_free) {
            ref2 = this.players;
            for (m = 0, len2 = ref2.length; m < len2; m++) {
              player = ref2[m];
              if (player.pos !== 7) {
                this.scores[player.name_vpass] = 0;
              }
            }
            this.scores[client.name_vpass] = -9;
          } else {
            ref3 = this.players;
            for (n = 0, len3 = ref3.length; n < len3; n++) {
              player = ref3[n];
              if (player.pos !== 7) {
                this.scores[player.name_vpass] = -5;
              }
            }
          }
          this.arena_score_handled = true;
        }
        index = _.indexOf(this.players, client);
        if (index !== -1) {
          this.players.splice(index, 1);
        }
        if (this.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && this.disconnector !== 'server' && client.pos < 4) {
          this.finished = true;
          if (!this.finished_by_death) {
            this.scores[client.name_vpass] = -9;
            if (this.random_type && !client.flee_free && (!settings.modules.reconnect.enabled || this.get_disconnected_count() === 0)) {
              ROOM_ban_player(client.name, client.ip, "${random_ban_reason_flee}");
              if (settings.modules.random_duel.record_match_scores && this.random_type === 'M') {
                ROOM_player_flee(client.name_vpass);
              }
            }
          }
        }
        if (this.players.length && !(this.windbot && client.is_host) && !(this.arena && this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && client.pos <= 3)) {
          left_name = (settings.modules.hide_name && this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN ? "********" : client.name);
          ygopro.stoc_send_chat_to_room(this, (left_name + " ${left_game}") + (error ? ": " + error : ''));
          if (!this.windbot && this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && settings.modules.http.websocket_roomlist) {
            roomlist.update(this);
          }
        } else {
          this.send_replays();
          this.process.kill();
          this["delete"]();
        }
        if (!CLIENT_reconnect_unregister(client, false, true)) {
          client.server.destroy();
        }
      }
    };

    Room.prototype.start_death = function() {
      var oppo_pos, win_pos;
      if (this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN || this.death) {
        return false;
      }
      oppo_pos = this.hostinfo.mode === 2 ? 2 : 1;
      if (this.duel_stage === ygopro.constants.DUEL_STAGE.DUELING) {
        switch (settings.modules.http.quick_death_rule) {
          case 3:
            this.death = -2;
            ygopro.stoc_send_chat_to_room(this, "${death_start_phase}", ygopro.constants.COLORS.BABYBLUE);
            break;
          default:
            this.death = (this.turn ? this.turn + 4 : 5);
            ygopro.stoc_send_chat_to_room(this, "${death_start}", ygopro.constants.COLORS.BABYBLUE);
        }
      } else {
        switch (settings.modules.http.quick_death_rule) {
          case 2:
          case 3:
            if (this.scores[this.dueling_players[0].name_vpass] === this.scores[this.dueling_players[oppo_pos].name_vpass]) {
              if (settings.modules.http.quick_death_rule === 3) {
                this.death = -1;
                ygopro.stoc_send_chat_to_room(this, "${death_start_quick}", ygopro.constants.COLORS.BABYBLUE);
              } else {
                this.death = 5;
                ygopro.stoc_send_chat_to_room(this, "${death_start_siding}", ygopro.constants.COLORS.BABYBLUE);
              }
            } else {
              win_pos = this.scores[this.dueling_players[0].name_vpass] > this.scores[this.dueling_players[oppo_pos].name_vpass] ? 0 : oppo_pos;
              this.finished_by_death = true;
              ygopro.stoc_send_chat_to_room(this, "${death2_finish_part1}" + this.dueling_players[win_pos].name + "${death2_finish_part2}", ygopro.constants.COLORS.BABYBLUE);
              if (this.hostinfo.mode === 1) {
                CLIENT_send_replays(this.dueling_players[oppo_pos - win_pos], this);
              }
              ygopro.stoc_send(this.dueling_players[oppo_pos - win_pos], 'DUEL_END');
              if (this.hostinfo.mode === 2) {
                ygopro.stoc_send(this.dueling_players[oppo_pos - win_pos + 1], 'DUEL_END');
              }
              this.scores[this.dueling_players[oppo_pos - win_pos].name_vpass] = -1;
              CLIENT_kick(this.dueling_players[oppo_pos - win_pos]);
              if (this.hostinfo.mode === 2) {
                CLIENT_kick(this.dueling_players[oppo_pos - win_pos + 1]);
              }
            }
            break;
          case 1:
            this.death = -1;
            ygopro.stoc_send_chat_to_room(this, "${death_start_quick}", ygopro.constants.COLORS.BABYBLUE);
            break;
          default:
            this.death = 5;
            ygopro.stoc_send_chat_to_room(this, "${death_start_siding}", ygopro.constants.COLORS.BABYBLUE);
        }
      }
      return true;
    };

    Room.prototype.cancel_death = function() {
      if (this.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN || !this.death) {
        return false;
      }
      this.death = 0;
      ygopro.stoc_send_chat_to_room(this, "${death_cancel}", ygopro.constants.COLORS.BABYBLUE);
      return true;
    };

    return Room;

  })();

  net.createServer(function(client) {
    var connect_count, server;
    client.ip = client.remoteAddress;
    client.is_local = client.ip && (client.ip.includes('127.0.0.1') || client.ip.includes(real_windbot_server_ip));
    connect_count = ROOM_connected_ip[client.ip] || 0;
    if (!settings.modules.test_mode.no_connect_count_limit && !client.is_local) {
      connect_count++;
    }
    ROOM_connected_ip[client.ip] = connect_count;
    server = new net.Socket();
    client.server = server;
    server.client = client;
    client.setTimeout(2000);
    client.on('close', function(had_error) {
      var room;
      room = ROOM_all[client.rid];
      connect_count = ROOM_connected_ip[client.ip];
      if (connect_count > 0) {
        connect_count--;
      }
      ROOM_connected_ip[client.ip] = connect_count;
      if (!client.closed) {
        client.closed = true;
        if (settings.modules.heartbeat_detection.enabled) {
          CLIENT_heartbeat_unregister(client);
        }
        if (room) {
          if (!CLIENT_reconnect_register(client, client.rid)) {
            room.disconnect(client);
          }
        } else if (!client.had_new_reconnection) {
          client.server.destroy();
        }
      }
    });
    client.on('error', function(error) {
      var room;
      room = ROOM_all[client.rid];
      connect_count = ROOM_connected_ip[client.ip];
      if (connect_count > 0) {
        connect_count--;
      }
      ROOM_connected_ip[client.ip] = connect_count;
      if (!client.closed) {
        client.closed = true;
        if (room) {
          if (!CLIENT_reconnect_register(client, client.rid, error)) {
            room.disconnect(client, error);
          }
        } else if (!client.had_new_reconnection) {
          client.server.destroy();
        }
      }
    });
    client.on('timeout', function() {
      if (!(settings.modules.reconnect.enabled && (disconnect_list[CLIENT_get_authorize_key(client)] || client.had_new_reconnection))) {
        client.destroy();
      }
    });
    server.on('close', function(had_error) {
      var room;
      room = ROOM_all[server.client.rid];
      if (room) {
        room.disconnector = 'server';
      }
      if (!server.closed) {
        server.closed = true;
      }
      if (!server.client) {
        return;
      }
      if (!server.client.closed) {
        ygopro.stoc_send_chat(server.client, "${server_closed}", ygopro.constants.COLORS.RED);
        CLIENT_kick(server.client);
        SERVER_clear_disconnect(server);
      }
    });
    server.on('error', function(error) {
      var room;
      room = ROOM_all[server.client.rid];
      if (room) {
        room.disconnector = 'server';
      }
      server.closed = error;
      if (!server.client) {
        return;
      }
      if (!server.client.closed) {
        ygopro.stoc_send_chat(server.client, "${server_error}: " + error, ygopro.constants.COLORS.RED);
        CLIENT_kick(server.client);
        SERVER_clear_disconnect(server);
      }
    });
    if (ROOM_bad_ip[client.ip] > 5 || ROOM_connected_ip[client.ip] > 10) {
      log.info('BAD IP', client.ip);
      CLIENT_kick(client);
      return;
    }
    if (settings.modules.cloud_replay.enabled) {
      client.open_cloud_replay = function(err, replay) {
        var buffer;
        if (err || !replay) {
          ygopro.stoc_die(client, "${cloud_replay_no}");
          return;
        }
        redisdb.expire("replay:" + replay.replay_id, 60 * 60 * 48);
        buffer = Buffer.from(replay.replay_buffer, 'binary');
        zlib.unzip(buffer, function(err, replay_buffer) {
          if (err) {
            log.info("cloud replay unzip error: " + err);
            ygopro.stoc_send_chat(client, "${cloud_replay_error}", ygopro.constants.COLORS.RED);
            CLIENT_kick(client);
            return;
          }
          ygopro.stoc_send_chat(client, "${cloud_replay_playing} R#" + replay.replay_id + " " + replay.player_names + " " + replay.date_time, ygopro.constants.COLORS.BABYBLUE);
          client.write(replay_buffer, function() {
            CLIENT_kick(client);
          });
        });
      };
    }
    client.pre_establish_buffers = new Array();
    client.on('data', function(ctos_buffer) {
      var b, bad_ip_count, buffer, cancel, ctos_event, ctos_message_length, ctos_proto, datas, info, len2, len3, len4, len5, looplimit, m, n, o, p, ref2, ref3, result, room, struct;
      if (client.is_post_watcher) {
        room = ROOM_all[client.rid];
        if (room && !CLIENT_is_banned_by_mc(client)) {
          room.watcher.write(ctos_buffer);
        }
      } else {
        ctos_message_length = 0;
        ctos_proto = 0;
        datas = [];
        looplimit = 0;
        while (true) {
          if (ctos_message_length === 0) {
            if (ctos_buffer.length >= 2) {
              ctos_message_length = ctos_buffer.readUInt16LE(0);
            } else {
              if (ctos_buffer.length !== 0) {
                log.warn("bad ctos_buffer length", client.ip);
              }
              break;
            }
          } else if (ctos_proto === 0) {
            if (ctos_buffer.length >= 3) {
              ctos_proto = ctos_buffer.readUInt8(2);
            } else {
              log.warn("bad ctos_proto length", client.ip);
              break;
            }
          } else {
            if (ctos_buffer.length >= 2 + ctos_message_length) {
              cancel = false;
              if (settings.modules.reconnect.enabled && client.pre_reconnecting && ygopro.constants.CTOS[ctos_proto] !== 'UPDATE_DECK') {
                cancel = true;
              }
              b = ctos_buffer.slice(3, ctos_message_length - 1 + 3);
              info = null;
              if (struct = ygopro.structs[ygopro.proto_structs.CTOS[ygopro.constants.CTOS[ctos_proto]]]) {
                struct._setBuff(b);
                info = _.clone(struct.fields);
              }
              if (ygopro.ctos_follows_before[ctos_proto] && !cancel) {
                ref2 = ygopro.ctos_follows_before[ctos_proto];
                for (m = 0, len2 = ref2.length; m < len2; m++) {
                  ctos_event = ref2[m];
                  result = ctos_event.callback(b, info, client, client.server, datas);
                  if (result && ctos_event.synchronous) {
                    cancel = true;
                  }
                }
              }
              if (ygopro.ctos_follows[ctos_proto] && !cancel) {
                result = ygopro.ctos_follows[ctos_proto].callback(b, info, client, client.server, datas);
                if (result && ygopro.ctos_follows[ctos_proto].synchronous) {
                  cancel = true;
                }
              }
              if (ygopro.ctos_follows_after[ctos_proto] && !cancel) {
                ref3 = ygopro.ctos_follows_after[ctos_proto];
                for (n = 0, len3 = ref3.length; n < len3; n++) {
                  ctos_event = ref3[n];
                  result = ctos_event.callback(b, info, client, client.server, datas);
                  if (result && ctos_event.synchronous) {
                    cancel = true;
                  }
                }
              }
              if (!cancel) {
                datas.push(ctos_buffer.slice(0, 2 + ctos_message_length));
              }
              ctos_buffer = ctos_buffer.slice(2 + ctos_message_length);
              ctos_message_length = 0;
              ctos_proto = 0;
            } else {
              if (ctos_message_length !== 17735) {
                log.warn("bad ctos_message length", client.ip, ctos_buffer.length, ctos_message_length, ctos_proto);
              }
              break;
            }
          }
          looplimit++;
          if (looplimit > 800 || ROOM_bad_ip[client.ip] > 5) {
            log.info("error ctos", client.name, client.ip);
            bad_ip_count = ROOM_bad_ip[client.ip];
            if (bad_ip_count) {
              ROOM_bad_ip[client.ip] = bad_ip_count + 1;
            } else {
              ROOM_bad_ip[client.ip] = 1;
            }
            CLIENT_kick(client);
            break;
          }
        }
        if (!client.server) {
          return;
        }
        if (client.established) {
          for (o = 0, len4 = datas.length; o < len4; o++) {
            buffer = datas[o];
            client.server.write(buffer);
          }
        } else {
          for (p = 0, len5 = datas.length; p < len5; p++) {
            buffer = datas[p];
            client.pre_establish_buffers.push(buffer);
          }
        }
      }
    });
    server.on('data', function(stoc_buffer) {
      var b, buffer, cancel, datas, info, len2, len3, len4, looplimit, m, n, o, ref2, ref3, result, stoc_event, stoc_message_length, stoc_proto, struct;
      stoc_message_length = 0;
      stoc_proto = 0;
      datas = [];
      looplimit = 0;
      while (true) {
        if (stoc_message_length === 0) {
          if (stoc_buffer.length >= 2) {
            stoc_message_length = stoc_buffer.readUInt16LE(0);
          } else {
            if (stoc_buffer.length !== 0) {
              log.warn("bad stoc_buffer length", server.client.ip);
            }
            break;
          }
        } else if (stoc_proto === 0) {
          if (stoc_buffer.length >= 3) {
            stoc_proto = stoc_buffer.readUInt8(2);
          } else {
            log.warn("bad stoc_proto length", server.client.ip);
            break;
          }
        } else {
          if (stoc_buffer.length >= 2 + stoc_message_length) {
            cancel = false;
            b = stoc_buffer.slice(3, stoc_message_length - 1 + 3);
            info = null;
            if (struct = ygopro.structs[ygopro.proto_structs.STOC[ygopro.constants.STOC[stoc_proto]]]) {
              struct._setBuff(b);
              info = _.clone(struct.fields);
            }
            if (ygopro.stoc_follows_before[stoc_proto] && !cancel) {
              ref2 = ygopro.stoc_follows_before[stoc_proto];
              for (m = 0, len2 = ref2.length; m < len2; m++) {
                stoc_event = ref2[m];
                result = stoc_event.callback(b, info, server.client, server, datas);
                if (result && stoc_event.synchronous) {
                  cancel = true;
                }
              }
            }
            if (ygopro.stoc_follows[stoc_proto] && !cancel) {
              result = ygopro.stoc_follows[stoc_proto].callback(b, info, server.client, server, datas);
              if (result && ygopro.stoc_follows[stoc_proto].synchronous) {
                cancel = true;
              }
            }
            if (ygopro.stoc_follows_after[stoc_proto] && !cancel) {
              ref3 = ygopro.stoc_follows_after[stoc_proto];
              for (n = 0, len3 = ref3.length; n < len3; n++) {
                stoc_event = ref3[n];
                result = stoc_event.callback(b, info, server.client, server, datas);
                if (result && stoc_event.synchronous) {
                  cancel = true;
                }
              }
            }
            if (!cancel) {
              datas.push(stoc_buffer.slice(0, 2 + stoc_message_length));
            }
            stoc_buffer = stoc_buffer.slice(2 + stoc_message_length);
            stoc_message_length = 0;
            stoc_proto = 0;
          } else {
            log.warn("bad stoc_message length", server.client.ip);
            break;
          }
        }
        looplimit++;
        if (looplimit > 800) {
          log.info("error stoc", server.client.name);
          server.destroy();
          break;
        }
      }
      if (server.client && !server.client.closed) {
        for (o = 0, len4 = datas.length; o < len4; o++) {
          buffer = datas[o];
          server.client.write(buffer);
        }
      }
    });
  }).listen(settings.port, function() {
    log.info("server started", settings.port);
  });

  if (settings.modules.stop) {
    log.info("NOTE: server not open due to config, ", settings.modules.stop);
  }

  ygopro.ctos_follow('PLAYER_INFO', true, function(buffer, info, client, server, datas) {
    var geo, lang, name, name_full, struct, vpass;
    name_full = info.name.split("$");
    name = name_full[0];
    vpass = name_full[1];
    if (vpass && !vpass.length) {
      vpass = null;
    }
    if (_.any(settings.ban.illegal_id, function(badid) {
      var matchs, regexp;
      regexp = new RegExp(badid, 'i');
      matchs = name.match(regexp);
      if (matchs) {
        name = matchs[1];
        return true;
      }
      return false;
    }, name)) {
      client.rag = true;
    }
    if (settings.modules.mycard.enabled) {
      request({
        url: settings.modules.mycard.ban_get,
        json: true,
        qs: {
          user: name
        }
      }, function(error, response, body) {
        if (_.isString(body)) {
          log.warn("ban get bad json", body);
        } else if (error || !body) {
          log.warn('ban get error', error, response);
        } else {
          client.ban_mc = body;
        }
      });
    }
    struct = ygopro.structs["CTOS_PlayerInfo"];
    struct._setBuff(buffer);
    struct.set("name", name);
    buffer = struct.buffer;
    client.name = name;
    client.vpass = vpass;
    client.name_vpass = vpass ? name + "$" + vpass : name;
    if (!settings.modules.i18n.auto_pick || client.is_local) {
      client.lang = settings.modules.i18n["default"];
    } else {
      geo = geoip.lookup(client.ip);
      if (!geo) {
        log.warn("fail to locate ip", client.name, client.ip);
        client.lang = settings.modules.i18n.fallback;
      } else {
        if (lang = settings.modules.i18n.map[geo.country]) {
          client.lang = lang;
        } else {
          client.lang = settings.modules.i18n.fallback;
        }
      }
    }
    return false;
  });

  ygopro.ctos_follow('JOIN_GAME', false, function(buffer, info, client, server, datas) {
    var buffer_handle_callback, check_buffer_indentity, len2, len3, m, match_permit_callback, n, name, pre_room, ref2, ref3, replay_id, room;
    info.pass = info.pass.trim();
    client.pass = info.pass;
    if (CLIENT_is_able_to_reconnect(client) || CLIENT_is_able_to_kick_reconnect(client)) {
      CLIENT_pre_reconnect(client);
      return;
    } else if (settings.modules.stop) {
      ygopro.stoc_die(client, settings.modules.stop);
    } else if (info.pass === "Marshtomp" || info.pass === "the Big Brother") {
      ygopro.stoc_die(client, "${bad_user_name}");
    } else if (info.pass.toUpperCase() === "R" && settings.modules.cloud_replay.enabled) {
      ygopro.stoc_send_chat(client, "${cloud_replay_hint}", ygopro.constants.COLORS.BABYBLUE);
      redisdb.lrange(CLIENT_get_authorize_key(client) + ":replays", 0, 2, function(err, result) {
        _.each(result, function(replay_id, id) {
          redisdb.hgetall("replay:" + replay_id, function(err, replay) {
            if (err || !replay) {
              if (err) {
                log.info("cloud replay getall error: " + err);
              }
              return;
            }
            ygopro.stoc_send_chat(client, "<" + (id - 0 + 1) + "> R#" + replay_id + " " + replay.player_names + " " + replay.date_time, ygopro.constants.COLORS.BABYBLUE);
          });
        });
      });
      setTimeout((function() {
        ygopro.stoc_send(client, 'ERROR_MSG', {
          msg: 1,
          code: 9
        });
        CLIENT_kick(client);
      }), 500);
    } else if (info.pass.slice(0, 2).toUpperCase() === "R#" && settings.modules.cloud_replay.enabled) {
      replay_id = info.pass.split("#")[1];
      if (replay_id > 0 && replay_id <= 9) {
        redisdb.lindex(client.ip + ":replays", replay_id - 1, function(err, replay_id) {
          if (err || !replay_id) {
            if (err) {
              log.info("cloud replay replayid error: " + err);
            }
            ygopro.stoc_die(client, "${cloud_replay_no}");
            return;
          }
          redisdb.hgetall("replay:" + replay_id, client.open_cloud_replay);
        });
      } else if (replay_id) {
        redisdb.hgetall("replay:" + replay_id, client.open_cloud_replay);
      } else {
        ygopro.stoc_die(client, "${cloud_replay_no}");
      }
    } else if (info.pass.toUpperCase() === "W" && settings.modules.cloud_replay.enabled) {
      replay_id = Cloud_replay_ids[Math.floor(Math.random() * Cloud_replay_ids.length)];
      redisdb.hgetall("replay:" + replay_id, client.open_cloud_replay);
    } else if (info.version !== settings.version) {
      ygopro.stoc_send_chat(client, settings.modules.update, ygopro.constants.COLORS.RED);
      ygopro.stoc_send(client, 'ERROR_MSG', {
        msg: 4,
        code: settings.version
      });
      CLIENT_kick(client);
    } else if (!info.pass.length && !settings.modules.random_duel.enabled && !settings.modules.windbot.enabled && !settings.modules.challonge.enabled) {
      ygopro.stoc_die(client, "${blank_room_name}");
    } else if (info.pass.length && settings.modules.mycard.enabled && info.pass.slice(0, 3) !== 'AI#') {
      ygopro.stoc_send_chat(client, '${loading_user_info}', ygopro.constants.COLORS.BABYBLUE);
      if (info.pass.length <= 8) {
        ygopro.stoc_die(client, '${invalid_password_length}');
        return;
      }
      buffer = Buffer.from(info.pass.slice(0, 8), 'base64');
      if (buffer.length !== 6) {
        ygopro.stoc_die(client, '${invalid_password_payload}');
        return;
      }
      check_buffer_indentity = function(buf) {
        var checksum, i, m, ref2;
        checksum = 0;
        for (i = m = 0, ref2 = buf.length; 0 <= ref2 ? m < ref2 : m > ref2; i = 0 <= ref2 ? ++m : --m) {
          checksum += buf.readUInt8(i);
        }
        return (checksum & 0xFF) === 0;
      };
      buffer_handle_callback = function(buffer, decrypted_buffer, match_permit) {
        var action, len2, len3, m, n, name, opt1, opt2, opt3, options, player, ref2, ref3, room, title;
        if (client.closed) {
          return;
        }
        action = buffer.readUInt8(1) >> 4;
        if (buffer !== decrypted_buffer && (action === 1 || action === 2 || action === 4)) {
          ygopro.stoc_die(client, '${invalid_password_unauthorized}');
          return;
        }
        switch (action) {
          case 1:
          case 2:
            name = crypto.createHash('md5').update(info.pass + client.name).digest('base64').slice(0, 10).replace('+', '-').replace('/', '_');
            if (ROOM_find_by_name(name)) {
              ygopro.stoc_die(client, '${invalid_password_existed}');
              return;
            }
            opt1 = buffer.readUInt8(2);
            opt2 = buffer.readUInt16LE(3);
            opt3 = buffer.readUInt8(5);
            options = {
              lflist: 0,
              time_limit: 180,
              rule: (opt1 >> 5) & 3,
              mode: (opt1 >> 3) & 3,
              duel_rule: (!!((opt1 >> 2) & 1) ? 3 : 4),
              no_check_deck: !!((opt1 >> 1) & 1),
              no_shuffle_deck: !!(opt1 & 1),
              start_lp: opt2,
              start_hand: opt3 >> 4,
              draw_count: opt3 & 0xF,
              no_watch: false,
              auto_death: false
            };
            options.lflist = _.findIndex(lflists, function(list) {
              return ((options.rule === 1) === list.tcg) && list.date.isBefore();
            });
            room = new Room(name, options);
            if (room) {
              room.title = info.pass.slice(8).replace(String.fromCharCode(0xFEFF), ' ');
              room["private"] = action === 2;
            }
            break;
          case 3:
            name = info.pass.slice(8);
            room = ROOM_find_by_name(name);
            if (!room) {
              ygopro.stoc_die(client, '${invalid_password_not_found}');
              return;
            }
            break;
          case 4:
            if (match_permit && !match_permit.permit) {
              ygopro.stoc_die(client, '${invalid_password_unauthorized}');
              return;
            }
            room = ROOM_find_or_create_by_name('M#' + info.pass.slice(8));
            if (room) {
              ref2 = room.get_playing_player();
              for (m = 0, len2 = ref2.length; m < len2; m++) {
                player = ref2[m];
                if (!(player && player.name === client.name)) {
                  continue;
                }
                ygopro.stoc_die(client, '${invalid_password_unauthorized}');
                return;
              }
              room["private"] = true;
              room.arena = settings.modules.arena_mode.mode;
              if (room.arena === "athletic") {
                room.max_player = 2;
                room.welcome = "${athletic_arena_tip}";
              }
            }
            break;
          case 5:
            title = info.pass.slice(8).replace(String.fromCharCode(0xFEFF), ' ');
            room = ROOM_find_by_title(title);
            if (!room) {
              ygopro.stoc_die(client, '${invalid_password_not_found}');
              return;
            }
            break;
          default:
            ygopro.stoc_die(client, '${invalid_password_action}');
            return;
        }
        if (!room) {
          ygopro.stoc_die(client, "${server_full}");
        } else if (room.error) {
          ygopro.stoc_die(client, room.error);
        } else if (room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
          if (settings.modules.cloud_replay.enable_halfway_watch && !room.hostinfo.no_watch) {
            client.setTimeout(300000);
            client.rid = _.indexOf(ROOM_all, room);
            client.is_post_watcher = true;
            ygopro.stoc_send_chat_to_room(room, client.name + " ${watch_join}");
            room.watchers.push(client);
            ygopro.stoc_send_chat(client, "${watch_watching}", ygopro.constants.COLORS.BABYBLUE);
            ref3 = room.watcher_buffers;
            for (n = 0, len3 = ref3.length; n < len3; n++) {
              buffer = ref3[n];
              client.write(buffer);
            }
          } else {
            ygopro.stoc_die(client, "${watch_denied}");
          }
        } else if (room.hostinfo.no_watch && room.players.length >= (room.hostinfo.mode === 2 ? 4 : 2)) {
          ygopro.stoc_die(client, "${watch_denied_room}");
        } else {
          client.setTimeout(300000);
          client.rid = _.indexOf(ROOM_all, room);
          room.connect(client);
        }
      };
      match_permit_callback = function(buffer, match_permit) {
        var decrypted_buffer, i, id, len2, m, ref2, secret;
        if (client.closed) {
          return;
        }
        if (id = users_cache[client.name]) {
          secret = id % 65535 + 1;
          decrypted_buffer = Buffer.allocUnsafe(6);
          ref2 = [0, 2, 4];
          for (m = 0, len2 = ref2.length; m < len2; m++) {
            i = ref2[m];
            decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i);
          }
          if (check_buffer_indentity(decrypted_buffer)) {
            return buffer_handle_callback(decrypted_buffer, decrypted_buffer, match_permit);
          }
        }
        request({
          baseUrl: settings.modules.mycard.auth_base_url,
          url: '/users/' + encodeURIComponent(client.name) + '.json',
          qs: {
            api_key: settings.modules.mycard.auth_key,
            api_username: client.name,
            skip_track_visit: true
          },
          json: true
        }, function(error, response, body) {
          var len3, n, ref3;
          if (body && body.user) {
            users_cache[client.name] = body.user.id;
            secret = body.user.id % 65535 + 1;
            decrypted_buffer = Buffer.allocUnsafe(6);
            ref3 = [0, 2, 4];
            for (n = 0, len3 = ref3.length; n < len3; n++) {
              i = ref3[n];
              decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i);
            }
            if (check_buffer_indentity(decrypted_buffer)) {
              buffer = decrypted_buffer;
            }
          }
          if (!check_buffer_indentity(buffer)) {
            ygopro.stoc_die(client, '${invalid_password_checksum}');
            return;
          }
          return buffer_handle_callback(buffer, decrypted_buffer, match_permit);
        });
      };
      if (settings.modules.arena_mode.check_permit) {
        request({
          url: settings.modules.arena_mode.check_permit,
          json: true,
          qs: {
            username: client.name,
            password: info.pass,
            arena: settings.modules.arena_mode.mode
          }
        }, function(error, response, body) {
          if (client.closed) {
            return;
          }
          if (!error && body) {
            match_permit_callback(buffer, body);
          } else {
            log.warn("Match permit request error", error);
            match_permit_callback(buffer, null);
          }
        });
      } else {
        match_permit_callback(buffer, null);
      }
    } else if (settings.modules.challonge.enabled) {
      pre_room = ROOM_find_by_name(info.pass);
      if (pre_room && pre_room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && settings.modules.cloud_replay.enable_halfway_watch && !pre_room.hostinfo.no_watch) {
        room = pre_room;
        client.setTimeout(300000);
        client.rid = _.indexOf(ROOM_all, room);
        client.is_post_watcher = true;
        ygopro.stoc_send_chat_to_room(room, client.name + " ${watch_join}");
        room.watchers.push(client);
        ygopro.stoc_send_chat(client, "${watch_watching}", ygopro.constants.COLORS.BABYBLUE);
        ref2 = room.watcher_buffers;
        for (m = 0, len2 = ref2.length; m < len2; m++) {
          buffer = ref2[m];
          client.write(buffer);
        }
      } else {
        ygopro.stoc_send_chat(client, '${loading_user_info}', ygopro.constants.COLORS.BABYBLUE);
        client.setTimeout(300000);
        challonge.participants._index({
          id: settings.modules.challonge.tournament_id,
          callback: function(err, data) {
            var found, k, user;
            if (client.closed) {
              return;
            }
            if (err || !data) {
              if (err) {
                log.warn("Failed loading Challonge user info", err);
              }
              ygopro.stoc_die(client, '${challonge_match_load_failed}');
              return;
            }
            found = false;
            for (k in data) {
              user = data[k];
              if (user.participant && user.participant.name && _.endsWith(user.participant.name, client.name)) {
                found = user.participant;
                break;
              }
            }
            if (!found) {
              ygopro.stoc_die(client, '${challonge_user_not_found}');
              return;
            }
            client.challonge_info = found;
            challonge.matches._index({
              id: settings.modules.challonge.tournament_id,
              callback: function(err, data) {
                var len3, len4, match, n, o, player, ref3, ref4;
                if (client.closed) {
                  return;
                }
                if (err || !data) {
                  if (err) {
                    log.warn("Failed loading Challonge match info", err);
                  }
                  ygopro.stoc_die(client, '${challonge_match_load_failed}');
                  return;
                }
                found = false;
                for (k in data) {
                  match = data[k];
                  if (match && match.match && !match.match.winnerId && match.match.state !== "complete" && match.match.player1Id && match.match.player2Id && (match.match.player1Id === client.challonge_info.id || match.match.player2Id === client.challonge_info.id)) {
                    found = match.match;
                    break;
                  }
                }
                if (!found) {
                  ygopro.stoc_die(client, '${challonge_match_not_found}');
                  return;
                }
                room = ROOM_find_or_create_by_name('M#' + found.id);
                if (room) {
                  room.challonge_info = found;
                  room.welcome = "${challonge_match_created}";
                }
                if (!room) {
                  ygopro.stoc_die(client, "${server_full}");
                } else if (room.error) {
                  ygopro.stoc_die(client, room.error);
                } else if (room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
                  if (settings.modules.cloud_replay.enable_halfway_watch && !room.hostinfo.no_watch) {
                    client.rid = _.indexOf(ROOM_all, room);
                    client.is_post_watcher = true;
                    ygopro.stoc_send_chat_to_room(room, client.name + " ${watch_join}");
                    room.watchers.push(client);
                    ygopro.stoc_send_chat(client, "${watch_watching}", ygopro.constants.COLORS.BABYBLUE);
                    ref3 = room.watcher_buffers;
                    for (n = 0, len3 = ref3.length; n < len3; n++) {
                      buffer = ref3[n];
                      client.write(buffer);
                    }
                  } else {
                    ygopro.stoc_die(client, "${watch_denied}");
                  }
                } else if (room.hostinfo.no_watch && room.players.length >= (room.hostinfo.mode === 2 ? 4 : 2)) {
                  ygopro.stoc_die(client, "${watch_denied_room}");
                } else {
                  ref4 = room.get_playing_player();
                  for (o = 0, len4 = ref4.length; o < len4; o++) {
                    player = ref4[o];
                    if (!(player && player !== client && player.challonge_info.id === client.challonge_info.id)) {
                      continue;
                    }
                    ygopro.stoc_die(client, "${challonge_player_already_in}");
                    return;
                  }
                  client.rid = _.indexOf(ROOM_all, room);
                  room.connect(client);
                }
              }
            });
          }
        });
      }
    } else if (!client.name || client.name === "") {
      ygopro.stoc_die(client, "${bad_user_name}");
    } else if (ROOM_connected_ip[client.ip] > 5) {
      log.warn("MULTI LOGIN", client.name, client.ip);
      ygopro.stoc_die(client, "${too_much_connection}" + client.ip);
    } else if (_.indexOf(settings.ban.banned_user, client.name) > -1) {
      settings.ban.banned_ip.push(client.ip);
      setting_save(settings);
      log.warn("BANNED USER LOGIN", client.name, client.ip);
      ygopro.stoc_die(client, "${banned_user_login}");
    } else if (_.indexOf(settings.ban.banned_ip, client.ip) > -1) {
      log.warn("BANNED IP LOGIN", client.name, client.ip);
      ygopro.stoc_die(client, "${banned_ip_login}");
    } else if (_.any(badwords.level3, function(badword) {
      var regexp;
      regexp = new RegExp(badword, 'i');
      return name.match(regexp);
    }, name = client.name)) {
      log.warn("BAD NAME LEVEL 3", client.name, client.ip);
      ygopro.stoc_die(client, "${bad_name_level3}");
    } else if (_.any(badwords.level2, function(badword) {
      var regexp;
      regexp = new RegExp(badword, 'i');
      return name.match(regexp);
    }, name = client.name)) {
      log.warn("BAD NAME LEVEL 2", client.name, client.ip);
      ygopro.stoc_die(client, "${bad_name_level2}");
    } else if (_.any(badwords.level1, function(badword) {
      var regexp;
      regexp = new RegExp(badword, 'i');
      return name.match(regexp);
    }, name = client.name)) {
      log.warn("BAD NAME LEVEL 1", client.name, client.ip);
      ygopro.stoc_die(client, "${bad_name_level1}");
    } else if (info.pass.length && !ROOM_validate(info.pass)) {
      ygopro.stoc_die(client, "${invalid_password_room}");
    } else {
      room = ROOM_find_or_create_by_name(info.pass, client.ip);
      if (!room) {
        ygopro.stoc_die(client, "${server_full}");
      } else if (room.error) {
        ygopro.stoc_die(client, room.error);
      } else if (room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
        if (settings.modules.cloud_replay.enable_halfway_watch && !room.hostinfo.no_watch) {
          client.setTimeout(300000);
          client.rid = _.indexOf(ROOM_all, room);
          client.is_post_watcher = true;
          ygopro.stoc_send_chat_to_room(room, client.name + " ${watch_join}");
          room.watchers.push(client);
          ygopro.stoc_send_chat(client, "${watch_watching}", ygopro.constants.COLORS.BABYBLUE);
          ref3 = room.watcher_buffers;
          for (n = 0, len3 = ref3.length; n < len3; n++) {
            buffer = ref3[n];
            client.write(buffer);
          }
        } else {
          ygopro.stoc_die(client, "${watch_denied}");
        }
      } else if (room.hostinfo.no_watch && room.players.length >= (room.hostinfo.mode === 2 ? 4 : 2)) {
        ygopro.stoc_die(client, "${watch_denied_room}");
      } else {
        client.setTimeout(300000);
        client.rid = _.indexOf(ROOM_all, room);
        room.connect(client);
      }
    }
  });

  ygopro.stoc_follow('JOIN_GAME', false, function(buffer, info, client, server, datas) {
    var len2, m, player, recorder, ref2, room, watcher;
    room = ROOM_all[client.rid];
    if (!(room && !client.reconnecting)) {
      return;
    }
    if (!room.join_game_buffer) {
      room.join_game_buffer = buffer;
    }
    if (settings.modules.welcome) {
      ygopro.stoc_send_chat(client, settings.modules.welcome, ygopro.constants.COLORS.GREEN);
    }
    if (room.welcome) {
      ygopro.stoc_send_chat(client, room.welcome, ygopro.constants.COLORS.BABYBLUE);
    }
    if (settings.modules.arena_mode.enabled && !client.is_local) {
      request({
        url: settings.modules.arena_mode.get_score + encodeURIComponent(client.name),
        json: true
      }, function(error, response, body) {
        var rank_txt;
        if (error) {
          log.warn('LOAD SCORE ERROR', client.name, error);
        } else if (!body || _.isString(body)) {
          log.warn('LOAD SCORE FAIL', client.name, response.statusCode, response.statusMessage, body);
        } else {
          rank_txt = body.arena_rank > 0 ? "${rank_arena}" + body.arena_rank : "${rank_blank}";
          ygopro.stoc_send_chat(client, client.name + "${exp_value_part1}" + body.exp + "${exp_value_part2}${exp_value_part3}" + (Math.round(body.pt)) + rank_txt + "${exp_value_part4}", ygopro.constants.COLORS.BABYBLUE);
        }
      });
    }
    if (settings.modules.random_duel.record_match_scores && room.random_type === 'M') {
      ygopro.stoc_send_chat_to_room(room, ROOM_player_get_score(client), ygopro.constants.COLORS.GREEN);
      ref2 = room.players;
      for (m = 0, len2 = ref2.length; m < len2; m++) {
        player = ref2[m];
        if (player.pos !== 7 && player !== client) {
          ygopro.stoc_send_chat(client, ROOM_player_get_score(player), ygopro.constants.COLORS.GREEN);
        }
      }
    }
    if (!room.recorder) {
      room.recorder = recorder = net.connect(room.port, function() {
        ygopro.ctos_send(recorder, 'PLAYER_INFO', {
          name: "Marshtomp"
        });
        ygopro.ctos_send(recorder, 'JOIN_GAME', {
          version: settings.version,
          pass: "Marshtomp"
        });
        ygopro.ctos_send(recorder, 'HS_TOOBSERVER');
      });
      recorder.on('data', function(data) {
        room = ROOM_all[client.rid];
        if (!(room && settings.modules.cloud_replay.enabled)) {
          return;
        }
        room.recorder_buffers.push(data);
      });
      recorder.on('error', function(error) {});
    }
    if (settings.modules.cloud_replay.enable_halfway_watch && !room.watcher && !room.hostinfo.no_watch) {
      room.watcher = watcher = settings.modules.test_mode.watch_public_hand ? room.recorder : net.connect(room.port, function() {
        ygopro.ctos_send(watcher, 'PLAYER_INFO', {
          name: "the Big Brother"
        });
        ygopro.ctos_send(watcher, 'JOIN_GAME', {
          version: settings.version,
          pass: "the Big Brother"
        });
        ygopro.ctos_send(watcher, 'HS_TOOBSERVER');
      });
      watcher.on('data', function(data) {
        var len3, n, ref3, w;
        room = ROOM_all[client.rid];
        if (!room) {
          return;
        }
        room.watcher_buffers.push(data);
        ref3 = room.watchers;
        for (n = 0, len3 = ref3.length; n < len3; n++) {
          w = ref3[n];
          if (w) {
            w.write(data);
          }
        }
      });
      watcher.on('error', function(error) {});
    }
  });

  load_dialogues = global.load_dialogues = function() {
    request({
      url: settings.modules.dialogues.get,
      json: true
    }, function(error, response, body) {
      if (_.isString(body)) {
        log.warn("dialogues bad json", body);
      } else if (error || !body) {
        log.warn('dialogues error', error, response);
      } else {
        setting_change(dialogues, "dialogues", body);
        log.info("dialogues loaded", _.size(dialogues.dialogues));
      }
    });
  };

  if (settings.modules.dialogues.get) {
    load_dialogues();
  }

  ygopro.stoc_follow('GAME_MSG', true, function(buffer, info, client, server, datas) {
    var card, chain, check, count, cpos, deck_found, found, hint_type, i, id, len2, len3, len4, len5, limbo_found, line, loc, m, max_loop, msg, n, o, oppo_pos, p, phase, player, playertype, pos, ppos, q, reason, ref2, ref3, ref4, ref5, ref6, ref7, room, trigger_location, val, win_pos;
    room = ROOM_all[client.rid];
    if (!(room && !client.reconnecting)) {
      return;
    }
    msg = buffer.readInt8(0);
    if (settings.modules.retry_handle.enabled) {
      if (ygopro.constants.MSG[msg] === 'RETRY') {
        if (client.retry_count == null) {
          client.retry_count = 0;
        }
        client.retry_count++;
        log.warn("MSG_RETRY detected", client.name, client.ip, msg, client.retry_count);
        if (settings.modules.retry_handle.max_retry_count && client.retry_count >= settings.modules.retry_handle.max_retry_count) {
          ygopro.stoc_send_chat_to_room(room, client.name + "${retry_too_much_room_part1}" + settings.modules.retry_handle.max_retry_count + "${retry_too_much_room_part2}", ygopro.constants.COLORS.BABYBLUE);
          ygopro.stoc_send_chat(client, "${retry_too_much_part1}" + settings.modules.retry_handle.max_retry_count + "${retry_too_much_part2}", ygopro.constants.COLORS.RED);
          CLIENT_send_replays(client, room);
          CLIENT_kick(client);
          return true;
        }
        if (client.last_game_msg) {
          if (settings.modules.retry_handle.max_retry_count) {
            ygopro.stoc_send_chat(client, "${retry_part1}" + client.retry_count + "${retry_part2}" + settings.modules.retry_handle.max_retry_count + "${retry_part3}", ygopro.constants.COLORS.RED);
          } else {
            ygopro.stoc_send_chat(client, "${retry_not_counted}", ygopro.constants.COLORS.BABYBLUE);
          }
          if (client.last_hint_msg) {
            ygopro.stoc_send(client, 'GAME_MSG', client.last_hint_msg);
          }
          ygopro.stoc_send(client, 'GAME_MSG', client.last_game_msg);
          return true;
        }
      } else {
        client.last_game_msg = buffer;
        client.last_game_msg_title = ygopro.constants.MSG[msg];
      }
    } else if (ygopro.constants.MSG[msg] !== 'RETRY') {
      client.last_game_msg = buffer;
      client.last_game_msg_title = ygopro.constants.MSG[msg];
    }
    if ((msg >= 10 && msg < 30) || msg === 132 || (msg >= 140 && msg <= 144)) {
      room.waiting_for_player = client;
      room.last_active_time = moment();
    }
    if (ygopro.constants.MSG[msg] === 'START') {
      playertype = buffer.readUInt8(1);
      client.is_first = !(playertype & 0xf);
      client.lp = room.hostinfo.start_lp;
      if (room.hostinfo.mode !== 2) {
        client.card_count = 0;
      }
      room.duel_stage = ygopro.constants.DUEL_STAGE.DUELING;
      if (client.pos === 0) {
        room.turn = 0;
        room.duel_count++;
        if (room.death && room.duel_count > 1) {
          if (room.death === -1) {
            ygopro.stoc_send_chat_to_room(room, "${death_start_final}", ygopro.constants.COLORS.BABYBLUE);
          } else {
            ygopro.stoc_send_chat_to_room(room, "${death_start_extra}", ygopro.constants.COLORS.BABYBLUE);
          }
        }
      }
      if (settings.modules.retry_handle.enabled) {
        client.retry_count = 0;
        client.last_game_msg = null;
      }
    }
    if (ygopro.constants.MSG[msg] === 'HINT') {
      hint_type = buffer.readUInt8(1);
      if (hint_type === 3) {
        client.last_hint_msg = buffer;
      }
    }
    if (ygopro.constants.MSG[msg] === 'NEW_TURN') {
      if (client.pos === 0) {
        room.turn++;
        if (room.death && room.death !== -2) {
          if (room.turn >= room.death) {
            oppo_pos = room.hostinfo.mode === 2 ? 2 : 1;
            if (room.dueling_players[0].lp !== room.dueling_players[oppo_pos].lp && room.turn > 1) {
              win_pos = room.dueling_players[0].lp > room.dueling_players[oppo_pos].lp ? 0 : oppo_pos;
              ygopro.stoc_send_chat_to_room(room, "${death_finish_part1}" + room.dueling_players[win_pos].name + "${death_finish_part2}", ygopro.constants.COLORS.BABYBLUE);
              if (room.hostinfo.mode === 2) {
                room.finished_by_death = true;
                ygopro.stoc_send(room.dueling_players[oppo_pos - win_pos], 'DUEL_END');
                ygopro.stoc_send(room.dueling_players[oppo_pos - win_pos + 1], 'DUEL_END');
                room.scores[room.dueling_players[oppo_pos - win_pos].name_vpass] = -1;
                CLIENT_kick(room.dueling_players[oppo_pos - win_pos]);
                CLIENT_kick(room.dueling_players[oppo_pos - win_pos + 1]);
              } else {
                ygopro.ctos_send(room.dueling_players[oppo_pos - win_pos].server, 'SURRENDER');
              }
            } else {
              room.death = -1;
              ygopro.stoc_send_chat_to_room(room, "${death_remain_final}", ygopro.constants.COLORS.BABYBLUE);
            }
          } else {
            ygopro.stoc_send_chat_to_room(room, "${death_remain_part1}" + (room.death - room.turn) + "${death_remain_part2}", ygopro.constants.COLORS.BABYBLUE);
          }
        }
      }
      if (client.surrend_confirm) {
        client.surrend_confirm = false;
        ygopro.stoc_send_chat(client, "${surrender_canceled}", ygopro.constants.COLORS.BABYBLUE);
      }
    }
    if (ygopro.constants.MSG[msg] === 'NEW_PHASE') {
      phase = buffer.readInt16LE(1);
      oppo_pos = room.hostinfo.mode === 2 ? 2 : 1;
      if (client.pos === 0 && room.death === -2 && !(phase === 0x1 && room.turn < 2)) {
        if (room.dueling_players[0].lp !== room.dueling_players[oppo_pos].lp) {
          win_pos = room.dueling_players[0].lp > room.dueling_players[oppo_pos].lp ? 0 : oppo_pos;
          ygopro.stoc_send_chat_to_room(room, "${death_finish_part1}" + room.dueling_players[win_pos].name + "${death_finish_part2}", ygopro.constants.COLORS.BABYBLUE);
          if (room.hostinfo.mode === 2) {
            room.finished_by_death = true;
            ygopro.stoc_send(room.dueling_players[oppo_pos - win_pos], 'DUEL_END');
            ygopro.stoc_send(room.dueling_players[oppo_pos - win_pos + 1], 'DUEL_END');
            room.scores[room.dueling_players[oppo_pos - win_pos].name_vpass] = -1;
            CLIENT_kick(room.dueling_players[oppo_pos - win_pos]);
            CLIENT_kick(room.dueling_players[oppo_pos - win_pos + 1]);
          } else {
            ygopro.ctos_send(room.dueling_players[oppo_pos - win_pos].server, 'SURRENDER');
          }
        } else {
          room.death = -1;
          ygopro.stoc_send_chat_to_room(room, "${death_remain_final}", ygopro.constants.COLORS.BABYBLUE);
        }
      }
    }
    if (ygopro.constants.MSG[msg] === 'WIN' && client.pos === 0) {
      pos = buffer.readUInt8(1);
      if (!(client.is_first || pos === 2 || room.duel_stage !== ygopro.constants.DUEL_STAGE.DUELING)) {
        pos = 1 - pos;
      }
      if (pos >= 0 && room.hostinfo.mode === 2) {
        pos = pos * 2;
      }
      reason = buffer.readUInt8(2);
      room.winner = pos;
      room.turn = 0;
      room.duel_stage = ygopro.constants.DUEL_STAGE.END;
      if (settings.modules.heartbeat_detection.enabled) {
        ref2 = room.players;
        for (m = 0, len2 = ref2.length; m < len2; m++) {
          player = ref2[m];
          player.heartbeat_protected = false;
        }
        delete room.long_resolve_card;
        delete room.long_resolve_chain;
      }
      if (room && !room.finished && room.dueling_players[pos]) {
        room.winner_name = room.dueling_players[pos].name_vpass;
        room.scores[room.winner_name] = room.scores[room.winner_name] + 1;
        if (room.match_kill) {
          room.match_kill = false;
          room.scores[room.winner_name] = 99;
        }
      }
      if (room.death) {
        if (settings.modules.http.quick_death_rule === 1 || settings.modules.http.quick_death_rule === 3) {
          room.death = -1;
        } else {
          room.death = 5;
        }
      }
    }
    if (ygopro.constants.MSG[msg] === 'MATCH_KILL' && client.pos === 0) {
      room.match_kill = true;
    }
    if (ygopro.constants.MSG[msg] === 'DAMAGE' && client.pos === 0) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      if (pos >= 0 && room.hostinfo.mode === 2) {
        pos = pos * 2;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp -= val;
      if (room.dueling_players[pos].lp < 0) {
        room.dueling_players[pos].lp = 0;
      }
      if ((0 < (ref3 = room.dueling_players[pos].lp) && ref3 <= 100)) {
        ygopro.stoc_send_chat_to_room(room, "${lp_low_opponent}", ygopro.constants.COLORS.PINK);
      }
    }
    if (ygopro.constants.MSG[msg] === 'RECOVER' && client.pos === 0) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      if (pos >= 0 && room.hostinfo.mode === 2) {
        pos = pos * 2;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp += val;
    }
    if (ygopro.constants.MSG[msg] === 'LPUPDATE' && client.pos === 0) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      if (pos >= 0 && room.hostinfo.mode === 2) {
        pos = pos * 2;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp = val;
    }
    if (ygopro.constants.MSG[msg] === 'PAY_LPCOST' && client.pos === 0) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      if (pos >= 0 && room.hostinfo.mode === 2) {
        pos = pos * 2;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp -= val;
      if (room.dueling_players[pos].lp < 0) {
        room.dueling_players[pos].lp = 0;
      }
      if ((0 < (ref4 = room.dueling_players[pos].lp) && ref4 <= 100)) {
        ygopro.stoc_send_chat_to_room(room, "${lp_low_self}", ygopro.constants.COLORS.PINK);
      }
    }
    if (ygopro.constants.MSG[msg] === 'MOVE' && room.hostinfo.mode !== 2) {
      pos = buffer.readUInt8(5);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      loc = buffer.readUInt8(6);
      if ((loc & 0xe) && pos === 0) {
        client.card_count--;
      }
      pos = buffer.readUInt8(9);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      loc = buffer.readUInt8(10);
      if ((loc & 0xe) && pos === 0) {
        client.card_count++;
      }
    }
    if (ygopro.constants.MSG[msg] === 'DRAW' && room.hostinfo.mode !== 2) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      if (pos === 0) {
        count = buffer.readInt8(2);
        client.card_count += count;
      }
    }
    if (settings.modules.heartbeat_detection.enabled && ygopro.constants.MSG[msg] === 'CONFIRM_CARDS') {
      check = false;
      count = buffer.readInt8(2);
      max_loop = 3 + (count - 1) * 7;
      deck_found = 0;
      limbo_found = 0;
      for (i = n = 3, ref5 = max_loop; n <= ref5; i = n += 7) {
        loc = buffer.readInt8(i + 5);
        if ((loc & 0x41) > 0) {
          deck_found++;
        } else if (loc === 0) {
          limbo_found++;
        }
        if ((deck_found > 0 && count > 1) || limbo_found > 0) {
          check = true;
          break;
        }
      }
      if (check) {
        client.heartbeat_protected = true;
      }
    }
    if (settings.modules.heartbeat_detection.enabled && client.pos === 0) {
      if (ygopro.constants.MSG[msg] === 'CHAINING') {
        card = buffer.readUInt32LE(1);
        found = false;
        for (o = 0, len3 = long_resolve_cards.length; o < len3; o++) {
          id = long_resolve_cards[o];
          if (!(id === card)) {
            continue;
          }
          found = true;
          break;
        }
        if (found) {
          room.long_resolve_card = card;
        } else {
          delete room.long_resolve_card;
        }
      } else if (ygopro.constants.MSG[msg] === 'CHAINED' && room.long_resolve_card) {
        chain = buffer.readInt8(1);
        if (!room.long_resolve_chain) {
          room.long_resolve_chain = [];
        }
        room.long_resolve_chain[chain] = true;
        delete room.long_resolve_card;
      } else if (ygopro.constants.MSG[msg] === 'CHAIN_SOLVING' && room.long_resolve_chain) {
        chain = buffer.readInt8(1);
        if (room.long_resolve_chain[chain]) {
          ref6 = room.get_playing_player();
          for (p = 0, len4 = ref6.length; p < len4; p++) {
            player = ref6[p];
            player.heartbeat_protected = true;
          }
        }
      } else if ((ygopro.constants.MSG[msg] === 'CHAIN_NEGATED' || ygopro.constants.MSG[msg] === 'CHAIN_DISABLED') && room.long_resolve_chain) {
        chain = buffer.readInt8(1);
        delete room.long_resolve_chain[chain];
      } else if (ygopro.constants.MSG[msg] === 'CHAIN_END') {
        delete room.long_resolve_card;
        delete room.long_resolve_chain;
      }
    }
    if (settings.modules.dialogues.enabled) {
      if (ygopro.constants.MSG[msg] === 'SUMMONING' || ygopro.constants.MSG[msg] === 'SPSUMMONING' || ygopro.constants.MSG[msg] === 'CHAINING') {
        card = buffer.readUInt32LE(1);
        trigger_location = buffer.readUInt8(6);
        if (dialogues.dialogues[card] && (ygopro.constants.MSG[msg] !== 'CHAINING' || (trigger_location & 0x8) && client.ready_trap)) {
          ref7 = _.lines(dialogues.dialogues[card][Math.floor(Math.random() * dialogues.dialogues[card].length)]);
          for (q = 0, len5 = ref7.length; q < len5; q++) {
            line = ref7[q];
            ygopro.stoc_send_chat(client, line, ygopro.constants.COLORS.PINK);
          }
        }
      }
      if (ygopro.constants.MSG[msg] === 'POS_CHANGE') {
        loc = buffer.readUInt8(6);
        ppos = buffer.readUInt8(8);
        cpos = buffer.readUInt8(9);
        client.ready_trap = !!(loc & 0x8) && !!(ppos & 0xa) && !!(cpos & 0x5);
      } else if (ygopro.constants.MSG[msg] !== 'UPDATE_CARD' && ygopro.constants.MSG[msg] !== 'WAITING') {
        client.ready_trap = false;
      }
    }
    return false;
  });

  ygopro.ctos_follow('HS_TOOBSERVER', true, function(buffer, info, client, server, datas) {
    var len2, m, player, ref2, room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    if (room.hostinfo.no_watch) {
      ygopro.stoc_send_chat(client, "${watch_denied_room}", ygopro.constants.COLORS.RED);
      return true;
    }
    if ((!room.arena && !settings.modules.challonge.enabled) || client.is_local) {
      return false;
    }
    ref2 = room.players;
    for (m = 0, len2 = ref2.length; m < len2; m++) {
      player = ref2[m];
      if (player === client) {
        ygopro.stoc_send_chat(client, "${cannot_to_observer}", ygopro.constants.COLORS.BABYBLUE);
        return true;
      }
    }
    return false;
  });

  ygopro.ctos_follow('HS_KICK', true, function(buffer, info, client, server, datas) {
    var len2, m, player, ref2, room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    ref2 = room.players;
    for (m = 0, len2 = ref2.length; m < len2; m++) {
      player = ref2[m];
      if (player && player.pos === info.pos && player !== client) {
        if (room.arena === "athletic" || settings.modules.challonge.enabled) {
          ygopro.stoc_send_chat_to_room(room, client.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
          CLIENT_kick(client);
          return true;
        }
        client.kick_count = client.kick_count ? client.kick_count + 1 : 1;
        if (client.kick_count >= 5 && room.random_type) {
          ygopro.stoc_send_chat_to_room(room, client.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
          ROOM_ban_player(player.name, player.ip, "${random_ban_reason_zombie}");
          CLIENT_kick(client);
          return true;
        }
        ygopro.stoc_send_chat_to_room(room, player.name + " ${kicked_by_player}", ygopro.constants.COLORS.RED);
      }
    }
    return false;
  });

  ygopro.stoc_follow('TYPE_CHANGE', true, function(buffer, info, client, server, datas) {
    var is_host, selftype;
    selftype = info.type & 0xf;
    is_host = ((info.type >> 4) & 0xf) !== 0;
    client.is_host = is_host;
    client.pos = selftype;
    return false;
  });

  ygopro.stoc_follow('HS_PLAYER_ENTER', true, function(buffer, info, client, server, datas) {
    var pos, room, struct;
    room = ROOM_all[client.rid];
    if (!(room && settings.modules.hide_name && room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN)) {
      return false;
    }
    pos = info.pos;
    if (pos < 4 && pos !== client.pos) {
      struct = ygopro.structs["STOC_HS_PlayerEnter"];
      struct._setBuff(buffer);
      struct.set("name", "********");
      buffer = struct.buffer;
    }
    return false;
  });

  ygopro.stoc_follow('HS_PLAYER_CHANGE', false, function(buffer, info, client, server, datas) {
    var is_ready, len2, len3, m, n, p1, p2, player, pos, ref2, ref3, room;
    room = ROOM_all[client.rid];
    if (!(room && room.max_player && client.is_host)) {
      return;
    }
    pos = info.status >> 4;
    is_ready = (info.status & 0xf) === 9;
    if (pos < room.max_player) {
      if (room.arena) {
        room.ready_player_count = 0;
        ref2 = room.players;
        for (m = 0, len2 = ref2.length; m < len2; m++) {
          player = ref2[m];
          if (player.pos === pos) {
            player.is_ready = is_ready;
          }
        }
        p1 = room.players[0];
        p2 = room.players[1];
        if (!p1 || !p2) {
          if (room.waiting_for_player_interval) {
            clearInterval(room.waiting_for_player_interval);
            room.waiting_for_player_interval = null;
          }
          return;
        }
        room.waiting_for_player2 = room.waiting_for_player;
        room.waiting_for_player = null;
        if (p1.is_ready && p2.is_ready) {
          room.waiting_for_player = p1.is_host ? p1 : p2;
        }
        if (!p1.is_ready && p2.is_ready) {
          room.waiting_for_player = p1;
        }
        if (!p2.is_ready && p1.is_ready) {
          room.waiting_for_player = p2;
        }
        if (room.waiting_for_player !== room.waiting_for_player2) {
          room.waiting_for_player2 = room.waiting_for_player;
          room.waiting_for_player_time = settings.modules.arena_mode.ready_time;
          room.waiting_for_player_interval = setInterval((function() {
            wait_room_start_arena(ROOM_all[client.rid]);
          }), 1000);
        } else if (!room.waiting_for_player && room.waiting_for_player_interval) {
          clearInterval(room.waiting_for_player_interval);
          room.waiting_for_player_interval = null;
          room.waiting_for_player_time = settings.modules.arena_mode.ready_time;
        }
      } else {
        room.ready_player_count_without_host = 0;
        ref3 = room.players;
        for (n = 0, len3 = ref3.length; n < len3; n++) {
          player = ref3[n];
          if (player.pos === pos) {
            player.is_ready = is_ready;
          }
          if (!player.is_host) {
            room.ready_player_count_without_host += player.is_ready;
          }
        }
        if (room.ready_player_count_without_host >= room.max_player - 1) {
          setTimeout((function() {
            wait_room_start(ROOM_all[client.rid], settings.modules.random_duel.ready_time);
          }), 1000);
        }
      }
    }
  });

  ygopro.ctos_follow('REQUEST_FIELD', true, function(buffer, info, client, server, datas) {
    return true;
  });

  ygopro.stoc_follow('FIELD_FINISH', true, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!(room && settings.modules.reconnect.enabled)) {
      return true;
    }
    client.reconnecting = false;
    if (client.time_confirm_required) {
      client.waiting_for_last = true;
    } else if (client.last_game_msg && client.last_game_msg_title !== 'WAITING') {
      if (client.last_hint_msg) {
        ygopro.stoc_send(client, 'GAME_MSG', client.last_hint_msg);
      }
      ygopro.stoc_send(client, 'GAME_MSG', client.last_game_msg);
    }
    return true;
  });

  ygopro.stoc_follow('DUEL_END', false, function(buffer, info, client, server, datas) {
    var len2, len3, m, n, player, ref2, ref3, results, room;
    room = ROOM_all[client.rid];
    if (!(room && settings.modules.replay_delay && room.hostinfo.mode === 1)) {
      return;
    }
    SOCKET_flush_data(client, datas);
    CLIENT_send_replays(client, room);
    if (!room.replays_sent_to_watchers) {
      room.replays_sent_to_watchers = true;
      ref2 = room.players;
      for (m = 0, len2 = ref2.length; m < len2; m++) {
        player = ref2[m];
        if (player && player.pos > 3) {
          CLIENT_send_replays(player, room);
        }
      }
      ref3 = room.watchers;
      results = [];
      for (n = 0, len3 = ref3.length; n < len3; n++) {
        player = ref3[n];
        if (player) {
          results.push(CLIENT_send_replays(player, room));
        }
      }
      return results;
    }
  });

  wait_room_start = function(room, time) {
    var len2, m, player, ref2;
    if (room && room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && room.ready_player_count_without_host >= room.max_player - 1) {
      time -= 1;
      if (time) {
        if (!(time % 5)) {
          ygopro.stoc_send_chat_to_room(room, "" + (time <= 9 ? ' ' : '') + time + "${kick_count_down}", time <= 9 ? ygopro.constants.COLORS.RED : ygopro.constants.COLORS.LIGHTBLUE);
        }
        setTimeout((function() {
          wait_room_start(room, time);
        }), 1000);
      } else {
        ref2 = room.players;
        for (m = 0, len2 = ref2.length; m < len2; m++) {
          player = ref2[m];
          if (player && player.is_host) {
            ROOM_ban_player(player.name, player.ip, "${random_ban_reason_zombie}");
            ygopro.stoc_send_chat_to_room(room, player.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
            CLIENT_kick(player);
          }
        }
      }
    }
  };

  wait_room_start_arena = function(room) {
    var display_name, len2, m, player, ref2;
    if (room && room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && room.waiting_for_player) {
      room.waiting_for_player_time = room.waiting_for_player_time - 1;
      if (room.waiting_for_player_time > 0) {
        if (!(room.waiting_for_player_time % 5)) {
          ref2 = room.players;
          for (m = 0, len2 = ref2.length; m < len2; m++) {
            player = ref2[m];
            if (!(player)) {
              continue;
            }
            display_name = (settings.modules.hide_name && player !== room.waiting_for_player ? "********" : room.waiting_for_player.name);
            ygopro.stoc_send_chat(player, "" + (room.waiting_for_player_time <= 9 ? ' ' : '') + room.waiting_for_player_time + "${kick_count_down_arena_part1} " + display_name + " ${kick_count_down_arena_part2}", room.waiting_for_player_time <= 9 ? ygopro.constants.COLORS.RED : ygopro.constants.COLORS.LIGHTBLUE);
          }
        }
      } else {
        ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
        CLIENT_kick(room.waiting_for_player);
        if (room.waiting_for_player_interval) {
          clearInterval(room.waiting_for_player_interval);
          room.waiting_for_player_interval = null;
        }
      }
    }
  };

  ygopro.stoc_send_random_tip = function(client) {
    if (settings.modules.tips.enabled && tips.tips.length) {
      ygopro.stoc_send_chat(client, "Tip: " + tips.tips[Math.floor(Math.random() * tips.tips.length)]);
    }
  };

  ygopro.stoc_send_random_tip_to_room = function(room) {
    if (settings.modules.tips.enabled && tips.tips.length) {
      ygopro.stoc_send_chat_to_room(room, "Tip: " + tips.tips[Math.floor(Math.random() * tips.tips.length)]);
    }
  };

  load_tips = global.load_tips = function() {
    request({
      url: settings.modules.tips.get,
      json: true
    }, function(error, response, body) {
      if (_.isString(body)) {
        log.warn("tips bad json", body);
      } else if (error || !body) {
        log.warn('tips error', error, response);
      } else {
        setting_change(tips, "tips", body);
        log.info("tips loaded", tips.tips.length);
      }
    });
  };

  if (settings.modules.tips.get) {
    load_tips();
    setInterval(function() {
      var len2, m, room;
      for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
        room = ROOM_all[m];
        if (room && room.established) {
          if (room.duel_stage === ygopro.constants.DUEL_STAGE.SIDING || room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN) {
            ygopro.stoc_send_random_tip_to_room(room);
          }
        }
      }
    }, 30000);
  }

  ygopro.stoc_follow('DUEL_START', false, function(buffer, info, client, server, datas) {
    var deck_arena, deck_name, deck_text, len2, len3, m, n, player, ref2, ref3, room;
    room = ROOM_all[client.rid];
    if (!(room && !client.reconnecting)) {
      return;
    }
    if (room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN) {
      room.duel_stage = ygopro.constants.DUEL_STAGE.FINGER;
      room.start_time = moment().format();
      room.turn = 0;
      if (!room.windbot && settings.modules.http.websocket_roomlist) {
        roomlist.start(room);
      }
      room.dueling_players = [];
      ref2 = room.players;
      for (m = 0, len2 = ref2.length; m < len2; m++) {
        player = ref2[m];
        if (!(player.pos !== 7)) {
          continue;
        }
        room.dueling_players[player.pos] = player;
        room.scores[player.name_vpass] = 0;
        room.player_datas.push({
          key: CLIENT_get_authorize_key(player),
          name: player.name
        });
        if (room.random_type === 'T') {
          ROOM_players_oppentlist[player.ip] = null;
        }
      }
      if (room.hostinfo.auto_death) {
        ygopro.stoc_send_chat_to_room(room, "${auto_death_part1}" + room.hostinfo.auto_death + "${auto_death_part2}", ygopro.constants.COLORS.BABYBLUE);
      }
    }
    if (settings.modules.hide_name && room.duel_count === 0) {
      ref3 = room.get_playing_player();
      for (n = 0, len3 = ref3.length; n < len3; n++) {
        player = ref3[n];
        if (player !== client) {
          ygopro.stoc_send(client, 'HS_PLAYER_ENTER', {
            name: player.name,
            pos: player.pos
          });
        }
      }
    }
    if (settings.modules.tips.enabled) {
      ygopro.stoc_send_random_tip(client);
    }
    deck_text = null;
    if (client.main && client.main.length) {
      deck_text = '#ygopro-server deck log\n#main\n' + client.main.join('\n') + '\n!side\n' + client.side.join('\n') + '\n';
      room.decks[client.name] = deck_text;
    }
    if (settings.modules.deck_log.enabled && deck_text && !client.deck_saved && !room.windbot) {
      deck_arena = settings.modules.deck_log.arena + '-';
      if (room.arena) {
        deck_arena = deck_arena + room.arena;
      } else if (room.hostinfo.mode === 2) {
        deck_arena = deck_arena + 'tag';
      } else if (room.random_type === 'S') {
        deck_arena = deck_arena + 'entertain';
      } else if (room.random_type === 'M') {
        deck_arena = deck_arena + 'athletic';
      } else {
        deck_arena = deck_arena + 'custom';
      }
      if (settings.modules.deck_log.local) {
        deck_name = moment().format('YYYY-MM-DD HH-mm-ss') + ' ' + room.process_pid + ' ' + client.pos + ' ' + client.ip.slice(7) + ' ' + client.name.replace(/[\/\\\?\*]/g, '_');
        fs.writeFile(settings.modules.deck_log.local + deck_name + '.ydk', deck_text, 'utf-8', function(err) {
          if (err) {
            return log.warn('DECK SAVE ERROR', err);
          }
        });
      }
      if (settings.modules.deck_log.post) {
        request.post({
          url: settings.modules.deck_log.post,
          form: {
            accesskey: settings.modules.deck_log.accesskey,
            deck: deck_text,
            playername: client.name,
            arena: deck_arena
          }
        }, function(error, response, body) {
          if (error) {
            log.warn('DECK POST ERROR', error);
          } else {
            if (response.statusCode !== 200) {
              log.warn('DECK POST FAIL', response.statusCode, client.name, body);
            }
          }
        });
      }
      client.deck_saved = true;
    }
  });

  ygopro.ctos_follow('SURRENDER', true, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    if (room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN || room.hostinfo.mode === 2) {
      return true;
    }
    if (room.random_type && room.turn < 3 && !client.flee_free && !settings.modules.test_mode.surrender_anytime && !(room.random_type === 'M' && settings.modules.random_duel.record_match_scores)) {
      ygopro.stoc_send_chat(client, "${surrender_denied}", ygopro.constants.COLORS.BABYBLUE);
      return true;
    }
    return false;
  });

  report_to_big_brother = global.report_to_big_brother = function(roomname, sender, ip, level, content, match) {
    if (!settings.modules.big_brother.enabled) {
      return;
    }
    request.post({
      url: settings.modules.big_brother.post,
      form: {
        accesskey: settings.modules.big_brother.accesskey,
        roomname: roomname,
        sender: sender,
        ip: ip,
        level: level,
        content: content,
        match: match
      }
    }, function(error, response, body) {
      if (error) {
        log.warn('BIG BROTHER ERROR', error);
      } else {
        if (response.statusCode !== 200) {
          log.warn('BIG BROTHER FAIL', response.statusCode, roomname, body);
        }
      }
    });
  };

  ygopro.ctos_follow('CHAT', true, function(buffer, info, client, server, datas) {
    var cancel, ccolor, cip, cmd, cmsg, cname, color, cvalue, msg, name, oldmsg, ref2, room, struct, windbot;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    msg = _.trim(info.msg);
    cancel = _.startsWith(msg, "/");
    if (!(cancel || !(room.random_type || room.arena))) {
      room.last_active_time = moment();
    }
    cmd = msg.split(' ');
    switch (cmd[0]) {
      case '/投降':
      case '/surrender':
        if (room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN || room.hostinfo.mode === 2) {
          return cancel;
        }
        if (room.random_type && room.turn < 3) {
          ygopro.stoc_send_chat(client, "${surrender_denied}", ygopro.constants.COLORS.BABYBLUE);
          return cancel;
        }
        if (client.surrend_confirm) {
          ygopro.ctos_send(client.server, 'SURRENDER');
        } else {
          ygopro.stoc_send_chat(client, "${surrender_confirm}", ygopro.constants.COLORS.BABYBLUE);
          client.surrend_confirm = true;
        }
        break;
      case '/help':
        ygopro.stoc_send_chat(client, "${chat_order_main}");
        ygopro.stoc_send_chat(client, "${chat_order_help}");
        if (!settings.modules.mycard.enabled) {
          ygopro.stoc_send_chat(client, "${chat_order_roomname}");
        }
        if (settings.modules.windbot.enabled) {
          ygopro.stoc_send_chat(client, "${chat_order_windbot}");
        }
        if (settings.modules.tips.enabled) {
          ygopro.stoc_send_chat(client, "${chat_order_tip}");
        }
        if (settings.modules.chat_color.enabled) {
          ygopro.stoc_send_chat(client, "${chat_order_chatcolor_1}");
        }
        if (settings.modules.chat_color.enabled) {
          ygopro.stoc_send_chat(client, "${chat_order_chatcolor_2}");
        }
        break;
      case '/tip':
        if (settings.modules.tips.enabled) {
          ygopro.stoc_send_random_tip(client);
        }
        break;
      case '/ai':
        if (settings.modules.windbot.enabled && client.is_host && !settings.modules.challonge.enabled && !room.arena && room.random_type !== 'M') {
          if (name = cmd[1]) {
            windbot = _.sample(_.filter(windbots, function(w) {
              return w.name === name || w.deck === name;
            }));
            if (!windbot) {
              ygopro.stoc_send_chat(client, "${windbot_deck_not_found}", ygopro.constants.COLORS.RED);
              return;
            }
          } else {
            windbot = _.sample(windbots);
          }
          if (room.random_type) {
            ygopro.stoc_send_chat(client, "${windbot_disable_random_room} " + room.name, ygopro.constants.COLORS.BABYBLUE);
          }
          room.add_windbot(windbot);
        }
        break;
      case '/roomname':
        if (room) {
          ygopro.stoc_send_chat(client, "${room_name} " + room.name, ygopro.constants.COLORS.BABYBLUE);
        }
        break;
      case '/color':
        if (settings.modules.chat_color.enabled) {
          cip = CLIENT_get_authorize_key(client);
          if (cmsg = cmd[1]) {
            if (cmsg.toLowerCase() === "help") {
              ygopro.stoc_send_chat(client, "${show_color_list}", ygopro.constants.COLORS.BABYBLUE);
              ref2 = ygopro.constants.COLORS;
              for (cname in ref2) {
                cvalue = ref2[cname];
                if (cvalue > 10) {
                  ygopro.stoc_send_chat(client, cname, cvalue);
                }
              }
            } else if (cmsg.toLowerCase() === "default") {
              chat_color.save_list[cip] = false;
              setting_save(chat_color);
              ygopro.stoc_send_chat(client, "${set_chat_color_default}", ygopro.constants.COLORS.BABYBLUE);
            } else {
              ccolor = cmsg.toUpperCase();
              if (ygopro.constants.COLORS[ccolor] && ygopro.constants.COLORS[ccolor] > 10 && ygopro.constants.COLORS[ccolor] < 20) {
                chat_color.save_list[cip] = ccolor;
                setting_save(chat_color);
                ygopro.stoc_send_chat(client, "${set_chat_color_part1}" + ccolor + "${set_chat_color_part2}", ygopro.constants.COLORS.BABYBLUE);
              } else {
                ygopro.stoc_send_chat(client, "${color_not_found_part1}" + ccolor + "${color_not_found_part2}", ygopro.constants.COLORS.RED);
              }
            }
          } else {
            if (color = chat_color.save_list[cip]) {
              ygopro.stoc_send_chat(client, "${get_chat_color_part1}" + color + "${get_chat_color_part2}", ygopro.constants.COLORS.BABYBLUE);
            } else {
              ygopro.stoc_send_chat(client, "${get_chat_color_default}", ygopro.constants.COLORS.BABYBLUE);
            }
          }
        }
    }
    if (msg.length > 100) {
      log.warn("SPAM WORD", client.name, client.ip, msg);
      if (client.abuse_count) {
        client.abuse_count = client.abuse_count + 2;
      }
      ygopro.stoc_send_chat(client, "${chat_warn_level0}", ygopro.constants.COLORS.RED);
      cancel = true;
    }
    if (!(room && (room.random_type || room.arena))) {
      return cancel;
    }
    if (client.abuse_count >= 5 || CLIENT_is_banned_by_mc(client)) {
      log.warn("BANNED CHAT", client.name, client.ip, msg);
      ygopro.stoc_send_chat(client, "${banned_chat_tip}" + (client.ban_mc && client.ban_mc.message ? ": " + client.ban_mc.message : ""), ygopro.constants.COLORS.RED);
      return true;
    }
    oldmsg = msg;
    if (_.any(badwords.level3, function(badword) {
      var regexp;
      regexp = new RegExp(badword, 'i');
      return msg.match(regexp);
    }, msg)) {
      log.warn("BAD WORD LEVEL 3", client.name, client.ip, oldmsg, RegExp.$1);
      report_to_big_brother(room.name, client.name, client.ip, 3, oldmsg, RegExp.$1);
      cancel = true;
      if (client.abuse_count > 0) {
        ygopro.stoc_send_chat(client, "${banned_duel_tip}", ygopro.constants.COLORS.RED);
        ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}");
        ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}", 3);
        CLIENT_send_replays(client, room);
        CLIENT_kick(client);
        return true;
      } else {
        client.abuse_count = client.abuse_count + 4;
        ygopro.stoc_send_chat(client, "${chat_warn_level2}", ygopro.constants.COLORS.RED);
      }
    } else if (client.rag && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
      client.rag = false;
      cancel = true;
    } else if (_.any(settings.ban.spam_word, function(badword) {
      var regexp;
      regexp = new RegExp(badword, 'i');
      return msg.match(regexp);
    }, msg)) {
      client.abuse_count = client.abuse_count + 2;
      ygopro.stoc_send_chat(client, "${chat_warn_level0}", ygopro.constants.COLORS.RED);
      cancel = true;
    } else if (_.any(badwords.level2, function(badword) {
      var regexp;
      regexp = new RegExp(badword, 'i');
      return msg.match(regexp);
    }, msg)) {
      log.warn("BAD WORD LEVEL 2", client.name, client.ip, oldmsg, RegExp.$1);
      report_to_big_brother(room.name, client.name, client.ip, 2, oldmsg, RegExp.$1);
      client.abuse_count = client.abuse_count + 3;
      ygopro.stoc_send_chat(client, "${chat_warn_level2}", ygopro.constants.COLORS.RED);
      cancel = true;
    } else {
      _.each(badwords.level1, function(badword) {
        var regexp;
        regexp = new RegExp(badword, "ig");
        msg = msg.replace(regexp, "**");
      }, msg);
      if (oldmsg !== msg) {
        log.warn("BAD WORD LEVEL 1", client.name, client.ip, oldmsg, RegExp.$1);
        report_to_big_brother(room.name, client.name, client.ip, 1, oldmsg, RegExp.$1);
        client.abuse_count = client.abuse_count + 1;
        ygopro.stoc_send_chat(client, "${chat_warn_level1}");
        struct = ygopro.structs["chat"];
        struct._setBuff(buffer);
        struct.set("msg", msg);
        buffer = struct.buffer;
      } else if (_.any(badwords.level0, function(badword) {
        var regexp;
        regexp = new RegExp(badword, 'i');
        return msg.match(regexp);
      }, msg)) {
        log.info("BAD WORD LEVEL 0", client.name, client.ip, oldmsg, RegExp.$1);
        report_to_big_brother(room.name, client.name, client.ip, 0, oldmsg, RegExp.$1);
      }
    }
    if (client.abuse_count >= 2) {
      ROOM_unwelcome(room, client, "${random_ban_reason_abuse}");
    }
    if (client.abuse_count >= 5) {
      ygopro.stoc_send_chat_to_room(room, client.name + " ${chat_banned}", ygopro.constants.COLORS.RED);
      ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}");
    }
    return cancel;
  });

  ygopro.ctos_follow('UPDATE_DECK', true, function(buffer, info, client, server, datas) {
    var buff_main, buff_side, card, code, current_deck, deck, deck_array, deck_main, deck_side, deck_text, deckbuf, decks, found_deck, i, len2, len3, len4, line, m, n, o, oppo_pos, ref2, room, struct, win_pos;
    if (settings.modules.reconnect.enabled && client.pre_reconnecting) {
      if (!CLIENT_is_able_to_reconnect(client) && !CLIENT_is_able_to_kick_reconnect(client)) {
        ygopro.stoc_send_chat(client, "${reconnect_failed}", ygopro.constants.COLORS.RED);
        CLIENT_kick(client);
      } else if (CLIENT_is_able_to_reconnect(client, buffer)) {
        CLIENT_reconnect(client);
      } else if (CLIENT_is_able_to_kick_reconnect(client, buffer)) {
        CLIENT_kick_reconnect(client, buffer);
      } else {
        ygopro.stoc_send_chat(client, "${deck_incorrect_reconnect}", ygopro.constants.COLORS.RED);
        ygopro.stoc_send(client, 'ERROR_MSG', {
          msg: 2,
          code: 0
        });
        ygopro.stoc_send(client, 'HS_PLAYER_CHANGE', {
          status: (client.pos << 4) | 0xa
        });
      }
      return true;
    }
    room = ROOM_all[client.rid];
    if (!room) {
      return false;
    }
    if (info.mainc > 256 || info.sidec > 256) {
      CLIENT_kick(client);
      return true;
    }
    buff_main = (function() {
      var m, ref2, results;
      results = [];
      for (i = m = 0, ref2 = info.mainc; 0 <= ref2 ? m < ref2 : m > ref2; i = 0 <= ref2 ? ++m : --m) {
        results.push(info.deckbuf[i]);
      }
      return results;
    })();
    buff_side = (function() {
      var m, ref2, ref3, results;
      results = [];
      for (i = m = ref2 = info.mainc, ref3 = info.mainc + info.sidec; ref2 <= ref3 ? m < ref3 : m > ref3; i = ref2 <= ref3 ? ++m : --m) {
        results.push(info.deckbuf[i]);
      }
      return results;
    })();
    client.main = buff_main;
    client.side = buff_side;
    if (room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
      client.selected_preduel = true;
      if (client.side_tcount) {
        clearInterval(client.side_interval);
        client.side_interval = null;
        client.side_tcount = null;
      }
    } else {
      client.start_deckbuf = Buffer.from(buffer);
    }
    oppo_pos = room.hostinfo.mode === 2 ? 2 : 1;
    if (settings.modules.http.quick_death_rule >= 2 && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && room.death && room.scores[room.dueling_players[0].name_vpass] !== room.scores[room.dueling_players[oppo_pos].name_vpass]) {
      win_pos = room.scores[room.dueling_players[0].name_vpass] > room.scores[room.dueling_players[oppo_pos].name_vpass] ? 0 : oppo_pos;
      room.finished_by_death = true;
      ygopro.stoc_send_chat_to_room(room, "${death2_finish_part1}" + room.dueling_players[win_pos].name + "${death2_finish_part2}", ygopro.constants.COLORS.BABYBLUE);
      if (room.hostinfo.mode === 1) {
        CLIENT_send_replays(room.dueling_players[oppo_pos - win_pos], room);
      }
      ygopro.stoc_send(room.dueling_players[oppo_pos - win_pos], 'DUEL_END');
      if (room.hostinfo.mode === 2) {
        ygopro.stoc_send(room.dueling_players[oppo_pos - win_pos + 1], 'DUEL_END');
      }
      room.scores[room.dueling_players[oppo_pos - win_pos].name_vpass] = -1;
      CLIENT_kick(room.dueling_players[oppo_pos - win_pos]);
      if (room.hostinfo.mode === 2) {
        CLIENT_kick(room.dueling_players[oppo_pos - win_pos + 1]);
      }
      return true;
    }
    if (settings.modules.side_restrict.enabled && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
      ref2 = settings.modules.side_restrict.restrict_cards;
      for (m = 0, len2 = ref2.length; m < len2; m++) {
        code = ref2[m];
        if (_.indexOf(buff_side, code) > -1) {
          ygopro.stoc_send_chat_to_room(room, "${invalid_side_rule}", ygopro.constants.COLORS.RED);
          ygopro.stoc_send(client, 'ERROR_MSG', {
            msg: 3,
            code: 0
          });
          return true;
        }
      }
    }
    if (room.random_type || room.arena) {
      if (client.pos === 0) {
        room.waiting_for_player = room.waiting_for_player2;
      }
      room.last_active_time = moment();
    } else if (room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && room.hostinfo.mode === 1 && settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.deck_check && fs.readdirSync(settings.modules.tournament_mode.deck_path).length) {
      struct = ygopro.structs["deck"];
      struct._setBuff(buffer);
      struct.set("mainc", 1);
      struct.set("sidec", 1);
      struct.set("deckbuf", [4392470, 4392470]);
      buffer = struct.buffer;
      found_deck = false;
      decks = fs.readdirSync(settings.modules.tournament_mode.deck_path);
      for (n = 0, len3 = decks.length; n < len3; n++) {
        deck = decks[n];
        if (_.endsWith(deck, client.name + ".ydk")) {
          found_deck = deck;
        }
        if (_.endsWith(deck, client.name + ".ydk.ydk")) {
          found_deck = deck;
        }
      }
      if (found_deck) {
        deck_text = fs.readFileSync(settings.modules.tournament_mode.deck_path + found_deck, {
          encoding: "ASCII"
        });
        deck_array = deck_text.split("\n");
        deck_main = [];
        deck_side = [];
        current_deck = deck_main;
        for (o = 0, len4 = deck_array.length; o < len4; o++) {
          line = deck_array[o];
          if (line.indexOf("!side") >= 0) {
            current_deck = deck_side;
          }
          card = parseInt(line);
          if (!isNaN(card)) {
            current_deck.push(card);
          }
        }
        if (_.isEqual(buff_main, deck_main) && _.isEqual(buff_side, deck_side)) {
          deckbuf = deck_main.concat(deck_side);
          struct.set("mainc", deck_main.length);
          struct.set("sidec", deck_side.length);
          struct.set("deckbuf", deckbuf);
          buffer = struct.buffer;
          ygopro.stoc_send_chat(client, "${deck_correct_part1} " + found_deck + " ${deck_correct_part2}", ygopro.constants.COLORS.BABYBLUE);
        } else {
          ygopro.stoc_send_chat(client, "${deck_incorrect_part1} " + found_deck + " ${deck_incorrect_part2}", ygopro.constants.COLORS.RED);
        }
      } else {
        ygopro.stoc_send_chat(client, client.name + "${deck_not_found}", ygopro.constants.COLORS.RED);
      }
    }
    return false;
  });

  ygopro.ctos_follow('RESPONSE', false, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!(room && (room.random_type || room.arena))) {
      return;
    }
    room.last_active_time = moment();
  });

  ygopro.stoc_follow('TIME_LIMIT', true, function(buffer, info, client, server, datas) {
    var check, cur_players, room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    if (settings.modules.reconnect.enabled) {
      if (client.closed) {
        ygopro.ctos_send(server, 'TIME_CONFIRM');
        return true;
      } else {
        client.time_confirm_required = true;
      }
    }
    if (!(settings.modules.heartbeat_detection.enabled && room.duel_stage === ygopro.constants.DUEL_STAGE.DUELING && !room.windbot)) {
      return;
    }
    check = false;
    if (room.hostinfo.mode !== 2) {
      check = (client.is_first && info.player === 0) || (!client.is_first && info.player === 1);
    } else {
      cur_players = [];
      switch (room.turn % 4) {
        case 1:
          cur_players[0] = 0;
          cur_players[1] = 3;
          break;
        case 2:
          cur_players[0] = 0;
          cur_players[1] = 2;
          break;
        case 3:
          cur_players[0] = 1;
          cur_players[1] = 2;
          break;
        case 0:
          cur_players[0] = 1;
          cur_players[1] = 3;
      }
      if (!room.dueling_players[0].is_first) {
        cur_players[0] = cur_players[0] + 2;
        cur_players[1] = cur_players[1] - 2;
      }
      check = client.pos === cur_players[info.player];
    }
    if (check) {
      CLIENT_heartbeat_register(client, false);
    }
    return false;
  });

  ygopro.ctos_follow('TIME_CONFIRM', false, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    if (settings.modules.reconnect.enabled) {
      if (client.waiting_for_last) {
        client.waiting_for_last = false;
        if (client.last_game_msg && client.last_game_msg_title !== 'WAITING') {
          if (client.last_hint_msg) {
            ygopro.stoc_send(client, 'GAME_MSG', client.last_hint_msg);
          }
          ygopro.stoc_send(client, 'GAME_MSG', client.last_game_msg);
        }
      }
      client.time_confirm_required = false;
    }
    if (settings.modules.heartbeat_detection.enabled) {
      client.heartbeat_protected = false;
      client.heartbeat_responsed = true;
      CLIENT_heartbeat_unregister(client);
    }
  });

  ygopro.ctos_follow('HAND_RESULT', false, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    client.selected_preduel = true;
    if (!(room.random_type || room.arena)) {
      return;
    }
    if (client.pos === 0) {
      room.waiting_for_player = room.waiting_for_player2;
    }
    room.last_active_time = moment().subtract(settings.modules.random_duel.hang_timeout - 19, 's');
  });

  ygopro.ctos_follow('TP_RESULT', false, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    client.selected_preduel = true;
    if (!(room.random_type || room.arena)) {
      return;
    }
    room.last_active_time = moment();
  });

  ygopro.stoc_follow('CHAT', true, function(buffer, info, client, server, datas) {
    var len2, m, pid, player, ref2, room, tcolor, tplayer;
    room = ROOM_all[client.rid];
    pid = info.player;
    if (!(room && pid < 4 && settings.modules.chat_color.enabled && (!settings.modules.hide_name || room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN))) {
      return;
    }
    if (room.duel_stage === ygopro.constants.DUEL_STAGE.DUELING && !room.dueling_players[0].is_first) {
      if (room.hostinfo.mode === 2) {
        pid = {
          0: 2,
          1: 3,
          2: 0,
          3: 1
        }[pid];
      } else {
        pid = 1 - pid;
      }
    }
    ref2 = room.players;
    for (m = 0, len2 = ref2.length; m < len2; m++) {
      player = ref2[m];
      if (player && player.pos === pid) {
        tplayer = player;
      }
    }
    if (!tplayer) {
      return;
    }
    tcolor = chat_color.save_list[CLIENT_get_authorize_key(tplayer)];
    if (tcolor) {
      ygopro.stoc_send(client, 'CHAT', {
        player: ygopro.constants.COLORS[tcolor],
        msg: tplayer.name + ": " + info.msg
      });
      return true;
    }
  });

  ygopro.stoc_follow('SELECT_HAND', false, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    client.selected_preduel = false;
    if (client.pos === 0) {
      room.duel_stage = ygopro.constants.DUEL_STAGE.FINGER;
    }
    if (!(room.random_type || room.arena)) {
      return;
    }
    if (client.pos === 0) {
      room.waiting_for_player = client;
    } else {
      room.waiting_for_player2 = client;
    }
    room.last_active_time = moment().subtract(settings.modules.random_duel.hang_timeout - 19, 's');
  });

  ygopro.stoc_follow('SELECT_TP', false, function(buffer, info, client, server, datas) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    client.selected_preduel = false;
    room.duel_stage = ygopro.constants.DUEL_STAGE.FIRSTGO;
    room.selecting_tp = client;
    if (room.random_type || room.arena) {
      room.waiting_for_player = client;
      room.last_active_time = moment();
    }
  });

  ygopro.stoc_follow('CHANGE_SIDE', false, function(buffer, info, client, server, datas) {
    var room, room_name, sinterval, temp_log;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    if (client.pos === 0) {
      room.duel_stage = ygopro.constants.DUEL_STAGE.SIDING;
    }
    client.selected_preduel = false;
    if (settings.modules.side_timeout) {
      client.side_tcount = settings.modules.side_timeout;
      ygopro.stoc_send_chat(client, "${side_timeout_part1}" + settings.modules.side_timeout + "${side_timeout_part2}", ygopro.constants.COLORS.BABYBLUE);
      sinterval = setInterval(function() {
        if (!(room && client && client.side_tcount && room.duel_stage === ygopro.constants.DUEL_STAGE.SIDING)) {
          clearInterval(sinterval);
          return;
        }
        if (client.side_tcount === 1) {
          ygopro.stoc_send_chat_to_room(room, client.name + "${side_overtime_room}", ygopro.constants.COLORS.BABYBLUE);
          ygopro.stoc_send_chat(client, "${side_overtime}", ygopro.constants.COLORS.RED);
          CLIENT_send_replays(client, room);
          CLIENT_kick(client);
          return clearInterval(sinterval);
        } else {
          client.side_tcount = client.side_tcount - 1;
          return ygopro.stoc_send_chat(client, "${side_remain_part1}" + client.side_tcount + "${side_remain_part2}", ygopro.constants.COLORS.BABYBLUE);
        }
      }, 60000);
      client.side_interval = sinterval;
    }
    if (settings.modules.challonge.enabled && settings.modules.challonge.post_score_midduel && room.hostinfo.mode !== 2 && client.pos === 0) {
      temp_log = JSON.parse(JSON.stringify(room.get_challonge_score()));
      delete temp_log.winnerId;
      room_name = room.name;
      challonge.matches._update({
        id: settings.modules.challonge.tournament_id,
        matchId: room.challonge_info.id,
        match: temp_log,
        callback: function(err, data) {
          if (err) {
            log.warn("Errored pushing scores to Challonge.", room_name, err);
          } else {
            refresh_challonge_cache();
          }
        }
      });
    }
    if (room.random_type || room.arena) {
      if (client.pos === 0) {
        room.waiting_for_player = client;
      } else {
        room.waiting_for_player2 = client;
      }
      room.last_active_time = moment();
    }
  });

  ygopro.stoc_follow('REPLAY', true, function(buffer, info, client, server, datas) {
    var duellog, dueltime, i, len2, len3, m, n, player, ref2, ref3, replay_filename, room;
    room = ROOM_all[client.rid];
    if (!room) {
      return settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.replay_safe && settings.modules.tournament_mode.block_replay_to_player || settings.modules.replay_delay;
    }
    if (settings.modules.cloud_replay.enabled && room.random_type) {
      Cloud_replay_ids.push(room.cloud_replay_id);
    }
    if (!room.replays[room.duel_count - 1]) {
      room.replays[room.duel_count - 1] = buffer;
    }
    if (settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.replay_safe) {
      if (client.pos === 0) {
        dueltime = moment().format('YYYY-MM-DD HH-mm-ss');
        replay_filename = dueltime;
        if (room.hostinfo.mode !== 2) {
          ref2 = room.dueling_players;
          for (i = m = 0, len2 = ref2.length; m < len2; i = ++m) {
            player = ref2[i];
            replay_filename = replay_filename + (i > 0 ? " VS " : " ") + player.name;
          }
        } else {
          ref3 = room.dueling_players;
          for (i = n = 0, len3 = ref3.length; n < len3; i = ++n) {
            player = ref3[i];
            replay_filename = replay_filename + (i > 0 ? (i === 2 ? " VS " : " & ") : " ") + player.name;
          }
        }
        replay_filename = replay_filename.replace(/[\/\\\?\*]/g, '_') + ".yrp";
        duellog = {
          time: dueltime,
          name: room.name + (settings.modules.tournament_mode.show_info ? " (Duel:" + room.duel_count + ")" : ""),
          roomid: room.process_pid.toString(),
          cloud_replay_id: "R#" + room.cloud_replay_id,
          replay_filename: replay_filename,
          roommode: room.hostinfo.mode,
          players: (function() {
            var len4, o, ref4, results;
            ref4 = room.dueling_players;
            results = [];
            for (o = 0, len4 = ref4.length; o < len4; o++) {
              player = ref4[o];
              results.push({
                name: player.name + (settings.modules.tournament_mode.show_ip && !player.is_local ? " (IP: " + player.ip.slice(7) + ")" : "") + (settings.modules.tournament_mode.show_info && !(room.hostinfo.mode === 2 && player.pos % 2 > 0) ? " (Score:" + room.scores[player.name_vpass] + " LP:" + (player.lp != null ? player.lp : room.hostinfo.start_lp) + (room.hostinfo.mode !== 2 ? " Cards:" + (player.card_count != null ? player.card_count : room.hostinfo.start_hand) : "") + ")" : ""),
                winner: player.pos === room.winner
              });
            }
            return results;
          })()
        };
        duel_log.duel_log.unshift(duellog);
        setting_save(duel_log);
        fs.writeFile(settings.modules.tournament_mode.replay_path + replay_filename, buffer, function(err) {
          if (err) {
            return log.warn("SAVE REPLAY ERROR", replay_filename, err);
          }
        });
      }
      if (settings.modules.cloud_replay.enabled) {
        ygopro.stoc_send_chat(client, "${cloud_replay_delay_part1}R#" + room.cloud_replay_id + "${cloud_replay_delay_part2}", ygopro.constants.COLORS.BABYBLUE);
      }
      return settings.modules.tournament_mode.block_replay_to_player || settings.modules.replay_delay && room.hostinfo.mode === 1;
    } else {
      return settings.modules.replay_delay && room.hostinfo.mode === 1;
    }
  });

  if (settings.modules.random_duel.enabled) {
    setInterval(function() {
      var len2, m, room, time_passed;
      for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
        room = ROOM_all[m];
        if (!(room && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && room.random_type && room.last_active_time && room.waiting_for_player && room.get_disconnected_count() === 0)) {
          continue;
        }
        time_passed = Math.floor((moment() - room.last_active_time) / 1000);
        if (time_passed >= settings.modules.random_duel.hang_timeout) {
          room.last_active_time = moment();
          ROOM_ban_player(room.waiting_for_player.name, room.waiting_for_player.ip, "${random_ban_reason_AFK}");
          room.scores[room.waiting_for_player.name_vpass] = -9;
          ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
          CLIENT_send_replays(room.waiting_for_player, room);
          CLIENT_kick(room.waiting_for_player);
        } else if (time_passed >= (settings.modules.random_duel.hang_timeout - 20) && !(time_passed % 10)) {
          ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " ${afk_warn_part1}" + (settings.modules.random_duel.hang_timeout - time_passed) + "${afk_warn_part2}", ygopro.constants.COLORS.RED);
          ROOM_unwelcome(room, room.waiting_for_player, "${random_ban_reason_AFK}");
        }
      }
    }, 1000);
  }

  if (settings.modules.mycard.enabled) {
    setInterval(function() {
      var len2, len3, m, n, player, room, time_passed, waited_time;
      for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
        room = ROOM_all[m];
        if (!(room && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && room.arena && room.last_active_time && room.waiting_for_player && room.get_disconnected_count() === 0)) {
          continue;
        }
        time_passed = Math.floor((moment() - room.last_active_time) / 1000);
        if (time_passed >= settings.modules.random_duel.hang_timeout) {
          room.last_active_time = moment();
          ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
          room.scores[room.waiting_for_player.name_vpass] = -9;
          CLIENT_send_replays(room.waiting_for_player, room);
          CLIENT_kick(room.waiting_for_player);
        } else if (time_passed >= (settings.modules.random_duel.hang_timeout - 20) && !(time_passed % 10)) {
          ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " ${afk_warn_part1}" + (settings.modules.random_duel.hang_timeout - time_passed) + "${afk_warn_part2}", ygopro.constants.COLORS.RED);
        }
      }
      if (settings.modules.arena_mode.punish_quit_before_match) {
        for (n = 0, len3 = ROOM_all.length; n < len3; n++) {
          room = ROOM_all[n];
          if (!(room && room.arena && room.duel_stage === ygopro.constants.DUEL_STAGE.BEGIN && room.get_playing_player().length < 2)) {
            continue;
          }
          player = room.get_playing_player()[0];
          if (player && player.join_time && !player.arena_quit_free) {
            waited_time = moment() - player.join_time;
            if (waited_time >= 30000) {
              ygopro.stoc_send_chat(player, "${arena_wait_timeout}", ygopro.constants.COLORS.BABYBLUE);
              player.arena_quit_free = true;
            } else if (waited_time >= 5000 && waited_time < 6000) {
              ygopro.stoc_send_chat(player, "${arena_wait_hint}", ygopro.constants.COLORS.BABYBLUE);
            }
          }
        }
      }
    }, 1000);
  }

  if (settings.modules.heartbeat_detection.enabled) {
    setInterval(function() {
      var len2, len3, m, n, player, ref2, room;
      for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
        room = ROOM_all[m];
        if (room && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && (room.hostinfo.time_limit === 0 || room.duel_stage !== ygopro.constants.DUEL_STAGE.DUELING) && !room.windbot) {
          ref2 = room.get_playing_player();
          for (n = 0, len3 = ref2.length; n < len3; n++) {
            player = ref2[n];
            if (player && (room.duel_stage !== ygopro.constants.DUEL_STAGE.SIDING || player.selected_preduel)) {
              CLIENT_heartbeat_register(player, true);
            }
          }
        }
      }
    }, settings.modules.heartbeat_detection.interval);
  }

  setInterval(function() {
    var current_time, len2, m, results, room;
    current_time = moment();
    results = [];
    for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
      room = ROOM_all[m];
      if (!(room && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && room.hostinfo.auto_death && !room.auto_death_triggered && current_time - moment(room.start_time) > 60000 * room.hostinfo.auto_death)) {
        continue;
      }
      room.auto_death_triggered = true;
      results.push(room.start_death());
    }
    return results;
  }, 1000);

  windbot_looplimit = 0;

  windbot_process = global.windbot_process = null;

  spawn_windbot = global.spawn_windbot = function() {
    var windbot_bin, windbot_parameters;
    if (/^win/.test(process.platform)) {
      windbot_bin = 'WindBot.exe';
      windbot_parameters = [];
    } else {
      windbot_bin = 'mono';
      windbot_parameters = ['WindBot.exe'];
    }
    windbot_parameters.push('ServerMode=true');
    windbot_parameters.push('ServerPort=' + settings.modules.windbot.port);
    windbot_process = spawn(windbot_bin, windbot_parameters, {
      cwd: 'windbot'
    });
    windbot_process.on('error', function(err) {
      log.warn('WindBot ERROR', err);
      if (windbot_looplimit < 1000 && !rebooted) {
        windbot_looplimit++;
        spawn_windbot();
      }
    });
    windbot_process.on('exit', function(code) {
      log.warn('WindBot EXIT', code);
      if (windbot_looplimit < 1000 && !rebooted) {
        windbot_looplimit++;
        spawn_windbot();
      }
    });
    windbot_process.stdout.setEncoding('utf8');
    windbot_process.stdout.on('data', function(data) {
      log.info('WindBot:', data);
    });
    windbot_process.stderr.setEncoding('utf8');
    windbot_process.stderr.on('data', function(data) {
      log.warn('WindBot Error:', data);
    });
  };

  if (settings.modules.windbot.enabled && settings.modules.windbot.spawn) {
    spawn_windbot();
  }

  rebooted = false;

  if (settings.modules.http) {
    addCallback = function(callback, text) {
      if (!callback) {
        return text;
      }
      return callback + "( " + text + " );";
    };
    requestListener = function(request, response) {
      var archive_args, archive_name, archive_process, check, death_room_found, duellog, error, filename, getpath, kick_room_found, len2, len3, len4, len5, len6, len7, m, n, o, p, parseQueryString, pass_validated, player, q, r, ref2, replay, room, roomsjson, u;
      parseQueryString = true;
      u = url.parse(request.url, parseQueryString);
      if (u.pathname === '/api/getrooms') {
        pass_validated = auth.auth(u.query.username, u.query.pass, "get_rooms", "get_rooms");
        if (!settings.modules.http.public_roomlist && !pass_validated) {
          response.writeHead(200);
          response.end(addCallback(u.query.callback, '{"rooms":[{"roomid":"0","roomname":"密码错误","needpass":"true"}]}'));
        } else {
          response.writeHead(200);
          roomsjson = JSON.stringify({
            rooms: (function() {
              var len2, m, results;
              results = [];
              for (m = 0, len2 = ROOM_all.length; m < len2; m++) {
                room = ROOM_all[m];
                if (room && room.established) {
                  results.push({
                    roomid: room.process_pid.toString(),
                    roomname: pass_validated ? room.name : room.name.split('$', 2)[0],
                    roommode: room.hostinfo.mode,
                    needpass: (room.name.indexOf('$') !== -1).toString(),
                    users: _.sortBy((function() {
                      var len3, n, ref2, results1;
                      ref2 = room.players;
                      results1 = [];
                      for (n = 0, len3 = ref2.length; n < len3; n++) {
                        player = ref2[n];
                        if (player.pos != null) {
                          results1.push({
                            id: (-1).toString(),
                            name: player.name,
                            ip: settings.modules.http.show_ip && pass_validated && !player.is_local ? player.ip.slice(7) : null,
                            status: settings.modules.http.show_info && room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN && player.pos !== 7 ? {
                              score: room.scores[player.name_vpass],
                              lp: player.lp != null ? player.lp : room.hostinfo.start_lp,
                              cards: room.hostinfo.mode !== 2 ? (player.card_count != null ? player.card_count : room.hostinfo.start_hand) : null
                            } : null,
                            pos: player.pos
                          });
                        }
                      }
                      return results1;
                    })(), "pos"),
                    istart: room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN ? (settings.modules.http.show_info ? "Duel:" + room.duel_count + " " + (room.duel_stage === ygopro.constants.DUEL_STAGE.SIDING ? "Siding" : "Turn:" + (room.turn != null ? room.turn : 0) + (room.death ? "/" + (room.death > 0 ? room.death - 1 : "Death") : "")) : 'start') : 'wait'
                  });
                }
              }
              return results;
            })()
          }, null, 2);
          response.end(addCallback(u.query.callback, roomsjson));
        }
      } else if (u.pathname === '/api/duellog' && settings.modules.tournament_mode.enabled) {
        if (!auth.auth(u.query.username, u.query.pass, "duel_log", "duel_log")) {
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "[{name:'密码错误'}]"));
          return;
        } else {
          response.writeHead(200);
          duellog = JSON.stringify(duel_log.duel_log, null, 2);
          response.end(addCallback(u.query.callback, duellog));
        }
      } else if (u.pathname === '/api/archive.zip' && settings.modules.tournament_mode.enabled) {
        if (!auth.auth(u.query.username, u.query.pass, "download_replay", "download_replay_archive")) {
          response.writeHead(403);
          response.end("Invalid password.");
          return;
        } else {
          try {
            archive_name = moment().format('YYYY-MM-DD HH-mm-ss') + ".zip";
            archive_args = ["a", "-mx0", "-y", archive_name];
            check = false;
            ref2 = duel_log.duel_log;
            for (m = 0, len2 = ref2.length; m < len2; m++) {
              replay = ref2[m];
              check = true;
              archive_args.push(replay.replay_filename);
            }
            if (!check) {
              response.writeHead(403);
              response.end("Duel logs not found.");
              return;
            }
            archive_process = spawn(settings.modules.tournament_mode.replay_archive_tool, archive_args, {
              cwd: settings.modules.tournament_mode.replay_path
            });
            archive_process.on('error', (function(_this) {
              return function(err) {
                response.writeHead(403);
                response.end("Failed packing replays. " + err);
              };
            })(this));
            archive_process.on('exit', (function(_this) {
              return function(code) {
                return fs.readFile(settings.modules.tournament_mode.replay_path + archive_name, function(error, buffer) {
                  if (error) {
                    response.writeHead(403);
                    response.end("Failed sending replays. " + error);
                  } else {
                    response.writeHead(200, {
                      "Content-Type": "application/octet-stream",
                      "Content-Disposition": "attachment"
                    });
                    response.end(buffer);
                  }
                });
              };
            })(this));
            archive_process.stdout.setEncoding('utf8');
            archive_process.stdout.on('data', (function(_this) {
              return function(data) {
                return log.info("archive process: " + data);
              };
            })(this));
            archive_process.stderr.setEncoding('utf8');
            archive_process.stderr.on('data', (function(_this) {
              return function(data) {
                return log.warn("archive error: " + data);
              };
            })(this));
          } catch (error1) {
            error = error1;
            response.writeHead(403);
            response.end("Failed reading replays. " + error);
          }
        }
      } else if (u.pathname === '/api/clearlog' && settings.modules.tournament_mode.enabled) {
        if (!auth.auth(u.query.username, u.query.pass, "clear_duel_log", "clear_duel_log")) {
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "[{name:'密码错误'}]"));
          return;
        } else {
          response.writeHead(200);
          if (settings.modules.tournament_mode.log_save_path) {
            fs.writeFile(settings.modules.tournament_mode.log_save_path + 'duel_log.' + moment().format('YYYY-MM-DD HH-mm-ss') + '.json', JSON.stringify(duel_log, null, 2), function(err) {
              if (err) {
                return log.warn('DUEL LOG SAVE ERROR', err);
              }
            });
          }
          duel_log.duel_log = [];
          setting_save(duel_log);
          response.end(addCallback(u.query.callback, "[{name:'Success'}]"));
        }
      } else if (_.startsWith(u.pathname, '/api/replay') && settings.modules.tournament_mode.enabled) {
        if (!auth.auth(u.query.username, u.query.pass, "download_replay", "download_replay")) {
          response.writeHead(403);
          response.end("密码错误");
          return;
        } else {
          getpath = u.pathname.split("/");
          filename = path.basename(decodeURIComponent(getpath.pop()));
          fs.readFile(settings.modules.tournament_mode.replay_path + filename, function(error, buffer) {
            if (error) {
              response.writeHead(404);
              response.end("未找到文件 " + filename);
            } else {
              response.writeHead(200, {
                "Content-Type": "application/octet-stream",
                "Content-Disposition": "attachment"
              });
              response.end(buffer);
            }
          });
        }
      } else if (u.pathname === '/api/message') {
        if (u.query.shout) {
          if (!auth.auth(u.query.username, u.query.pass, "shout", "shout")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          for (n = 0, len3 = ROOM_all.length; n < len3; n++) {
            room = ROOM_all[n];
            if (room && room.established) {
              ygopro.stoc_send_chat_to_room(room, u.query.shout, ygopro.constants.COLORS.YELLOW);
            }
          }
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['shout ok', '" + u.query.shout + "']"));
        } else if (u.query.stop) {
          if (!auth.auth(u.query.username, u.query.pass, "stop", "stop")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          if (u.query.stop === 'false') {
            u.query.stop = false;
          }
          setting_change(settings, 'modules:stop', u.query.stop);
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['stop ok', '" + u.query.stop + "']"));
        } else if (u.query.welcome) {
          if (!auth.auth(u.query.username, u.query.pass, "change_settings", "change_welcome")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          setting_change(settings, 'modules:welcome', u.query.welcome);
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['welcome ok', '" + u.query.welcome + "']"));
        } else if (u.query.getwelcome) {
          if (!auth.auth(u.query.username, u.query.pass, "change_settings", "get_welcome")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['get ok', '" + settings.modules.welcome + "']"));
        } else if (u.query.loadtips) {
          if (!auth.auth(u.query.username, u.query.pass, "change_settings", "change_tips")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          load_tips();
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['loading tip', '" + settings.modules.tips.get + "']"));
        } else if (u.query.loaddialogues) {
          if (!auth.auth(u.query.username, u.query.pass, "change_settings", "change_dialogues")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          load_dialogues();
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['loading dialogues', '" + settings.modules.dialogues.get + "']"));
        } else if (u.query.ban) {
          if (!auth.auth(u.query.username, u.query.pass, "ban_user", "ban_user")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          ban_user(u.query.ban);
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['ban ok', '" + u.query.ban + "']"));
        } else if (u.query.kick) {
          if (!auth.auth(u.query.username, u.query.pass, "kick_user", "kick_user")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          kick_room_found = false;
          for (o = 0, len4 = ROOM_all.length; o < len4; o++) {
            room = ROOM_all[o];
            if (!(room && room.established && (u.query.kick === "all" || u.query.kick === room.process_pid.toString() || u.query.kick === room.name))) {
              continue;
            }
            kick_room_found = true;
            if (room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
              room.scores[room.dueling_players[0].name_vpass] = 0;
              room.scores[room.dueling_players[1].name_vpass] = 0;
            }
            room.kicked = true;
            room.send_replays();
            room.process.kill();
            room["delete"]();
          }
          response.writeHead(200);
          if (kick_room_found) {
            response.end(addCallback(u.query.callback, "['kick ok', '" + u.query.kick + "']"));
          } else {
            response.end(addCallback(u.query.callback, "['room not found', '" + u.query.kick + "']"));
          }
        } else if (u.query.death) {
          if (!auth.auth(u.query.username, u.query.pass, "start_death", "start_death")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          death_room_found = false;
          for (p = 0, len5 = ROOM_all.length; p < len5; p++) {
            room = ROOM_all[p];
            if (room && (u.query.death === "all" || u.query.death === room.process_pid.toString() || u.query.death === room.name)) {
              if (room.start_death()) {
                death_room_found = true;
              }
            }
          }
          response.writeHead(200);
          if (death_room_found) {
            response.end(addCallback(u.query.callback, "['death ok', '" + u.query.death + "']"));
          } else {
            response.end(addCallback(u.query.callback, "['room not found', '" + u.query.death + "']"));
          }
        } else if (u.query.deathcancel) {
          if (!auth.auth(u.query.username, u.query.pass, "start_death", "cancel_death")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          death_room_found = false;
          for (q = 0, len6 = ROOM_all.length; q < len6; q++) {
            room = ROOM_all[q];
            if (room && (u.query.deathcancel === "all" || u.query.deathcancel === room.process_pid.toString() || u.query.deathcancel === room.name)) {
              if (room.cancel_death()) {
                death_room_found = true;
              }
            }
          }
          response.writeHead(200);
          if (death_room_found) {
            response.end(addCallback(u.query.callback, "['death cancel ok', '" + u.query.deathcancel + "']"));
          } else {
            response.end(addCallback(u.query.callback, "['room not found', '" + u.query.deathcancel + "']"));
          }
        } else if (u.query.reboot) {
          if (!auth.auth(u.query.username, u.query.pass, "stop", "reboot")) {
            response.writeHead(200);
            response.end(addCallback(u.query.callback, "['密码错误', 0]"));
            return;
          }
          for (r = 0, len7 = ROOM_all.length; r < len7; r++) {
            room = ROOM_all[r];
            if (!(room)) {
              continue;
            }
            if (room.duel_stage !== ygopro.constants.DUEL_STAGE.BEGIN) {
              room.scores[room.dueling_players[0].name_vpass] = 0;
              room.scores[room.dueling_players[1].name_vpass] = 0;
            }
            room.kicked = true;
            room.send_replays();
            room.process.kill();
            room["delete"]();
          }
          rebooted = true;
          if (windbot_process) {
            windbot_process.kill();
          }
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['reboot ok', '" + u.query.reboot + "']"));
          throw "rebooted";
        } else {
          response.writeHead(400);
          response.end();
        }
      } else {
        response.writeHead(400);
        response.end();
      }
    };
    http_server = http.createServer(requestListener);
    http_server.listen(settings.modules.http.port);
    if (settings.modules.http.ssl.enabled) {
      https = require('https');
      options = {
        cert: fs.readFileSync(settings.modules.http.ssl.cert),
        key: fs.readFileSync(settings.modules.http.ssl.key)
      };
      https_server = https.createServer(options, requestListener);
      if (settings.modules.http.websocket_roomlist && roomlist) {
        roomlist.init(https_server, ROOM_all);
      }
      https_server.listen(settings.modules.http.ssl.port);
    }
  }

  if (!fs.existsSync('./plugins')) {
    fs.mkdirSync('./plugins');
  }

  plugin_list = fs.readdirSync("./plugins");

  for (m = 0, len2 = plugin_list.length; m < len2; m++) {
    plugin_filename = plugin_list[m];
    plugin_path = "./plugins/" + plugin_filename;
    require(plugin_path);
  }

}).call(this);