// Generated by CoffeeScript 1.12.2
(function() {
  var Cloud_replay_ids, ROOM_all, ROOM_bad_ip, ROOM_ban_player, ROOM_connected_ip, ROOM_find_by_name, 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_players_banned, ROOM_players_oppentlist, ROOM_unwelcome, ROOM_validate, Room, _, addCallback, ban_user, bunyan, cppversion, crypto, date, defaultconfig, execFile, fs, geoip, get_memory_usage, http, http_server, https, https_server, list, load_dialogues, load_tips, log, moment, nconf, net, options, os, path, pgClient, pg_client, pg_query, redis, redisdb, report_to_big_brother, request, requestListener, roomlist, settings, spawn, spawnSync, url, users_cache, wait_room_start, windbot_process, ygopro, zlib;

  net = require('net');

  http = require('http');

  url = require('url');

  path = require('path');

  fs = require('fs');

  os = require('os');

  crypto = require('crypto');

  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.locale('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年'
    }
  });

  nconf = require('nconf');

  nconf.file('./config.user.json');

  defaultconfig = require('./config.json');

  nconf.defaults(defaultconfig);

  settings = global.settings = nconf.get();

  nconf.myset = function(settings, path, val) {
    var key, target;
    nconf.set(path, val);
    nconf.save();
    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;
    }
  };

  try {
    cppversion = parseInt(fs.readFileSync('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16');
    nconf.myset(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)");
  }

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

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

  if (settings.modules.windbot.enabled) {
    settings.modules.windbots = require(settings.modules.windbot.botlist).windbots;
  }

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

  if (settings.modules.http.websocket_roomlist) {
    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();
  }

  get_memory_usage = function() {
    var actualFree, buffers, cached, free, line, lines, percentUsed, prc_free, total;
    prc_free = spawnSync("free", []);
    if (prc_free.stdout) {
      lines = prc_free.stdout.toString().split(/\n/g);
      line = lines[1].split(/\s+/);
      total = parseInt(line[1], 10);
      free = parseInt(line[3], 10);
      buffers = parseInt(line[5], 10);
      cached = parseInt(line[6], 10);
      actualFree = free + buffers + cached;
      percentUsed = parseFloat(((1 - (actualFree / total)) * 100).toFixed(2));
    } else {
      percentUsed = 0;
    }
    return percentUsed;
  };

  Cloud_replay_ids = [];

  ROOM_all = [];

  ROOM_players_oppentlist = {};

  ROOM_players_banned = [];

  ROOM_connected_ip = {};

  ROOM_bad_ip = {};

  ban_user = function(name) {
    var bad_ip, j, k, len, len1, player, ref, room;
    settings.ban.banned_user.push(name);
    nconf.myset(settings, "ban:banned_user", settings.ban.banned_user);
    bad_ip = 0;
    for (j = 0, len = ROOM_all.length; j < len; j++) {
      room = ROOM_all[j];
      if (room && room.established) {
        ref = room.players;
        for (k = 0, len1 = ref.length; k < len1; k++) {
          player = ref[k];
          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);
            player.destroy();
            continue;
          }
        }
      }
    }
  };

  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_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 (get_memory_usage() >= 90) {
      return null;
    } else {
      return new Room(name);
    }
  };

  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.started && ((type === '' && 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 (get_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 = 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(settings.modules.windbots);
      name = 'AI#' + Math.floor(Math.random() * 100000);
    } else if (namea.length > 1) {
      ainame = namea[namea.length - 1];
      windbot = _.sample(_.filter(settings.modules.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(settings.modules.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 = function(name) {
    var result;
    result = _.find(ROOM_all, function(room) {
      return room && room.name === name;
    });
    return result;
  };

  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 = function(port) {
    return _.find(ROOM_all, function(room) {
      return room && room.port === port;
    });
  };

  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 = function(room, bad_player, reason) {
    var j, len, player, ref;
    if (!room) {
      return;
    }
    ref = room.players;
    for (j = 0, len = ref.length; j < len; j++) {
      player = ref[j];
      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);
      }
    }
  };

  Room = (function() {
    function Room(name, hostinfo) {
      var draw_count, lflist, param, rule, start_hand, start_lp, time_limit;
      this.hostinfo = hostinfo;
      this.name = name;
      this.alive = true;
      this.players = [];
      this.player_datas = [];
      this.status = 'starting';
      this.started = false;
      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 = {};
      ROOM_all.push(this);
      this.hostinfo || (this.hostinfo = JSON.parse(JSON.stringify(settings.hostinfo)));
      if (settings.lflist.length) {
        if (this.hostinfo.rule === 1 && this.hostinfo.lflist === 0) {
          this.hostinfo.lflist = _.findIndex(settings.lflist, function(list) {
            return list.tcg;
          });
        }
      } else {
        this.hostinfo.lflist = -1;
      }
      this.hostinfo.replay_mode = settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.replay_safe ? 1 : 0;
      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;
      } 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.enable_priority = param[3] === 'T';
        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(settings.lflist, function(list) {
            return list.tcg;
          });
        }
        if (rule.match(/(^|,|,)(OCGONLY|OO)(,|,|$)/)) {
          this.hostinfo.rule = 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.enable_priority = true;
        }
      }
      param = [0, this.hostinfo.lflist, this.hostinfo.rule, this.hostinfo.mode, (this.hostinfo.enable_priority ? 'T' : 'F'), (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.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, j, len, ref;
                ref = player.pre_establish_buffers;
                for (j = 0, len = ref.length; j < len; j++) {
                  buffer = ref[j];
                  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));
      } catch (error1) {
        this.error = "${create_room_failed}";
      }
    }

    Room.prototype["delete"] = function() {
      var index, log_rep_id, name, player_ips, player_names, recorder_buffer, ref, replay_id, score, score_array;
      if (this.deleted) {
        return;
      }
      if (this.started && settings.modules.arena_mode.enabled && this.arena) {
        score_array = [];
        ref = this.scores;
        for (name in ref) {
          score = ref[name];
          score_array.push({
            name: name,
            score: score
          });
        }
        if (score_array.length === 2) {
          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,
              start: this.start_time,
              end: moment().format(),
              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 (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.ip);
        });
        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) {
            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 (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.add_windbot = function(botdata) {
      this.windbot = botdata;
      request({
        url: "http://127.0.0.1:" + settings.modules.windbot.port + "/?name=" + (encodeURIComponent(botdata.name)) + "&deck=" + (encodeURIComponent(botdata.deck)) + "&host=127.0.0.1&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);
      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.started && settings.modules.http.websocket_roomlist) {
          roomlist.update(this);
        }
        client.server.connect(this.port, '127.0.0.1', function() {
          var buffer, j, len, ref;
          ref = client.pre_establish_buffers;
          for (j = 0, len = ref.length; j < len; j++) {
            buffer = ref[j];
            client.server.write(buffer);
          }
          client.established = true;
          client.pre_establish_buffers = [];
        });
      }
    };

    Room.prototype.disconnect = function(client, error) {
      var index;
      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);
        }
      } else {
        index = _.indexOf(this.players, client);
        if (index !== -1) {
          this.players.splice(index, 1);
        }
        if (this.started && this.disconnector !== 'server' && (client.pos < 4 || client.is_host)) {
          this.finished = true;
          this.scores[client.name] = -9;
          if (this.random_type && !client.flee_free) {
            ROOM_ban_player(client.name, client.ip, "${random_ban_reason_flee}");
          }
        }
        if (this.players.length && !(this.windbot && client.is_host)) {
          ygopro.stoc_send_chat_to_room(this, (client.name + " ${left_game}") + (error ? ": " + error : ''));
          if (!this.windbot && !this.started && settings.modules.http.websocket_roomlist) {
            roomlist.update(this);
          }
        } else {
          this.process.kill();
          this["delete"]();
        }
      }
    };

    return Room;

  })();

  net.createServer(function(client) {
    var connect_count, server;
    client.ip = client.remoteAddress;
    connect_count = ROOM_connected_ip[client.ip] || 0;
    if (!settings.modules.test_mode.no_connect_count_limit && client.ip !== '::ffff:127.0.0.1') {
      connect_count++;
    }
    ROOM_connected_ip[client.ip] = connect_count;
    server = new net.Socket();
    client.server = server;
    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 (room) {
          room.disconnect(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 = error;
        if (room) {
          room.disconnect(client, error);
        }
      }
      server.destroy();
    });
    client.on('timeout', function() {
      server.destroy();
    });
    server.on('close', function(had_error) {
      var room;
      room = ROOM_all[client.rid];
      if (room) {
        room.disconnector = 'server';
      }
      if (!server.closed) {
        server.closed = true;
      }
      if (!client.closed) {
        ygopro.stoc_send_chat(client, "${server_closed}", ygopro.constants.COLORS.RED);
        client.destroy();
      }
    });
    server.on('error', function(error) {
      var room;
      room = ROOM_all[client.rid];
      if (room) {
        room.disconnector = 'server';
      }
      server.closed = error;
      if (!client.closed) {
        ygopro.stoc_send_chat(client, "${server_error}: " + error, ygopro.constants.COLORS.RED);
        client.destroy();
      }
    });
    if (ROOM_bad_ip[client.ip] > 5 || ROOM_connected_ip[client.ip] > 10) {
      log.info('BAD IP', client.ip);
      client.destroy();
      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 = new Buffer(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.destroy();
            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.destroy();
          });
        });
      };
    }
    client.pre_establish_buffers = new Array();
    client.on('data', function(ctos_buffer) {
      var b, bad_ip_count, buffer, cancel, ctos_message_length, ctos_proto, datas, info, j, k, len, len1, looplimit, room, struct;
      if (client.is_post_watcher) {
        room = ROOM_all[client.rid];
        if (room) {
          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 (ygopro.ctos_follows[ctos_proto]) {
                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[ctos_proto].synchronous) {
                  cancel = ygopro.ctos_follows[ctos_proto].callback(b, info, client, server);
                } else {
                  ygopro.ctos_follows[ctos_proto].callback(b, info, client, server);
                }
              }
              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.destroy();
            break;
          }
        }
        if (client.established) {
          for (j = 0, len = datas.length; j < len; j++) {
            buffer = datas[j];
            server.write(buffer);
          }
        } else {
          for (k = 0, len1 = datas.length; k < len1; k++) {
            buffer = datas[k];
            client.pre_establish_buffers.push(buffer);
          }
        }
      }
    });
    server.on('data', function(stoc_buffer) {
      var b, buffer, cancel, datas, info, j, len, looplimit, stanzas, 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", 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", client.ip);
            break;
          }
        } else {
          if (stoc_buffer.length >= 2 + stoc_message_length) {
            cancel = false;
            stanzas = stoc_proto;
            if (ygopro.stoc_follows[stoc_proto]) {
              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[stoc_proto].synchronous) {
                cancel = ygopro.stoc_follows[stoc_proto].callback(b, info, client, server);
              } else {
                ygopro.stoc_follows[stoc_proto].callback(b, info, client, server);
              }
            }
            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", client.ip);
            break;
          }
        }
        looplimit++;
        if (looplimit > 800) {
          log.info("error stoc", client.name);
          server.destroy();
          break;
        }
      }
      for (j = 0, len = datas.length; j < len; j++) {
        buffer = datas[j];
        client.write(buffer);
      }
    });
  }).listen(settings.port, function() {
    log.info("server started", settings.port);
  });

  ygopro.ctos_follow('PLAYER_INFO', true, function(buffer, info, client, server) {
    var geo, lang, name, struct;
    name = info.name.split("$")[0];
    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;
    }
    struct = ygopro.structs["CTOS_PlayerInfo"];
    struct._setBuff(buffer);
    struct.set("name", name);
    buffer = struct.buffer;
    client.name = name;
    if (!settings.modules.i18n.auto_pick || client.ip === "::ffff:127.0.0.1") {
      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) {
    var check, decrypted_buffer, finish, i, id, j, k, len, len1, name, ref, ref1, replay_id, room, secret;
    info.pass = info.pass.trim();
    if (settings.modules.stop) {
      ygopro.stoc_die(client, settings.modules.stop);
    } 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.ip + ":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.destroy();
      }), 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.destroy();
    } else if (!info.pass.length && !settings.modules.random_duel.enabled && !settings.modules.windbot.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 = new Buffer(info.pass.slice(0, 8), 'base64');
      if (buffer.length !== 6) {
        ygopro.stoc_die(client, '${invalid_password_payload}');
        return;
      }
      check = function(buf) {
        var checksum, i, j, ref;
        checksum = 0;
        for (i = j = 0, ref = buf.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
          checksum += buf.readUInt8(i);
        }
        return (checksum & 0xFF) === 0;
      };
      finish = function(buffer) {
        var action, j, len, name, opt1, opt2, opt3, options, ref, room, title;
        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,
              enable_priority: !!((opt1 >> 2) & 1),
              no_check_deck: !!((opt1 >> 1) & 1),
              no_shuffle_deck: !!(opt1 & 1),
              start_lp: opt2,
              start_hand: opt3 >> 4,
              draw_count: opt3 & 0xF
            };
            options.lflist = _.findIndex(settings.lflist, function(list) {
              return ((options.rule === 1) === list.tcg) && list.date.isBefore();
            });
            room = new Room(name, options);
            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:
            room = ROOM_find_or_create_by_name('M#' + info.pass.slice(8));
            room["private"] = true;
            room.arena = settings.modules.arena_mode.mode;
            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.started) {
          if (settings.modules.cloud_replay.enable_halfway_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);
            ref = room.watcher_buffers;
            for (j = 0, len = ref.length; j < len; j++) {
              buffer = ref[j];
              client.write(buffer);
            }
          } else {
            ygopro.stoc_die(client, "${watch_denied}");
          }
        } else {
          client.setTimeout(300000);
          client.rid = _.indexOf(ROOM_all, room);
          room.connect(client);
        }
      };
      if (id = users_cache[client.name]) {
        secret = id % 65535 + 1;
        decrypted_buffer = new Buffer(6);
        ref = [0, 2, 4];
        for (j = 0, len = ref.length; j < len; j++) {
          i = ref[j];
          decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i);
        }
        if (check(decrypted_buffer)) {
          return finish(decrypted_buffer);
        }
      }
      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 k, len1, ref1;
        if (body && body.user) {
          users_cache[client.name] = body.user.id;
          secret = body.user.id % 65535 + 1;
          decrypted_buffer = new Buffer(6);
          ref1 = [0, 2, 4];
          for (k = 0, len1 = ref1.length; k < len1; k++) {
            i = ref1[k];
            decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i);
          }
          if (check(decrypted_buffer)) {
            buffer = decrypted_buffer;
          }
        }
        if (!check(buffer)) {
          ygopro.stoc_die(client, '${invalid_password_checksum}');
          return;
        }
        return finish(buffer);
      });
    } 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);
      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(settings.ban.badword_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(settings.ban.badword_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(settings.ban.badword_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.started) {
        if (settings.modules.cloud_replay.enable_halfway_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);
          ref1 = room.watcher_buffers;
          for (k = 0, len1 = ref1.length; k < len1; k++) {
            buffer = ref1[k];
            client.write(buffer);
          }
        } else {
          ygopro.stoc_die(client, "${watch_denied}");
        }
      } else {
        client.setTimeout(300000);
        client.rid = _.indexOf(ROOM_all, room);
        room.connect(client);
      }
    }
  });

  ygopro.stoc_follow('JOIN_GAME', false, function(buffer, info, client, server) {
    var recorder, room, watcher;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    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.ip !== '::ffff:127.0.0.1') {
      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 (!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.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 j, len, ref, w;
        room = ROOM_all[client.rid];
        if (!room) {
          return;
        }
        room.watcher_buffers.push(data);
        ref = room.watchers;
        for (j = 0, len = ref.length; j < len; j++) {
          w = ref[j];
          if (w) {
            w.write(data);
          }
        }
      });
      watcher.on('error', function(error) {});
    }
  });

  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 {
        nconf.myset(settings, "dialogues", body);
        log.info("dialogues loaded", _.size(body));
      }
    });
  };

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

  ygopro.stoc_follow('GAME_MSG', false, function(buffer, info, client, server) {
    var card, j, len, line, msg, playertype, pos, reason, ref, ref1, ref2, room, val;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    msg = buffer.readInt8(0);
    if (msg >= 10 && msg < 30) {
      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 (client.is_host) {
        room.turn = 0;
      }
    }
    if (ygopro.constants.MSG[msg] === 'NEW_TURN') {
      if (client.is_host) {
        room.turn = room.turn + 1;
      }
      if (client.surrend_confirm) {
        client.surrend_confirm = false;
        ygopro.stoc_send_chat(client, "${surrender_canceled}", ygopro.constants.COLORS.BABYBLUE);
      }
    }
    if (ygopro.constants.MSG[msg] === 'WIN' && client.is_host) {
      pos = buffer.readUInt8(1);
      if (!(client.is_first || pos === 2)) {
        pos = 1 - pos;
      }
      reason = buffer.readUInt8(2);
      room.winner = pos;
      if (room && !room.finished && room.dueling_players[pos]) {
        room.winner_name = room.dueling_players[pos].name;
        room.scores[room.winner_name] = room.scores[room.winner_name] + 1;
      }
    }
    if (ygopro.constants.MSG[msg] === 'DAMAGE' && client.is_host) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp -= val;
      if ((0 < (ref = room.dueling_players[pos].lp) && ref <= 100)) {
        ygopro.stoc_send_chat_to_room(room, "${lp_low_opponent}", ygopro.constants.COLORS.PINK);
      }
    }
    if (ygopro.constants.MSG[msg] === 'RECOVER' && client.is_host) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp += val;
    }
    if (ygopro.constants.MSG[msg] === 'LPUPDATE' && client.is_host) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp = val;
    }
    if (ygopro.constants.MSG[msg] === 'PAY_LPCOST' && client.is_host) {
      pos = buffer.readUInt8(1);
      if (!client.is_first) {
        pos = 1 - pos;
      }
      val = buffer.readInt32LE(2);
      room.dueling_players[pos].lp -= val;
      if ((0 < (ref1 = room.dueling_players[pos].lp) && ref1 <= 100)) {
        ygopro.stoc_send_chat_to_room(room, "${lp_low_self}", ygopro.constants.COLORS.PINK);
      }
    }
    if (settings.modules.dialogues.enabled) {
      if (ygopro.constants.MSG[msg] === 'SUMMONING' || ygopro.constants.MSG[msg] === 'SPSUMMONING') {
        card = buffer.readUInt32LE(1);
        if (settings.dialogues[card]) {
          ref2 = _.lines(settings.dialogues[card][Math.floor(Math.random() * settings.dialogues[card].length)]);
          for (j = 0, len = ref2.length; j < len; j++) {
            line = ref2[j];
            ygopro.stoc_send_chat(client, line, ygopro.constants.COLORS.PINK);
          }
        }
      }
    }
  });

  ygopro.ctos_follow('HS_KICK', true, function(buffer, info, client, server) {
    var j, len, player, ref, room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    ref = room.players;
    for (j = 0, len = ref.length; j < len; j++) {
      player = ref[j];
      if (player && player.pos === info.pos && player !== client) {
        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.destroy();
          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', false, function(buffer, info, client, server) {
    var is_host, selftype;
    selftype = info.type & 0xf;
    is_host = ((info.type >> 4) & 0xf) !== 0;
    client.is_host = is_host;
    client.pos = selftype;
  });

  ygopro.stoc_follow('HS_PLAYER_CHANGE', false, function(buffer, info, client, server) {
    var is_ready, j, len, player, pos, ref, 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) {
      room.ready_player_count_without_host = 0;
      ref = room.players;
      for (j = 0, len = ref.length; j < len; j++) {
        player = ref[j];
        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], 20);
        }), 1000);
      }
    }
  });

  wait_room_start = function(room, time) {
    var j, len, player, ref;
    if (!(!room || room.started || 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 {
        ref = room.players;
        for (j = 0, len = ref.length; j < len; j++) {
          player = ref[j];
          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);
            player.destroy();
          }
        }
      }
    }
  };

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

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

  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 {
        nconf.myset(settings, "tips", body);
        log.info("tips loaded", settings.tips.length);
      }
    });
  };

  if (settings.modules.tips.get) {
    load_tips();
    setInterval(function() {
      var j, len, room;
      for (j = 0, len = ROOM_all.length; j < len; j++) {
        room = ROOM_all[j];
        if (room && room.established) {
          if (!room.started || room.changing_side) {
            ygopro.stoc_send_random_tip_to_room(room);
          }
        }
      }
    }, 30000);
  }

  ygopro.stoc_follow('DUEL_START', false, function(buffer, info, client, server) {
    var deck_arena, deck_name, deck_text, j, len, player, ref, room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    if (!room.started) {
      room.started = true;
      room.start_time = moment().format();
      if (!room.windbot && settings.modules.http.websocket_roomlist) {
        roomlist.start(room);
      }
      room.dueling_players = [];
      ref = room.players;
      for (j = 0, len = ref.length; j < len; j++) {
        player = ref[j];
        if (!(player.pos !== 7)) {
          continue;
        }
        room.dueling_players[player.pos] = player;
        room.scores[player.name] = 0;
        room.player_datas.push({
          ip: player.ip,
          name: player.name
        });
        if (room.random_type === 'T') {
          ROOM_players_oppentlist[player.ip] = null;
        }
      }
    }
    if (settings.modules.tips.enabled) {
      ygopro.stoc_send_random_tip(client);
    }
    if (settings.modules.deck_log.enabled && client.main && client.main.length && !client.deck_saved && !room.windbot) {
      deck_text = '#ygopro-server deck log\n#main\n' + client.main.join('\n') + '\n!side\n' + client.side.join('\n') + '\n';
      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.port + ' ' + client.pos + ' ' + 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) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    if (!room.started || room.hostinfo.mode === 2) {
      return true;
    }
    if (room.random_type && room.turn < 3 && !client.flee_free) {
      ygopro.stoc_send_chat(client, "${surrender_denied}", ygopro.constants.COLORS.BABYBLUE);
      return true;
    }
    return false;
  });

  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) {
    var cancel, cmd, msg, name, oldmsg, 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.started || 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}");
        }
        break;
      case '/tip':
        if (settings.modules.tips.enabled) {
          ygopro.stoc_send_random_tip(client);
        }
        break;
      case '/ai':
        if (settings.modules.windbot.enabled) {
          if (name = cmd[1]) {
            windbot = _.sample(_.filter(settings.modules.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(settings.modules.windbots);
          }
          room.add_windbot(windbot);
        }
        break;
      case '/roomname':
        if (room) {
          ygopro.stoc_send_chat(client, "${room_name} " + room.name, 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)) {
      return cancel;
    }
    if (client.abuse_count >= 5) {
      log.warn("BANNED CHAT", client.name, client.ip, msg);
      ygopro.stoc_send_chat(client, "${banned_chat_tip}", ygopro.constants.COLORS.RED);
      return true;
    }
    oldmsg = msg;
    if (_.any(settings.ban.badword_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.destroy();
        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.started) {
      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(settings.ban.badword_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(settings.ban.badword_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(settings.ban.badword_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) {
    var buff_main, buff_side, card, current_deck, deck, deck_array, deck_main, deck_side, deck_text, deckbuf, decks, found_deck, i, j, k, len, len1, line, room, struct;
    room = ROOM_all[client.rid];
    if (!room) {
      return false;
    }
    buff_main = (function() {
      var j, ref, results;
      results = [];
      for (i = j = 0, ref = info.mainc; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
        results.push(info.deckbuf[i]);
      }
      return results;
    })();
    buff_side = (function() {
      var j, ref, ref1, results;
      results = [];
      for (i = j = ref = info.mainc, ref1 = info.mainc + info.sidec; ref <= ref1 ? j < ref1 : j > ref1; i = ref <= ref1 ? ++j : --j) {
        results.push(info.deckbuf[i]);
      }
      return results;
    })();
    client.main = buff_main;
    client.side = buff_side;
    if (room.random_type || room.arena) {
      if (client.is_host) {
        room.waiting_for_player = room.waiting_for_player2;
      }
      room.last_active_time = moment();
    } else if (!room.started && room.hostinfo.mode === 1 && settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.deck_check) {
      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 (j = 0, len = decks.length; j < len; j++) {
        deck = decks[j];
        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 (k = 0, len1 = deck_array.length; k < len1; k++) {
          line = deck_array[k];
          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) {
    var room;
    room = ROOM_all[client.rid];
    if (!(room && (room.random_type || room.arena))) {
      return;
    }
    room.last_active_time = moment();
  });

  ygopro.ctos_follow('HAND_RESULT', false, function(buffer, info, client, server) {
    var room;
    room = ROOM_all[client.rid];
    if (!(room && (room.random_type || room.arena))) {
      return;
    }
    if (client.is_host) {
      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) {
    var room;
    room = ROOM_all[client.rid];
    if (!(room && (room.random_type || room.arena))) {
      return;
    }
    room.last_active_time = moment();
  });

  ygopro.stoc_follow('SELECT_HAND', false, function(buffer, info, client, server) {
    var room;
    room = ROOM_all[client.rid];
    if (!(room && (room.random_type || room.arena))) {
      return;
    }
    if (client.is_host) {
      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) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    room.changing_side = false;
    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) {
    var room;
    room = ROOM_all[client.rid];
    if (!room) {
      return;
    }
    room.changing_side = true;
    if (room.random_type || room.arena) {
      if (client.is_host) {
        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) {
    var duellog, dueltime, i, j, len, player, ref, replay_filename, room;
    room = ROOM_all[client.rid];
    if (!room) {
      return settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.replay_safe;
    }
    if (settings.modules.cloud_replay.enabled && room.random_type) {
      Cloud_replay_ids.push(room.cloud_replay_id);
    }
    if (settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.replay_safe) {
      if (client.is_host) {
        dueltime = moment().format('YYYY-MM-DD HH:mm:ss');
        replay_filename = dueltime;
        ref = room.dueling_players;
        for (i = j = 0, len = ref.length; j < len; i = ++j) {
          player = ref[i];
          replay_filename = replay_filename + (i > 0 ? " VS " : " ") + player.name;
        }
        replay_filename = replay_filename.replace(/[\/\\\?\*]/g, '_') + ".yrp";
        duellog = {
          time: dueltime,
          name: room.name,
          roomid: room.port.toString(),
          cloud_replay_id: "R#" + room.cloud_replay_id,
          replay_filename: replay_filename,
          players: (function() {
            var k, len1, ref1, results;
            ref1 = room.dueling_players;
            results = [];
            for (k = 0, len1 = ref1.length; k < len1; k++) {
              player = ref1[k];
              results.push({
                name: player.name,
                winner: player.pos === room.winner
              });
            }
            return results;
          })()
        };
        settings.modules.tournament_mode.duel_log.unshift(duellog);
        nconf.myset(settings, "modules:tournament_mode:duel_log", settings.modules.tournament_mode.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 true;
    } else {
      return false;
    }
  });

  if (settings.modules.random_duel.enabled) {
    setInterval(function() {
      var j, len, room, time_passed;
      for (j = 0, len = ROOM_all.length; j < len; j++) {
        room = ROOM_all[j];
        if (!(room && room.started && room.random_type && room.last_active_time && room.waiting_for_player)) {
          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}");
          ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " ${kicked_by_system}", ygopro.constants.COLORS.RED);
          room.waiting_for_player.server.destroy();
        } 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 j, len, room, time_passed;
      for (j = 0, len = ROOM_all.length; j < len; j++) {
        room = ROOM_all[j];
        if (!(room && room.started && room.arena && room.last_active_time && room.waiting_for_player)) {
          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] = -9;
          room.waiting_for_player.server.destroy();
        } 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);
        }
      }
    }, 1000);
  }

  if (settings.modules.windbot.spawn) {
    windbot_process = spawn('mono', ['WindBot.exe', settings.modules.windbot.port], {
      cwd: 'windbot'
    });
    windbot_process.on('error', function(err) {
      log.warn('WindBot ERROR', err);
    });
    windbot_process.on('exit', function(code) {
      log.warn('WindBot EXIT', code);
    });
    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.http) {
    addCallback = function(callback, text) {
      if (!callback) {
        return text;
      }
      return callback + "( " + text + " );";
    };
    requestListener = function(request, response) {
      var duellog, filename, getpath, j, len, parseQueryString, pass_validated, player, room, roomsjson, u;
      parseQueryString = true;
      u = url.parse(request.url, parseQueryString);
      pass_validated = u.query.pass === settings.modules.http.password;
      if (u.pathname === '/api/getrooms') {
        if (!pass_validated && !settings.modules.http.public_roomlist) {
          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 j, len, results;
              results = [];
              for (j = 0, len = ROOM_all.length; j < len; j++) {
                room = ROOM_all[j];
                if (room && room.established) {
                  results.push({
                    pid: room.process.pid.toString(),
                    roomid: room.port.toString(),
                    roomname: pass_validated ? room.name : room.name.split('$', 2)[0],
                    needpass: (room.name.indexOf('$') !== -1).toString(),
                    users: (function() {
                      var k, len1, ref, results1;
                      ref = room.players;
                      results1 = [];
                      for (k = 0, len1 = ref.length; k < len1; k++) {
                        player = ref[k];
                        if (player.pos != null) {
                          results1.push({
                            id: (-1).toString(),
                            name: player.name,
                            pos: player.pos
                          });
                        }
                      }
                      return results1;
                    })(),
                    istart: room.started ? '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 (!(u.query.pass === settings.modules.tournament_mode.password)) {
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "[{name:'密码错误'}]"));
          return;
        } else {
          response.writeHead(200);
          duellog = JSON.stringify(settings.modules.tournament_mode.duel_log, null, 2);
          response.end(addCallback(u.query.callback, duellog));
        }
      } else if (_.startsWith(u.pathname, '/api/replay') && settings.modules.tournament_mode.enabled) {
        if (!(u.query.pass === settings.modules.tournament_mode.password)) {
          response.writeHead(403);
          response.end("密码错误");
          return;
        } else {
          getpath = u.pathname.split("/");
          filename = decodeURIComponent(getpath.pop());
          fs.readFile(settings.modules.tournament_mode.replay_path + filename, function(error, buffer) {
            if (error) {
              response.writeHead(404);
              return response.end("未找到文件 " + filename);
            } else {
              response.writeHead(200, {
                "Content-Type": "application/octet-stream",
                "Content-Disposition": "attachment"
              });
              return response.end(buffer);
            }
          });
        }
      } else if (u.pathname === '/api/message') {
        if (!pass_validated) {
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['密码错误', 0]"));
          return;
        }
        if (u.query.shout) {
          for (j = 0, len = ROOM_all.length; j < len; j++) {
            room = ROOM_all[j];
            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 (u.query.stop === 'false') {
            u.query.stop = false;
          }
          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) {
          nconf.myset(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) {
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['get ok', '" + settings.modules.welcome + "']"));
        } else if (u.query.loadtips) {
          load_tips();
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['loading tip', '" + settings.modules.tips.get + "']"));
        } else if (u.query.loaddialogues) {
          load_dialogues();
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['loading dialogues', '" + settings.modules.dialogues.get + "']"));
        } else if (u.query.ban) {
          ban_user(u.query.ban);
          response.writeHead(200);
          response.end(addCallback(u.query.callback, "['ban ok', '" + u.query.ban + "']"));
        } 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);
      roomlist.init(https_server, ROOM_all);
      https_server.listen(settings.modules.http.ssl.port);
    }
  }

}).call(this);