Commit 8c5a41cb authored by mercury233's avatar mercury233

merge

parent 87a452c6
# ignore # ignore
test* jsconfig.json
coffeelint.json
.vscode/
password.json
config.*.json
node_modules/.bin/ /ygopro
node_modules/bunyan/ /windbot
node_modules/request/ /decks/
node_modules/underscore/ /decks_save*
node_modules/underscore.string/ /node_modules/
node_modules/sqlite3/
node_modules/moment/
test*
*.heapsnapshot *.heapsnapshot
*.tmp *.tmp
*.bak *.bak
*.log *.log
Thumbs.db Thumbs.db
ehthumbs.db ehthumbs.db
Desktop.ini Desktop.ini
......
## ygopro-server ## ygopro-server
一个YGOPRO服务器,基于mycard代码修改 一个YGOPRO服务器。
现用于[YGOPRO 233服](http://mercury233.me/ygosrv233/) 现用于[mycard](https://mycard.moe/)
###支持功能 ###支持功能
* Linux上运行 * Linux上运行
...@@ -11,29 +11,56 @@ ...@@ -11,29 +11,56 @@
* 广播消息 * 广播消息
* 召唤台词 * 召唤台词
* 先行卡一键更新 * 先行卡一键更新
* Windbot在线AI
###不支持功能 ###不支持功能
* 用户账号系统 * 用户账号系统
* 在线AI
* 在线聊天室 * 在线聊天室
###使用方法 ###使用方法
* 安装修改后的mycard版ygopro服务端:https://github.com/mercury233/ygopro/tree/server * 可参考[wiki](https://github.com/mercury233/ygopro-server/wiki)安装
* `git clone https://github.com/mercury233/ygopro-server.git` * 手动安装:
* `cd ygopro-server` * 安装mycard版ygopro服务端:https://github.com/mycard/ygopro/tree/server
* `npm install` * `git clone https://github.com/mycard/ygopro-server.git`
* 修改`config.json` * `cd ygopro-server`
* `npm install`
*`config.json`复制为`config.user.json`并进行修改
* `port`为你想要的端口 * `port`为你想要的端口
* `version`为ygopro的十进制版本号(例如,0x1336=4918)
* `ygopro_path`为ygopro服务端的相对路径
* `modules.stop`为文本时,表示服务器关闭 * `modules.stop`为文本时,表示服务器关闭
* `modules.TCG_banlist_id`为lflist中正在使用的TCG禁卡表的编号,0开始 * 更多选项参见wiki
* `node ygopro-server.js`即可运行 * `node ygopro-server.js`即可运行
* 简易的控制台在http://mercury233.me/ygosrv233/dashboard.html (我没有开发给用户使用的大厅的打算。) * 简易的控制台在http://mercury233.me/ygosrv233/dashboard.html (我没有开发给用户使用的大厅的打算。)
###高级功能
* 待补充说明
* 简易的先行卡更新控制台在http://mercury233.me/ygosrv233/pre-dashboard.html * 简易的先行卡更新控制台在http://mercury233.me/ygosrv233/pre-dashboard.html
###开发计划 ### 开发计划
* 重写全部代码,与SalvationServer合并,或作为分支版本 * 重做CTOS和STOC部分
* 模块化附加功能
* 房名代码
* 随机对战
* 召唤台词
* WindBot
* 云录像
* 比赛模式
* 先行卡更新
* 用户账号系统和管理员账号系统
* 云录像更换存储方式
### TODO
* refactoring CTOS and STOC
* change features to modules
* room name parsing
* random duel
* summon dialogues
* WindBot
* cloud replay
* tournament mode
* expansions updater
* user and admin account system
* new database for cloud replay
## Install Docker ## Install Docker
```bash ```bash
......
{ {
"port": 7911, "port": 7911,
"ygopro_path": "ygopro",
"modules": { "modules": {
"welcome": "YGOPRO Server", "welcome": "YGOPRO Server",
"update": "请更新游戏版本,可在社区手动下载更新包", "update": "请更新游戏版本,可在社区手动下载更新包",
"stop": false, "stop": false,
"tips": "http://mycard.moe/tips.json", "tips": "http://mycard.moe/tips.json",
"dialogues": "http://mycard.moe/dialogues.json", "dialogues": "http://mycard.moe/dialogues.json",
"redis_port": 6379,
"enable_websocket_roomlist": true,
"enable_random_duel": false, "enable_random_duel": false,
"enable_halfway_watch": true,
"enable_TCG_as_default": false,
"enable_cloud_replay": false,
"enable_windbot": true, "enable_windbot": true,
"enable_websocket_roomlist": true,
"enable_deck_log": false,
"redis_port": 6379,
"windbot_port": 2399,
"spawn_windbot": true,
"mycard_auth": "https://ygobbs.com", "mycard_auth": "https://ygobbs.com",
"post_start_watching": true, "hang_timeout": 90,
"enable_TCG_as_default": false, "tournament_mode": {
"enabled": false,
"deck_path": "./decks/",
"duel_log": [],
"password": "123456",
"port": 7933
},
"pre_util": {
"enabled": false,
"port": 7944,
"password": "123456",
"git_html_path": "../mercury233.github.io/",
"html_path": "../mercury233.github.io/ygosrv233/",
"html_filename": "pre.html",
"git_db_path": "../ygopro-unofficial/",
"db_path": "../ygopro-unofficial/unofficial/",
"html_img_rel_path": "pre/pics/",
"html_img_thumbnail": "thumbnail/",
"ygopro_path": "../ygopro-pre/",
"only_show_dbs": {
"news.cdb": true,
"pre-release.cdb": true
},
"html_gits": [
{
"name": "GitHub",
"push": ["push", "origin"]
},
{
"name": "Coding",
"push": ["push", "coding", "master:master"]
}
]
},
"http": { "http": {
"port": 7922, "port": 7922,
"password": "123456", "password": "123456",
...@@ -24,5 +62,14 @@ ...@@ -24,5 +62,14 @@
"key": "ssl/privkey.pem" "key": "ssl/privkey.pem"
} }
} }
},
"ban": {
"banned_user": [],
"banned_ip": [],
"badword_level0": ["滚", "衮", "操", "草", "艹", "狗", "日", "曰", "妈", "娘", "逼"],
"badword_level1": ["傻逼", "鸡巴"],
"badword_level2": ["死妈", "草你妈"],
"badword_level3": ["迷奸", "仿真枪"],
"illegal_id": ["^Lv.\\d+", "^VIP.\\d+"]
} }
} }
{
"port": 2333,
"password": "123456",
"git_html_path": "../mercury233.github.io/",
"html_path": "../mercury233.github.io/ygosrv233/",
"html_filename": "pre.html",
"git_db_path": "../ygopro-cards/",
"db_path": "../ygopro-cards/unofficial/",
"html_img_rel_path": "pre/pics/",
"ygopro_path": "../ygopro-pre/"
}
\ No newline at end of file
...@@ -247,6 +247,18 @@ ...@@ -247,6 +247,18 @@
"ATTRIBUTE_LIGHT": 16, "ATTRIBUTE_LIGHT": 16,
"ATTRIBUTE_DARK": 32, "ATTRIBUTE_DARK": 32,
"ATTRIBUTE_DEVINE": 64 "ATTRIBUTE_DEVINE": 64
},
"COLORS": {
"LIGHTBLUE": 8,
"RED": 11,
"GREEN": 12,
"BLUE": 13,
"BABYBLUE": 14,
"PINK": 15,
"YELLOW": 16,
"WHITE": 17,
"GRAY": 18,
"DARKGRAY": 19
} }
} }
......
...@@ -10,14 +10,14 @@ ...@@ -10,14 +10,14 @@
], ],
"author": "zh99998 <zh99998@gmail.com>, mercury233 <me@mercury233.me>", "author": "zh99998 <zh99998@gmail.com>, mercury233 <me@mercury233.me>",
"dependencies": { "dependencies": {
"bunyan": "latest",
"moment": "latest",
"nconf": "latest",
"pg": "latest",
"request": "latest",
"underscore": "latest", "underscore": "latest",
"underscore.string": "latest", "underscore.string": "latest",
"request": "latest", "ws": "latest"
"moment": "latest",
"sqlite3": "latest",
"bunyan": "latest",
"ws": "latest",
"pg": "latest"
}, },
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"GAME_MSG": "GameMsg_Hint_Card_only", "GAME_MSG": "GameMsg_Hint_Card_only",
"SELECT_HAND": "", "SELECT_HAND": "",
"SELECT_TP": "", "SELECT_TP": "",
"REPLAY": "",
"CHAT": "STOC_Chat" "CHAT": "STOC_Chat"
} }
} }
_ = require 'underscore'
_.str = require 'underscore.string'
_.mixin(_.str.exports());
spawn = require('child_process').spawn
spawnSync = require('child_process').spawnSync
settings = require './config.json'
ygopro = require './ygopro.js'
roomlist = require './roomlist' if settings.modules.enable_websocket_roomlist
bunyan = require 'bunyan'
moment = require 'moment'
#redis = require 'redis'
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年'
}
})
log = bunyan.createLogger name: "mycard-room"
#redisdb = redis.createClient host: "127.0.0.1", port: settings.modules.redis_port
#获取可用内存
get_memory_usage = ()->
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
class Room
#name
#port
#players: [client]
#process
#established
#alive
@all = []
@players_oppentlist = {}
@players_banned = []
@ban_player: (name, ip, reason)->
bannedplayer = _.find Room.players_banned, (bannedplayer)->
ip == bannedplayer.ip
if bannedplayer
bannedplayer.count = bannedplayer.count + 1
bantime = if bannedplayer.count > 3 then Math.pow(2, bannedplayer.count - 3) * 2 else 0
bannedplayer.time = if moment() < bannedplayer.time then moment(bannedplayer.time).add(bantime, 'm') else moment().add(bantime, 'm')
bannedplayer.reasons.push(reason) if not _.find bannedplayer.reasons, (bannedreason)->
bannedreason == reason
bannedplayer.need_tip = true;
else
bannedplayer = {"ip": ip, "time": moment(), "count": 1, "reasons": [reason], "need_tip": true}
Room.players_banned.push(bannedplayer)
log.info("banned", name, ip, reason, bannedplayer.count)
return
@find_or_create_by_name: (name, player_ip)->
if settings.modules.enable_random_duel and (name == '' or name.toUpperCase() == 'S' or name.toUpperCase() == 'M' or name.toUpperCase() == 'T')
return @find_or_create_random(name.toUpperCase(), player_ip)
if room = @find_by_name(name)
return room
else if get_memory_usage() >= 90
return null
else
return new Room(name)
@find_or_create_random: (type, player_ip)->
bannedplayer = _.find Room.players_banned, (bannedplayer)->
return player_ip == bannedplayer.ip
if bannedplayer
if bannedplayer.count > 6 and moment() < bannedplayer.time
return {"error": "因为您近期在游戏中多次#{bannedplayer.reasons.join('、')},您已被禁止使用随机对战功能,将在#{moment(bannedplayer.time).fromNow(true)}后解封"}
if bannedplayer.count > 3 and moment() < bannedplayer.time and bannedplayer.need_tip
bannedplayer.need_tip = false
return {"error": "因为您近期在游戏中#{bannedplayer.reasons.join('、')},在#{moment(bannedplayer.time).fromNow(true)}内您随机对战时只能遇到其他违规玩家"}
else if bannedplayer.need_tip
bannedplayer.need_tip = false
return {"error": "系统检测到您近期在游戏中#{bannedplayer.reasons.join('、')},若您违规超过3次,将受到惩罚"}
else if bannedplayer.count > 2
bannedplayer.need_tip = true
max_player = if type == 'T' then 4 else 2
playerbanned = (bannedplayer and bannedplayer.count > 3 and moment() < bannedplayer.time)
result = _.find @all, (room)->
return room.random_type != '' and !room.started and ((type == '' and room.random_type != 'T') or room.random_type == type) and room.get_playing_player().length < max_player and (room.get_host() == null or room.get_host().remoteAddress != Room.players_oppentlist[player_ip]) and (playerbanned == room.deprecated)
if result
result.welcome = '对手已经在等你了,开始决斗吧!'
#log.info 'found room', player_name
else
type = if type then type else 'S'
name = type + ',RANDOM#' + Math.floor(Math.random() * 100000)
result = new Room(name)
result.random_type = type
result.max_player = max_player
result.welcome = '已建立随机对战房间,正在等待对手!'
result.deprecated = playerbanned
#log.info 'create room', player_name, name
return result
@find_by_name: (name)->
result = _.find @all, (room)->
room.name == name
#log.info 'find_by_name', name, result
return result
@find_by_port: (port)->
_.find @all, (room)->
room.port == port
@validate: (name)->
client_name_and_pass = name.split('$', 2)
client_name = client_name_and_pass[0]
client_pass = client_name_and_pass[1]
return true if !client_pass
!_.find Room.all, (room)->
room_name_and_pass = room.name.split('$', 2)
room_name = room_name_and_pass[0]
room_pass = room_name_and_pass[1]
client_name == room_name and client_pass != room_pass
constructor: (name, @hostinfo) ->
@name = name
@alive = true
@players = []
@player_datas = []
@status = 'starting'
@started = false
@established = false
@watcher_buffers = []
@watchers = []
@random_type = ''
@welcome = ''
Room.all.push this
@hostinfo ||=
lflist: _.findIndex settings.lflist, (list)-> !list.tcg and list.date.isBefore()
rule: if settings.modules.enable_TCG_as_default then 2 else 0
mode: 0
enable_priority: false
no_check_deck: false
no_shuffle_deck: false
start_lp: 8000
start_hand: 5
draw_count: 1
time_limit: 180
if name[0...2] == 'M#'
@hostinfo.mode = 1
else if name[0...2] == 'T#'
@hostinfo.mode = 2
@hostinfo.start_lp = 16000
else if (param = name.match /^(\d)(\d)(T|F)(T|F)(T|F)(\d+),(\d+),(\d+)/i)
@hostinfo.rule = parseInt(param[1])
@hostinfo.mode = parseInt(param[2])
@hostinfo.enable_priority = param[3] == 'T'
@hostinfo.no_check_deck = param[4] == 'T'
@hostinfo.no_shuffle_deck = param[5] == 'T'
@hostinfo.start_lp = parseInt(param[6])
@hostinfo.start_hand = parseInt(param[7])
@hostinfo.draw_count = parseInt(param[8])
else if (((param = name.match /(.+)#/) != null) and ( (param[1].length <= 2 and param[1].match(/(S|N|M|T)(0|1|2|T|A)/i)) or (param[1].match(/^(S|N|M|T)(0|1|2|O|T|A)(0|1|O|T)/i)) ) )
rule = param[1].toUpperCase()
#log.info "C", rule
switch rule.charAt(0)
when "M","1"
@hostinfo.mode = 1
when "T","2"
@hostinfo.mode = 2
@hostinfo.start_lp = 16000
else
@hostinfo.mode = 0
switch rule.charAt(1)
when "0","O"
@hostinfo.rule = 0
when "1","T"
@hostinfo.rule = 1
else
@hostinfo.rule = 2
switch rule.charAt(2)
when "1","T"
@hostinfo.lflist = _.findIndex settings.lflist, (list)-> list.tcg and list.date.isBefore()
else
@hostinfo.lflist = _.findIndex settings.lflist, (list)-> !list.tcg and list.date.isBefore()
if ((param = parseInt(rule.charAt(3).match(/\d/))) >= 0)
@hostinfo.time_limit = param * 60
switch rule.charAt(4)
when "T","1"
@hostinfo.enable_priority = true
else
@hostinfo.enable_priority = false
switch rule.charAt(5)
when "T","1"
@hostinfo.no_check_deck = true
else
@hostinfo.no_check_deck = false
switch rule.charAt(6)
when "T","1"
@hostinfo.no_shuffle_deck = true
else
@hostinfo.no_shuffle_deck = false
if ((param = parseInt(rule.charAt(7).match(/\d/))) > 0)
@hostinfo.start_lp = param * 4000
if ((param = parseInt(rule.charAt(8).match(/\d/))) > 0)
@hostinfo.start_hand = param
if ((param = parseInt(rule.charAt(9).match(/\d/))) >= 0)
@hostinfo.draw_count = param
else if ((param = name.match /(.+)#/) != null)
rule = param[1].toUpperCase()
#log.info "233", rule
if (rule.match /(^|,|,)(M|MATCH)(,|,|$)/)
@hostinfo.mode = 1
if (rule.match /(^|,|,)(T|TAG)(,|,|$)/)
@hostinfo.mode = 2
@hostinfo.start_lp = 16000
if (rule.match /(^|,|,)(TCGONLY|TO)(,|,|$)/)
@hostinfo.rule = 1
@hostinfo.lflist = _.findIndex settings.lflist, (list)-> list.tcg and list.date.isBefore()
if (rule.match /(^|,|,)(OCGONLY|OO)(,|,|$)/)
@hostinfo.rule = 0
if (rule.match /(^|,|,)(OT|TCG)(,|,|$)/)
@hostinfo.rule = 2
if (param = rule.match /(^|,|,)LP(\d+)(,|,|$)/)
start_lp = parseInt(param[2])
if (start_lp <= 0) then start_lp = 1
if (start_lp >= 99999) then start_lp = 99999
@hostinfo.start_lp = start_lp
if (param = rule.match /(^|,|,)(TIME|TM|TI)(\d+)(,|,|$)/)
time_limit = parseInt(param[3])
if (time_limit < 0) then time_limit = 180
if (time_limit >= 1 and time_limit <= 60) then time_limit = time_limit * 60
if (time_limit >= 999) then time_limit = 999
@hostinfo.time_limit = time_limit
if (param = rule.match /(^|,|,)(START|ST)(\d+)(,|,|$)/)
start_hand = parseInt(param[3])
if (start_hand <= 0) then start_hand = 1
if (start_hand >= 40) then start_hand = 40
@hostinfo.start_hand = start_hand
if (param = rule.match /(^|,|,)(DRAW|DR)(\d+)(,|,|$)/)
draw_count = parseInt(param[3])
if (draw_count >= 35) then draw_count = 35
@hostinfo.draw_count = draw_count
if (param = rule.match /(^|,|,)(LFLIST|LF)(\d+)(,|,|$)/)
lflist = parseInt(param[3]) - 1
@hostinfo.lflist = lflist
if (rule.match /(^|,|,)(NOLFLIST|NF)(,|,|$)/)
@hostinfo.lflist = -1
if (rule.match /(^|,|,)(NOUNIQUE|NU)(,|,|$)/)
@hostinfo.rule = 3
if (rule.match /(^|,|,)(NOCHECK|NC)(,|,|$)/)
@hostinfo.no_check_deck = true
if (rule.match /(^|,|,)(NOSHUFFLE|NS)(,|,|$)/)
@hostinfo.no_shuffle_deck = true
if (rule.match /(^|,|,)(IGPRIORITY|PR)(,|,|$)/)
@hostinfo.enable_priority = true
param = [0, @hostinfo.lflist, @hostinfo.rule, @hostinfo.mode, (if @hostinfo.enable_priority then 'T' else 'F'),
(if @hostinfo.no_check_deck then 'T' else 'F'), (if @hostinfo.no_shuffle_deck then 'T' else 'F'),
@hostinfo.start_lp, @hostinfo.start_hand, @hostinfo.draw_count, @hostinfo.time_limit]
try
@process = spawn './ygopro', param, {cwd: settings.ygopro_path}
@process.on 'exit', (code)=>
@disconnector = 'server' unless @disconnector
this.delete()
return
@process.stdout.setEncoding('utf8')
@process.stdout.once 'data', (data)=>
@established = true
roomlist.create(this) if !@private and settings.modules.enable_websocket_roomlist
@port = parseInt data
_.each @players, (player)=>
player.server.connect @port, '127.0.0.1', =>
player.server.write buffer for buffer in player.pre_establish_buffers
player.established = true
player.pre_establish_buffers = []
return
return
console.log @windbot
if @windbot
spawn 'mono', ['WindBot.exe'], {
cwd: 'windbot', env: {
YGOPRO_VERSION: settings.version
YGOPRO_HOST: '127.0.0.1'
YGOPRO_PORT: @port
YGOPRO_NAME: @windbot.name
YGOPRO_DECK: @windbot.deck
YGOPRO_DIALOG: @windbot.dialog
}
}
return
catch
@error = "建立房间失败,请重试"
delete: ->
#积分
return if @deleted
#log.info 'room-delete', this.name, Room.all.length
###
if @player_datas.length
replay_buffer = Buffer.concat(@watcher_buffers).toString('binary')
player_names=@player_datas[0].name + (if @player_datas[2] then "+" + @player_datas[2].name else "") +
" VS " +
@player_datas[1].name + (if @player_datas[3] then "+" + @player_datas[3].name else "")
date_time=moment().format('YYYY-MM-DD HH:mm:ss')
replay_id=Math.floor(Math.random()*100000000)
redisdb.hmset("replay:"+replay_id,
"replay_id", replay_id,
"replay_buffer", replay_buffer,
"player_names", player_names,
"date_time", date_time)
recorded_ip=[]
_.each @player_datas, (player)=>
return if _.contains(recorded_ip, player.ip)
recorded_ip.push player.ip
redisdb.lpush(player.ip+":replays", replay_id)
return
###
@watcher_buffers = []
@players = []
@watcher.end() if @watcher
@deleted = true
index = _.indexOf(Room.all, this)
#Room.all[index] = null unless index == -1
Room.all.splice(index, 1) unless index == -1
roomlist.delete @name if !@private and !@started and @established and settings.modules.enable_websocket_roomlist
return
get_playing_player: ->
playing_player = []
_.each @players, (player)=>
if player.pos < 4 then playing_player.push player
return
return playing_player
get_host: ->
host_player = null
_.each @players, (player)=>
if player.is_host then host_player = player
return
return host_player
connect: (client)->
@players.push client
client.ip = client.remoteAddress
if @random_type
host_player = @get_host()
if host_player && (host_player != client)
#进来时已经有人在等待了,互相记录为匹配过
Room.players_oppentlist[host_player.remoteAddress] = client.remoteAddress
Room.players_oppentlist[client.remoteAddress] = host_player.remoteAddress
else
#第一个玩家刚进来,还没就位
Room.players_oppentlist[client.remoteAddress] = null
if @established
roomlist.update(this) if !@private and !@started and settings.modules.enable_websocket_roomlist
client.server.connect @port, '127.0.0.1', ->
client.server.write buffer for buffer in client.pre_establish_buffers
client.established = true
client.pre_establish_buffers = []
return
return
disconnect: (client, error)->
if client.is_post_watcher
ygopro.stoc_send_chat_to_room this, "#{client.name} #{'退出了观战'}#{if error then ": #{error}" else ''}"
index = _.indexOf(@watchers, client)
@watchers.splice(index, 1) unless index == -1
#client.room = null
else
index = _.indexOf(@players, client)
@players.splice(index, 1) unless index == -1
#log.info(@started,@disconnector,client.room.random_type)
if @started and @disconnector != 'server' and client.room.random_type
Room.ban_player(client.name, client.ip, "强退")
if @players.length
ygopro.stoc_send_chat_to_room this, "#{client.name} #{'离开了游戏'}#{if error then ": #{error}" else ''}"
roomlist.update(this) if !@private and !@started and settings.modules.enable_websocket_roomlist
#client.room = null
else
@process.kill()
#client.room = null
this.delete()
return
module.exports = Room
// Generated by CoffeeScript 1.10.0
(function() {
var Room, _, bunyan, get_memory_usage, log, moment, roomlist, settings, spawn, spawnSync, ygopro;
_ = require('underscore');
_.str = require('underscore.string');
_.mixin(_.str.exports());
spawn = require('child_process').spawn;
spawnSync = require('child_process').spawnSync;
settings = require('./config.json');
ygopro = require('./ygopro.js');
if (settings.modules.enable_websocket_roomlist) {
roomlist = require('./roomlist');
}
bunyan = require('bunyan');
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年'
}
});
log = bunyan.createLogger({
name: "mycard-room"
});
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;
};
Room = (function() {
Room.all = [];
Room.players_oppentlist = {};
Room.players_banned = [];
Room.ban_player = function(name, ip, reason) {
var bannedplayer, bantime;
bannedplayer = _.find(Room.players_banned, function(bannedplayer) {
return ip === bannedplayer.ip;
});
if (bannedplayer) {
bannedplayer.count = bannedplayer.count + 1;
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": 1,
"reasons": [reason],
"need_tip": true
};
Room.players_banned.push(bannedplayer);
}
log.info("banned", name, ip, reason, bannedplayer.count);
};
Room.find_or_create_by_name = function(name, player_ip) {
var room;
if (settings.modules.enable_random_duel && (name === '' || name.toUpperCase() === 'S' || name.toUpperCase() === 'M' || name.toUpperCase() === 'T')) {
return this.find_or_create_random(name.toUpperCase(), player_ip);
}
if (room = this.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": "因为您近期在游戏中多次" + (bannedplayer.reasons.join('')) + ",您已被禁止使用随机对战功能,将在" + (moment(bannedplayer.time).fromNow(true)) + "后解封"
};
}
if (bannedplayer.count > 3 && moment() < bannedplayer.time && bannedplayer.need_tip) {
bannedplayer.need_tip = false;
return {
"error": "因为您近期在游戏中" + (bannedplayer.reasons.join('')) + ",在" + (moment(bannedplayer.time).fromNow(true)) + "内您随机对战时只能遇到其他违规玩家"
};
} else if (bannedplayer.need_tip) {
bannedplayer.need_tip = false;
return {
"error": "系统检测到您近期在游戏中" + (bannedplayer.reasons.join('')) + ",若您违规超过3次,将受到惩罚"
};
} 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(this.all, function(room) {
return room.random_type !== '' && !room.started && ((type === '' && room.random_type !== 'T') || room.random_type === type) && room.get_playing_player().length < max_player && (room.get_host() === null || room.get_host().remoteAddress !== Room.players_oppentlist[player_ip]) && (playerbanned === room.deprecated);
});
if (result) {
result.welcome = '对手已经在等你了,开始决斗吧!';
} else {
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 = '已建立随机对战房间,正在等待对手!';
result.deprecated = playerbanned;
}
return result;
};
Room.find_by_name = function(name) {
var result;
result = _.find(this.all, function(room) {
return room.name === name;
});
return result;
};
Room.find_by_port = function(port) {
return _.find(this.all, function(room) {
return 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;
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;
});
};
function Room(name, hostinfo) {
var draw_count, error1, 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.watchers = [];
this.random_type = '';
this.welcome = '';
Room.all.push(this);
this.hostinfo || (this.hostinfo = {
lflist: _.findIndex(settings.lflist, function(list) {
return !list.tcg && list.date.isBefore();
}),
rule: settings.modules.enable_TCG_as_default ? 2 : 0,
mode: 0,
enable_priority: false,
no_check_deck: false,
no_shuffle_deck: false,
start_lp: 8000,
start_hand: 5,
draw_count: 1,
time_limit: 180
});
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 ((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) && ((param[1].length <= 2 && param[1].match(/(S|N|M|T)(0|1|2|T|A)/i)) || (param[1].match(/^(S|N|M|T)(0|1|2|O|T|A)(0|1|O|T)/i)))) {
rule = param[1].toUpperCase();
switch (rule.charAt(0)) {
case "M":
case "1":
this.hostinfo.mode = 1;
break;
case "T":
case "2":
this.hostinfo.mode = 2;
this.hostinfo.start_lp = 16000;
break;
default:
this.hostinfo.mode = 0;
}
switch (rule.charAt(1)) {
case "0":
case "O":
this.hostinfo.rule = 0;
break;
case "1":
case "T":
this.hostinfo.rule = 1;
break;
default:
this.hostinfo.rule = 2;
}
switch (rule.charAt(2)) {
case "1":
case "T":
this.hostinfo.lflist = _.findIndex(settings.lflist, function(list) {
return list.tcg && list.date.isBefore();
});
break;
default:
this.hostinfo.lflist = _.findIndex(settings.lflist, function(list) {
return !list.tcg && list.date.isBefore();
});
}
if ((param = parseInt(rule.charAt(3).match(/\d/))) >= 0) {
this.hostinfo.time_limit = param * 60;
}
switch (rule.charAt(4)) {
case "T":
case "1":
this.hostinfo.enable_priority = true;
break;
default:
this.hostinfo.enable_priority = false;
}
switch (rule.charAt(5)) {
case "T":
case "1":
this.hostinfo.no_check_deck = true;
break;
default:
this.hostinfo.no_check_deck = false;
}
switch (rule.charAt(6)) {
case "T":
case "1":
this.hostinfo.no_shuffle_deck = true;
break;
default:
this.hostinfo.no_shuffle_deck = false;
}
if ((param = parseInt(rule.charAt(7).match(/\d/))) > 0) {
this.hostinfo.start_lp = param * 4000;
}
if ((param = parseInt(rule.charAt(8).match(/\d/))) > 0) {
this.hostinfo.start_hand = param;
}
if ((param = parseInt(rule.charAt(9).match(/\d/))) >= 0) {
this.hostinfo.draw_count = param;
}
} 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 && list.date.isBefore();
});
}
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];
try {
this.process = spawn('./ygopro', param, {
cwd: settings.ygopro_path
});
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["private"] && settings.modules.enable_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, i, len, ref;
ref = player.pre_establish_buffers;
for (i = 0, len = ref.length; i < len; i++) {
buffer = ref[i];
player.server.write(buffer);
}
player.established = true;
player.pre_establish_buffers = [];
});
});
console.log(_this.windbot);
if (_this.windbot) {
spawn('mono', ['WindBot.exe'], {
cwd: 'windbot',
env: {
YGOPRO_VERSION: settings.version,
YGOPRO_HOST: '127.0.0.1',
YGOPRO_PORT: _this.port,
YGOPRO_NAME: _this.windbot.name,
YGOPRO_DECK: _this.windbot.deck,
YGOPRO_DIALOG: _this.windbot.dialog
}
});
}
};
})(this));
} catch (error1) {
this.error = "建立房间失败,请重试";
}
}
Room.prototype["delete"] = function() {
var index;
if (this.deleted) {
return;
}
/*
if @player_datas.length
replay_buffer = Buffer.concat(@watcher_buffers).toString('binary')
player_names=@player_datas[0].name + (if @player_datas[2] then "+" + @player_datas[2].name else "") +
" VS " +
@player_datas[1].name + (if @player_datas[3] then "+" + @player_datas[3].name else "")
date_time=moment().format('YYYY-MM-DD HH:mm:ss')
replay_id=Math.floor(Math.random()*100000000)
redisdb.hmset("replay:"+replay_id,
"replay_id", replay_id,
"replay_buffer", replay_buffer,
"player_names", player_names,
"date_time", date_time)
recorded_ip=[]
_.each @player_datas, (player)=>
return if _.contains(recorded_ip, player.ip)
recorded_ip.push player.ip
redisdb.lpush(player.ip+":replays", replay_id)
return
*/
this.watcher_buffers = [];
this.players = [];
if (this.watcher) {
this.watcher.end();
}
this.deleted = true;
index = _.indexOf(Room.all, this);
if (index !== -1) {
Room.all.splice(index, 1);
}
if (!this["private"] && !this.started && this.established && settings.modules.enable_websocket_roomlist) {
roomlist["delete"](this.name);
}
};
Room.prototype.get_playing_player = function() {
var playing_player;
playing_player = [];
_.each(this.players, (function(_this) {
return function(player) {
if (player.pos < 4) {
playing_player.push(player);
}
};
})(this));
return playing_player;
};
Room.prototype.get_host = function() {
var host_player;
host_player = null;
_.each(this.players, (function(_this) {
return function(player) {
if (player.is_host) {
host_player = player;
}
};
})(this));
return host_player;
};
Room.prototype.connect = function(client) {
var host_player;
this.players.push(client);
client.ip = client.remoteAddress;
if (this.random_type) {
host_player = this.get_host();
if (host_player && (host_player !== client)) {
Room.players_oppentlist[host_player.remoteAddress] = client.remoteAddress;
Room.players_oppentlist[client.remoteAddress] = host_player.remoteAddress;
} else {
Room.players_oppentlist[client.remoteAddress] = null;
}
}
if (this.established) {
if (!this["private"] && !this.started && settings.modules.enable_websocket_roomlist) {
roomlist.update(this);
}
client.server.connect(this.port, '127.0.0.1', function() {
var buffer, i, len, ref;
ref = client.pre_establish_buffers;
for (i = 0, len = ref.length; i < len; i++) {
buffer = ref[i];
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 + " " + '退出了观战' + (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.room.random_type) {
Room.ban_player(client.name, client.ip, "强退");
}
if (this.players.length) {
ygopro.stoc_send_chat_to_room(this, client.name + " " + '离开了游戏' + (error ? ": " + error : ''));
if (!this["private"] && !this.started && settings.modules.enable_websocket_roomlist) {
roomlist.update(this);
}
} else {
this.process.kill();
this["delete"]();
}
}
};
return Room;
})();
module.exports = Room;
}).call(this);
WebSocketServer = require('ws').Server; WebSocketServer = require('ws').Server
server = null server = null
......
// a special version of node-struct by xdenser
// https://github.com/xdenser/node-struct/tree/f843487d6768cd0bf20c2ce7803dde2d92df5694
function byteField(p, offset) { function byteField(p, offset) {
this.length = 1; this.length = 1;
this.get = function () { this.get = function () {
......
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
"deck": [ "deck": [
{"name": "mainc", "type": "unsigned int"}, {"name": "mainc", "type": "unsigned int"},
{"name": "sidec", "type": "unsigned int"}, {"name": "sidec", "type": "unsigned int"},
{"name": "deckbuf", "type": "unsigned int", "length": 75} {"name": "deckbuf", "type": "unsigned int", "length": 90}
], ],
"chat": [ "chat": [
{"name": "msg", "type": "unsigned short", "length":"255", "encoding": "UTF-16LE"} {"name": "msg", "type": "unsigned short", "length":"255", "encoding": "UTF-16LE"}
......
...@@ -8,11 +8,9 @@ ...@@ -8,11 +8,9 @@
*/ */
var sqlite3 = require('sqlite3').verbose(); var sqlite3 = require('sqlite3').verbose();
var fs = require('fs'); var fs = require('fs');
var config = require('./config.deckstats.json'); //{ "deckpath": "../decks", "dbfile": "cards.cdb" }
var constants = require('./constants.json'); var constants = require('./constants.json');
var DECKPATH="C:\\deck\\deck"
var DBFILE="F:\\Works\\deck\\cards.cdb"
var ALL_MAIN_CARDS={}; var ALL_MAIN_CARDS={};
var ALL_SIDE_CARDS={}; var ALL_SIDE_CARDS={};
var ALL_CARD_DATAS={}; var ALL_CARD_DATAS={};
...@@ -27,6 +25,9 @@ function add_to_deck(deck,id) { ...@@ -27,6 +25,9 @@ function add_to_deck(deck,id) {
} }
function add_to_all_list(LIST,id,use) { function add_to_all_list(LIST,id,use) {
if (!ALL_CARD_DATAS[id]) {
return;
}
if (ALL_CARD_DATAS[id].alias) { if (ALL_CARD_DATAS[id].alias) {
id=ALL_CARD_DATAS[id].alias; id=ALL_CARD_DATAS[id].alias;
} }
...@@ -46,7 +47,7 @@ function add_to_all_list(LIST,id,use) { ...@@ -46,7 +47,7 @@ function add_to_all_list(LIST,id,use) {
function read_deck_file(filename) { function read_deck_file(filename) {
console.log("reading "+filename); console.log("reading "+filename);
var deck_text=fs.readFileSync(DECKPATH+"\\"+filename,{encoding:"ASCII"}) var deck_text=fs.readFileSync(config.deckpath+"\\"+filename,{encoding:"ASCII"})
var deck_array=deck_text.split("\n"); var deck_array=deck_text.split("\n");
var deck_main={}; var deck_main={};
var deck_side={}; var deck_side={};
...@@ -68,10 +69,10 @@ function read_deck_file(filename) { ...@@ -68,10 +69,10 @@ function read_deck_file(filename) {
} }
function load_database(callback) { function load_database(callback) {
var db=new sqlite3.Database(DBFILE); var db=new sqlite3.Database(config.dbfile);
db.each("select * from datas,texts where datas.id=texts.id", function (err,result) { db.each("select * from datas,texts where datas.id=texts.id", function (err,result) {
if (err) { if (err) {
console.log(DBFILE + ":" + err); console.log(config.dbfile + ":" + err);
return; return;
} }
else { else {
...@@ -141,7 +142,7 @@ function load_database(callback) { ...@@ -141,7 +142,7 @@ function load_database(callback) {
} }
function read_decks() { function read_decks() {
var ALL_DECKS=fs.readdirSync(DECKPATH); var ALL_DECKS=fs.readdirSync(config.deckpath);
for (var i in ALL_DECKS) { for (var i in ALL_DECKS) {
var filename=ALL_DECKS[i]; var filename=ALL_DECKS[i];
...@@ -150,7 +151,12 @@ function read_decks() { ...@@ -150,7 +151,12 @@ function read_decks() {
} }
} }
output_csv(ALL_MAIN_CARDS,"main.csv"); output_csv(ALL_MAIN_CARDS,"main.csv");
if (ALL_SIDE_CARDS.length) { var ALL_SIDE_CARDS_isempty = true;
for (var j in ALL_SIDE_CARDS) {
ALL_SIDE_CARDS_isempty = false;
break;
}
if (!ALL_SIDE_CARDS_isempty) {
output_csv(ALL_SIDE_CARDS,"side.csv"); output_csv(ALL_SIDE_CARDS,"side.csv");
} }
} }
......
...@@ -17,7 +17,13 @@ var moment = require('moment'); ...@@ -17,7 +17,13 @@ var moment = require('moment');
moment.locale('zh-cn'); moment.locale('zh-cn');
var constants = require('./constants.json'); var constants = require('./constants.json');
var config = require('./config.pre.json');
var nconf = require('nconf');
nconf.file('./config.user.json');
var defaultconfig = require('./config.json');
nconf.defaults(defaultconfig);
var settings = nconf.get();
config=settings.modules.pre_util;
//全卡HTML列表 //全卡HTML列表
var cardHTMLs=[]; var cardHTMLs=[];
...@@ -52,7 +58,7 @@ var loadDb = function(db_file) { ...@@ -52,7 +58,7 @@ var loadDb = function(db_file) {
var cardHTML="<tr>"; var cardHTML="<tr>";
cardHTML+='<td><a href="'+ config.html_img_rel_path + result.id +'.jpg" target="_blank"><img src="'+config.html_img_rel_path+'thumbnail/'+ result.id +'.jpg" alt="'+ result.name +'"></a></td>'; cardHTML+='<td><a href="'+ config.html_img_rel_path + result.id +'.jpg" target="_blank"><img src="'+config.html_img_rel_path+config.html_img_thumbnail+ result.id +'.jpg" alt="'+ result.name +'"></a></td>';
cardHTML+='<td>'+ result.name +'</td>'; cardHTML+='<td>'+ result.name +'</td>';
var cardText=""; var cardText="";
...@@ -92,7 +98,7 @@ var loadDb = function(db_file) { ...@@ -92,7 +98,7 @@ var loadDb = function(db_file) {
if (result.race & constants.RACES.RACE_ZOMBIE) {cardRace="不死";} if (result.race & constants.RACES.RACE_ZOMBIE) {cardRace="不死";}
if (result.race & constants.RACES.RACE_MACHINE) {cardRace="机械";} if (result.race & constants.RACES.RACE_MACHINE) {cardRace="机械";}
if (result.race & constants.RACES.RACE_AQUA) {cardRace="";} if (result.race & constants.RACES.RACE_AQUA) {cardRace="";}
if (result.race & constants.RACES.RACE_PYRO) {cardRace="念动力";} if (result.race & constants.RACES.RACE_PYRO) {cardRace="";}
if (result.race & constants.RACES.RACE_ROCK) {cardRace="岩石";} if (result.race & constants.RACES.RACE_ROCK) {cardRace="岩石";}
if (result.race & constants.RACES.RACE_WINDBEAST) {cardRace="鸟兽";} if (result.race & constants.RACES.RACE_WINDBEAST) {cardRace="鸟兽";}
if (result.race & constants.RACES.RACE_PLANT) {cardRace="植物";} if (result.race & constants.RACES.RACE_PLANT) {cardRace="植物";}
...@@ -119,7 +125,7 @@ var loadDb = function(db_file) { ...@@ -119,7 +125,7 @@ var loadDb = function(db_file) {
if (result.attribute & constants.ATTRIBUTES.ATTRIBUTE_LIGHT) {cardAttr="";} if (result.attribute & constants.ATTRIBUTES.ATTRIBUTE_LIGHT) {cardAttr="";}
if (result.attribute & constants.ATTRIBUTES.ATTRIBUTE_DARK) {cardAttr="";} if (result.attribute & constants.ATTRIBUTES.ATTRIBUTE_DARK) {cardAttr="";}
if (result.attribute & constants.ATTRIBUTES.ATTRIBUTE_DEVINE) {cardAttr="";} if (result.attribute & constants.ATTRIBUTES.ATTRIBUTE_DEVINE) {cardAttr="";}
cardText+="/"+ cardAttr +"\r\n"; cardText+="/"+ cardAttr +"\n";
var cardLevel; var cardLevel;
var cardLScale; var cardLScale;
...@@ -141,14 +147,14 @@ var loadDb = function(db_file) { ...@@ -141,14 +147,14 @@ var loadDb = function(db_file) {
cardText+=" " + cardLScale + "/" +cardRScale; cardText+=" " + cardLScale + "/" +cardRScale;
} }
cardText+="\r\n"; cardText+="\n";
} }
else { else {
cardText+="\r\n"; cardText+="\n";
} }
cardText+=result.desc; cardText+=result.desc;
cardHTML+='<td>'+ cardText.replace(/\r\n/g,"<br>") +'</td>'; cardHTML+='<td>'+ cardText.replace(/\n/g,"<br>") +'</td>';
cardHTML+='</tr>'; cardHTML+='</tr>';
cardHTMLs.push(cardHTML); cardHTMLs.push(cardHTML);
...@@ -166,11 +172,11 @@ var loadDb = function(db_file) { ...@@ -166,11 +172,11 @@ var loadDb = function(db_file) {
//将cardHTMLs中内容更新到指定列表页,同步 //将cardHTMLs中内容更新到指定列表页,同步
var writeToFile = function(message) { var writeToFile = function(message) {
var fileContent=fs.readFileSync(config.html_path+config.html_filename, {"encoding":"utf-8"}); var fileContent=fs.readFileSync(config.html_path+config.html_filename, {"encoding":"utf-8"});
var newContent=cardHTMLs.join("\r\n"); var newContent=cardHTMLs.join("\n");
fileContent=fileContent.replace(/<tbody class="auto-generated">[\w\W]*<\/tbody>/,'<tbody class="auto-generated">\r\n'+newContent+'\r\n</tbody>'); fileContent=fileContent.replace(/<tbody class="auto-generated">[\w\W]*<\/tbody>/,'<tbody class="auto-generated">\n'+newContent+'\n</tbody>');
if (message) { if (message) {
message="<li>"+moment().format('L HH:mm')+"<ul><li>"+message.split("!换行符!").join("</li><li>")+"</li></ul></li>"; message="<li>"+moment().format('L HH:mm')+"<ul><li>"+message.split("!换行符!").join("</li><li>")+"</li></ul></li>";
fileContent=fileContent.replace(/<ul class="auto-generated">/,'<ul class="auto-generated">\r\n'+message); fileContent=fileContent.replace(/<ul class="auto-generated">/,'<ul class="auto-generated">\n'+message);
} }
fs.writeFileSync(config.html_path+config.html_filename, fileContent); fs.writeFileSync(config.html_path+config.html_filename, fileContent);
sendResponse("列表更新完成。"); sendResponse("列表更新完成。");
...@@ -183,7 +189,7 @@ var loadAllDbs = function() { ...@@ -183,7 +189,7 @@ var loadAllDbs = function() {
var files = fs.readdirSync(config.db_path+"expansions/"); var files = fs.readdirSync(config.db_path+"expansions/");
for (var i in files) { for (var i in files) {
var filename = files[i]; var filename = files[i];
if (filename.slice(-4) === ".cdb") { if (filename.slice(-4) === ".cdb" && (!config.only_show_dbs || config.only_show_dbs.length==0 || config.only_show_dbs[filename])) {
loadDb(config.db_path+"expansions/"+filename); loadDb(config.db_path+"expansions/"+filename);
} }
} }
...@@ -198,7 +204,7 @@ var fetchDatas = function() { ...@@ -198,7 +204,7 @@ var fetchDatas = function() {
}); });
proc.stderr.setEncoding('utf8'); proc.stderr.setEncoding('utf8');
proc.stderr.on('data', function(data) { proc.stderr.on('data', function(data) {
sendResponse("git pull error: "+data); sendResponse("git pull: "+data);
}); });
proc.on('close', function (code) { proc.on('close', function (code) {
sendResponse("数据更新完成。"); sendResponse("数据更新完成。");
...@@ -210,7 +216,7 @@ var fetchDatas = function() { ...@@ -210,7 +216,7 @@ var fetchDatas = function() {
}); });
proc2.stderr.setEncoding('utf8'); proc2.stderr.setEncoding('utf8');
proc2.stderr.on('data', function(data) { proc2.stderr.on('data', function(data) {
sendResponse("git pull error: "+data); sendResponse("git pull: "+data);
}); });
proc2.on('close', function (code) { proc2.on('close', function (code) {
sendResponse("网页同步完成。"); sendResponse("网页同步完成。");
...@@ -225,30 +231,27 @@ var pushDatas = function() { ...@@ -225,30 +231,27 @@ var pushDatas = function() {
} catch (error) { } catch (error) {
sendResponse("git error: "+error.stdout); sendResponse("git error: "+error.stdout);
} }
var proc2 = spawn("git", ["push", "gitcafe", "master:gitcafe-pages"], { cwd: config.git_html_path, env: process.env }); for (var i in config.html_gits) {
proc2.stdout.setEncoding('utf8'); var git = config.html_gits[i];
proc2.stdout.on('data', function(data) { var proc = spawn("git", git.push, { cwd: config.git_html_path, env: process.env });
sendResponse("git push: "+data); proc.stdout.setEncoding('utf8');
}); proc.stdout.on('data', (function(git) {
proc2.stderr.setEncoding('utf8'); return function(data) {
proc2.stderr.on('data', function(data) { sendResponse(git.name + " git push: " + data);
sendResponse("git push error: "+data); }
}); })(git));
proc2.on('close', function (code) { proc.stderr.setEncoding('utf8');
sendResponse("gitcafe上传完成。"); proc.stderr.on('data', (function(git) {
}); return function(data) {
var proc = spawn("git", ["push"], { cwd: config.git_html_path, env: process.env }); sendResponse(git.name + " git push: " + data);
proc.stdout.setEncoding('utf8'); }
proc.stdout.on('data', function(data) { })(git));
sendResponse("git push: "+data); proc.on('close', (function(git) {
}); return function(code) {
proc.stderr.setEncoding('utf8'); sendResponse(git.name + "上传完成。");
proc.stderr.on('data', function(data) { }
sendResponse("git push: "+data); })(git));
}); }
proc.on('close', function (code) {
sendResponse("github上传完成。");
});
} }
//将数据库文件夹里卡图复制到列表页对应文件夹里,同步 //将数据库文件夹里卡图复制到列表页对应文件夹里,同步
...@@ -260,9 +263,13 @@ var copyImages = function() { ...@@ -260,9 +263,13 @@ var copyImages = function() {
//将数据库文件夹复制到YGOPRO文件夹里,同步 //将数据库文件夹复制到YGOPRO文件夹里,同步
var copyToYGOPRO = function() { var copyToYGOPRO = function() {
execSync('rm -rf "' + config.ygopro_path + 'expansions/*' + '"'); execSync('rm -rf ' + config.ygopro_path + 'expansions/*' + '');
execSync('cp -rf "' + config.db_path + 'expansions' + '" "' + config.ygopro_path + '"'); execSync('cp -rf "' + config.db_path + 'expansions' + '" "' + config.ygopro_path + '"');
execSync('cp -rf "' + config.db_path + 'script' + '" "' + config.ygopro_path + 'expansions"'); execSync('cp -rf "' + config.db_path + 'script' + '" "' + config.ygopro_path + 'expansions"');
try {
execSync('cp -rf "' + config.db_path + 'lflist.conf' + '" "' + config.ygopro_path + '"');
}
catch (e) {}
sendResponse("更新完成。"); sendResponse("更新完成。");
} }
......
#标准库 # 标准库
net = require 'net' net = require 'net'
http = require 'http' http = require 'http'
url = require 'url' url = require 'url'
...@@ -7,52 +7,125 @@ fs = require 'fs' ...@@ -7,52 +7,125 @@ fs = require 'fs'
os = require 'os' os = require 'os'
crypto = require 'crypto' crypto = require 'crypto'
execFile = require('child_process').execFile execFile = require('child_process').execFile
spawn = require('child_process').spawn
spawnSync = require('child_process').spawnSync
#三方库 # 三方库
_ = require 'underscore' _ = require 'underscore'
_.str = require 'underscore.string' _.str = require 'underscore.string'
_.mixin(_.str.exports()); _.mixin(_.str.exports())
request = require 'request' request = require 'request'
bunyan = require 'bunyan' bunyan = require 'bunyan'
log = bunyan.createLogger name: "mycard"
moment = require 'moment' moment = require 'moment'
moment.locale('zh-cn', {
#redis = require 'redis' relativeTime: {
#redisdb = redis.createClient host: "127.0.0.1", port: settings.modules.redis_port 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年'
}
})
#heapdump = require 'heapdump' #heapdump = require 'heapdump'
#配置文件 # 配置
settings = require './config.json' # use nconf to save user config.user.json .
settings.BANNED_user = [] # config.json shouldn't be changed
settings.BANNED_IP = [] nconf = require 'nconf'
settings.modules.hang_timeout = 90 nconf.file('./config.user.json')
defaultconfig = require('./config.json')
nconf.defaults(defaultconfig)
settings = global.settings = nconf.get()
nconf.myset = (settings, path, val) ->
# path should be like "modules:welcome"
nconf.set(path, val)
nconf.save()
log.info("setting changed", path, val) if _.isString(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
return
# ban a user manually and permanently
ban_user = (name) ->
settings.ban.banned_user.push(name)
nconf.myset(settings, "ban:banned_user", settings.ban.banned_user)
bad_ip=0
for room in ROOM_all when room and room.established
for player in room.players
if player and (player.name == name or player.ip == bad_ip)
bad_ip = player.ip
ROOM_bad_ip.push(player.ip)
settings.ban.banned_ip.push(player.ip)
ygopro.stoc_send_chat_to_room(room, "#{player.name} 被系统请出了房间", ygopro.constants.COLORS.RED)
player.destroy()
continue
return
settings.version = parseInt(fs.readFileSync('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16') settings.version = parseInt(fs.readFileSync('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16')
# load the lflist of current date
settings.lflist = (for list in fs.readFileSync('ygopro/lflist.conf', 'utf8').match(/!.*/g) settings.lflist = (for list in fs.readFileSync('ygopro/lflist.conf', 'utf8').match(/!.*/g)
date=list.match(/!([\d\.]+)/)
continue unless date
{date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), tcg: list.indexOf('TCG') != -1}) {date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), tcg: list.indexOf('TCG') != -1})
if settings.modules.enable_cloud_replay
redis = require 'redis'
zlib = require 'zlib'
redisdb = redis.createClient host: "127.0.0.1", port: settings.modules.redis_port
redisdb.on 'error', (err)->
log.warn err
return
if settings.modules.enable_windbot if settings.modules.enable_windbot
settings.modules.windbot = require('./windbot/bots.json').windbots settings.modules.windbots = require('./windbot/bots.json').windbots
#组件 # 组件
ygopro = require './ygopro.js' ygopro = require './ygopro.js'
Room = require './room.js'
roomlist = require './roomlist.js' if settings.modules.enable_websocket_roomlist roomlist = require './roomlist.js' if settings.modules.enable_websocket_roomlist
# cache users of mycard login
users_cache = {} users_cache = {}
#debug模式 端口号+1 # 获取可用内存
debug = false get_memory_usage = ()->
log = null prc_free = spawnSync("free", [])
if process.argv[2] == '--debug' if (prc_free.stdout)
settings.port++ lines = prc_free.stdout.toString().split(/\n/g)
settings.modules.http.port++ if settings.modules.http line = lines[1].split(/\s+/)
log = bunyan.createLogger name: "mycard-debug" total = parseInt(line[1], 10)
else free = parseInt(line[3], 10)
log = bunyan.createLogger name: "mycard" 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
# 定时清理关闭的连接
# the server write data directly to the socket object
# so this is a dumb way to clean data
Graveyard = [] Graveyard = []
tribute = (socket) -> tribute = (socket) ->
...@@ -69,84 +142,513 @@ setInterval ()-> ...@@ -69,84 +142,513 @@ setInterval ()->
return return
, 3000 , 3000
#网络连接 Cloud_replay_ids = []
ROOM_all = []
ROOM_players_oppentlist = {}
ROOM_players_banned = []
ROOM_connected_ip = {}
ROOM_bad_ip = {}
# automatically ban user to use random duel
ROOM_ban_player = (name, ip, reason, countadd = 1)->
bannedplayer = _.find ROOM_players_banned, (bannedplayer)->
ip == bannedplayer.ip
if bannedplayer
bannedplayer.count = bannedplayer.count + countadd
bantime = if bannedplayer.count > 3 then Math.pow(2, bannedplayer.count - 3) * 2 else 0
bannedplayer.time = if moment() < bannedplayer.time then moment(bannedplayer.time).add(bantime, 'm') else moment().add(bantime, 'm')
bannedplayer.reasons.push(reason) if not _.find bannedplayer.reasons, (bannedreason)->
bannedreason == reason
bannedplayer.need_tip = true
else
bannedplayer = {"ip": ip, "time": moment(), "count": countadd, "reasons": [reason], "need_tip": true}
ROOM_players_banned.push(bannedplayer)
#log.info("banned", name, ip, reason, bannedplayer.count)
return
ROOM_find_or_create_by_name = (name, player_ip)->
uname=name.toUpperCase()
if settings.modules.enable_windbot and (uname[0...2] == 'AI' or (!settings.modules.enable_random_duel and uname == ''))
return ROOM_find_or_create_ai(name)
if settings.modules.enable_random_duel and (uname == '' or uname == 'S' or uname == 'M' or 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 = (type, player_ip)->
bannedplayer = _.find ROOM_players_banned, (bannedplayer)->
return player_ip == bannedplayer.ip
if bannedplayer
if bannedplayer.count > 6 and moment() < bannedplayer.time
return {"error": "因为您近期在游戏中多次#{bannedplayer.reasons.join('、')},您已被禁止使用随机对战功能,将在#{moment(bannedplayer.time).fromNow(true)}后解封"}
if bannedplayer.count > 3 and moment() < bannedplayer.time and bannedplayer.need_tip
bannedplayer.need_tip = false
return {"error": "因为您近期在游戏中#{bannedplayer.reasons.join('、')},在#{moment(bannedplayer.time).fromNow(true)}内您随机对战时只能遇到其他违规玩家"}
else if bannedplayer.need_tip
bannedplayer.need_tip = false
return {"error": "系统检测到您近期在游戏中#{bannedplayer.reasons.join('、')},若您违规超过3次,将受到惩罚"}
else if bannedplayer.count > 2
bannedplayer.need_tip = true
max_player = if type == 'T' then 4 else 2
playerbanned = (bannedplayer and bannedplayer.count > 3 and moment() < bannedplayer.time)
result = _.find ROOM_all, (room)->
return room and room.random_type != '' and !room.started and
((type == '' and room.random_type != 'T') or room.random_type == type) and
room.get_playing_player().length < max_player and
(room.get_host() == null or room.get_host().ip != ROOM_players_oppentlist[player_ip]) and
(playerbanned == room.deprecated)
if result
result.welcome = '对手已经在等你了,开始决斗吧!'
#log.info 'found room', player_name
else if get_memory_usage() < 90
type = if type then type else 'S'
name = type + ',RANDOM#' + Math.floor(Math.random() * 100000)
result = new Room(name)
result.random_type = type
result.max_player = max_player
result.welcome = '已建立随机对战房间,正在等待对手!'
result.deprecated = playerbanned
#log.info 'create room', player_name, name
else
return null
if result.random_type=='M' then result.welcome = result.welcome + '\n您进入了比赛模式的房间,我们推荐使用竞技卡组!'
return result
ROOM_find_or_create_ai = (name)->
if name == ''
name = 'AI'
if name[0...3] == 'AI_'
name = 'AI#' + name.slice(3)
namea = name.split('#')
if room = ROOM_find_by_name(name)
return room
else if name == '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, (w)->
w.name == ainame or w.deck == ainame
if !windbot
return { "error": "未找到该AI角色或卡组" }
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": "AI房间名过长,请在建立房间后输入 /ai 来添加AI" }
result = new Room(name)
result.windbot = windbot
return result
ROOM_find_by_name = (name)->
result = _.find ROOM_all, (room)->
return room and room.name == name
#log.info 'find_by_name', name, result
return result
ROOM_find_by_port = (port)->
_.find ROOM_all, (room)->
return room and room.port == port
ROOM_validate = (name)->
client_name_and_pass = name.split('$', 2)
client_name = client_name_and_pass[0]
client_pass = client_name_and_pass[1]
return true if !client_pass
!_.find ROOM_all, (room)->
return false unless room
room_name_and_pass = room.name.split('$', 2)
room_name = room_name_and_pass[0]
room_pass = room_name_and_pass[1]
client_name == room_name and client_pass != room_pass
class Room
constructor: (name, @hostinfo) ->
@name = name
@alive = true
@players = []
@player_datas = []
@status = 'starting'
@started = false
@established = false
@watcher_buffers = []
@recorder_buffers = []
@cloud_replay_id = Math.floor(Math.random()*100000000)
@watchers = []
@random_type = ''
@welcome = ''
ROOM_all.push this
@hostinfo ||=
lflist: if settings.lflist.length then 0 else -1
rule: if settings.modules.enable_TCG_as_default then 2 else 0
mode: 0
enable_priority: false
no_check_deck: false
no_shuffle_deck: false
start_lp: 8000
start_hand: 5
draw_count: 1
time_limit: 180
replay_mode: if settings.modules.tournament_mode.enabled then 1 else 0
if name[0...2] == 'M#'
@hostinfo.mode = 1
else if name[0...2] == 'T#'
@hostinfo.mode = 2
@hostinfo.start_lp = 16000
else if (param = name.match /^(\d)(\d)(T|F)(T|F)(T|F)(\d+),(\d+),(\d+)/i)
@hostinfo.rule = parseInt(param[1])
@hostinfo.mode = parseInt(param[2])
@hostinfo.enable_priority = param[3] == 'T'
@hostinfo.no_check_deck = param[4] == 'T'
@hostinfo.no_shuffle_deck = param[5] == 'T'
@hostinfo.start_lp = parseInt(param[6])
@hostinfo.start_hand = parseInt(param[7])
@hostinfo.draw_count = parseInt(param[8])
else if ((param = name.match /(.+)#/) != null)
rule = param[1].toUpperCase()
if (rule.match /(^|,|,)(M|MATCH)(,|,|$)/)
@hostinfo.mode = 1
if (rule.match /(^|,|,)(T|TAG)(,|,|$)/)
@hostinfo.mode = 2
@hostinfo.start_lp = 16000
if (rule.match /(^|,|,)(TCGONLY|TO)(,|,|$)/)
@hostinfo.rule = 1
@hostinfo.lflist = _.findIndex settings.lflist, (list)-> list.tcg
if (rule.match /(^|,|,)(OCGONLY|OO)(,|,|$)/)
@hostinfo.rule = 0
if (rule.match /(^|,|,)(OT|TCG)(,|,|$)/)
@hostinfo.rule = 2
if (param = rule.match /(^|,|,)LP(\d+)(,|,|$)/)
start_lp = parseInt(param[2])
if (start_lp <= 0) then start_lp = 1
if (start_lp >= 99999) then start_lp = 99999
@hostinfo.start_lp = start_lp
if (param = rule.match /(^|,|,)(TIME|TM|TI)(\d+)(,|,|$)/)
time_limit = parseInt(param[3])
if (time_limit < 0) then time_limit = 180
if (time_limit >= 1 and time_limit <= 60) then time_limit = time_limit * 60
if (time_limit >= 999) then time_limit = 999
@hostinfo.time_limit = time_limit
if (param = rule.match /(^|,|,)(START|ST)(\d+)(,|,|$)/)
start_hand = parseInt(param[3])
if (start_hand <= 0) then start_hand = 1
if (start_hand >= 40) then start_hand = 40
@hostinfo.start_hand = start_hand
if (param = rule.match /(^|,|,)(DRAW|DR)(\d+)(,|,|$)/)
draw_count = parseInt(param[3])
if (draw_count >= 35) then draw_count = 35
@hostinfo.draw_count = draw_count
if (param = rule.match /(^|,|,)(LFLIST|LF)(\d+)(,|,|$)/)
lflist = parseInt(param[3]) - 1
@hostinfo.lflist = lflist
if (rule.match /(^|,|,)(NOLFLIST|NF)(,|,|$)/)
@hostinfo.lflist = -1
if (rule.match /(^|,|,)(NOUNIQUE|NU)(,|,|$)/)
@hostinfo.rule = 3
if (rule.match /(^|,|,)(NOCHECK|NC)(,|,|$)/)
@hostinfo.no_check_deck = true
if (rule.match /(^|,|,)(NOSHUFFLE|NS)(,|,|$)/)
@hostinfo.no_shuffle_deck = true
if (rule.match /(^|,|,)(IGPRIORITY|PR)(,|,|$)/)
@hostinfo.enable_priority = true
param = [0, @hostinfo.lflist, @hostinfo.rule, @hostinfo.mode, (if @hostinfo.enable_priority then 'T' else 'F'),
(if @hostinfo.no_check_deck then 'T' else 'F'), (if @hostinfo.no_shuffle_deck then 'T' else 'F'),
@hostinfo.start_lp, @hostinfo.start_hand, @hostinfo.draw_count, @hostinfo.time_limit, @hostinfo.replay_mode]
try
@process = spawn './ygopro', param, {cwd: 'ygopro'}
@process.on 'exit', (code)=>
@disconnector = 'server' unless @disconnector
this.delete()
return
@process.stdout.setEncoding('utf8')
@process.stdout.once 'data', (data)=>
@established = true
roomlist.create(this) if !@private and settings.modules.enable_websocket_roomlist
@port = parseInt data
_.each @players, (player)=>
player.server.connect @port, '127.0.0.1', ->
player.server.write buffer for buffer in player.pre_establish_buffers
player.established = true
player.pre_establish_buffers = []
return
return
if @windbot
setTimeout ()=>
@add_windbot(@windbot)
, 200
return
@process.stderr.on 'data', (data)=>
data = "Debug: " + data
data = data.replace(/\n$/, "")
log.info "YGOPRO " + data
ygopro.stoc_send_chat_to_room this, data, ygopro.constants.COLORS.RED
@has_ygopro_error = true
return
catch
@error = "建立房间失败,请重试"
delete: ->
return if @deleted
#log.info 'room-delete', this.name, ROOM_all.length
if @player_datas.length and settings.modules.enable_cloud_replay
replay_id = @cloud_replay_id
if @has_ygopro_error
log_rep_id = true
player_names=@player_datas[0].name + (if @player_datas[2] then "+" + @player_datas[2].name else "") +
" VS " +
(if @player_datas[1] then @player_datas[1].name else "AI") +
(if @player_datas[3] then "+" + @player_datas[3].name else "")
player_ips=[]
_.each @player_datas, (player)->
player_ips.push(player.ip)
return
recorder_buffer=Buffer.concat(@recorder_buffers)
zlib.deflate recorder_buffer, (err, replay_buffer) ->
replay_buffer=replay_buffer.toString('binary')
#log.info err, replay_buffer
date_time=moment().format('YYYY-MM-DD HH:mm:ss')
#replay_id=Math.floor(Math.random()*100000000)
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, (player_ip)->
return if _.contains(recorded_ip, player_ip)
recorded_ip.push player_ip
redisdb.lpush(player_ip+":replays", replay_id)
return
if log_rep_id
log.info "error replay: R#" + replay_id
return
@watcher_buffers = []
@recorder_buffers = []
@players = []
@watcher.destroy() if @watcher
@recorder.destroy() if @recorder
@deleted = true
index = _.indexOf(ROOM_all, this)
ROOM_all[index] = null unless index == -1
#ROOM_all.splice(index, 1) unless index == -1
roomlist.delete @name if !@private and !@started and @established and settings.modules.enable_websocket_roomlist
return
get_playing_player: ->
playing_player = []
_.each @players, (player)->
if player.pos < 4 then playing_player.push player
return
return playing_player
get_host: ->
host_player = null
_.each @players, (player)->
if player.is_host then host_player = player
return
return host_player
add_windbot: (botdata)->
@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(@name)}"
, (error, response, body)=>
if error
log.warn 'windbot add error', error, this.name, response
ygopro.stoc_send_chat_to_room(this, "添加AI失败,可尝试输入 /ai 重新添加", ygopro.constants.COLORS.RED)
#else
#log.info "windbot added"
return
return
connect: (client)->
@players.push client
if @random_type
client.abuse_count = 0
host_player = @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 @established
roomlist.update(this) if !@private and !@started and settings.modules.enable_websocket_roomlist
client.server.connect @port, '127.0.0.1', ->
client.server.write buffer for buffer in client.pre_establish_buffers
client.established = true
client.pre_establish_buffers = []
return
return
disconnect: (client, error)->
if client.is_post_watcher
ygopro.stoc_send_chat_to_room this, "#{client.name} 退出了观战" + if error then ": #{error}" else ''
index = _.indexOf(@watchers, client)
@watchers.splice(index, 1) unless index == -1
#client.room = null
else
index = _.indexOf(@players, client)
@players.splice(index, 1) unless index == -1
#log.info(@started,@disconnector,@random_type)
if @started and @disconnector != 'server' and @random_type and (client.pos < 4 or client.is_host)
ROOM_ban_player(client.name, client.ip, "强退")
if @players.length and !(@windbot and client.is_host)
ygopro.stoc_send_chat_to_room this, "#{client.name} 离开了游戏" + if error then ": #{error}" else ''
roomlist.update(this) if !@private and !@started and settings.modules.enable_websocket_roomlist
#client.room = null
else
@process.kill()
#client.room = null
this.delete()
return
# 网络连接
net.createServer (client) -> net.createServer (client) ->
client.ip = client.remoteAddress
connect_count = ROOM_connected_ip[client.ip] or 0
if client.ip != '::ffff:127.0.0.1'
connect_count++
ROOM_connected_ip[client.ip] = connect_count
#log.info "connect", client.ip, ROOM_connected_ip[client.ip]
# server stand for the connection to ygopro server process
server = new net.Socket() server = new net.Socket()
client.server = server client.server = server
client.setTimeout(300000) #5分钟 client.setTimeout(2000) #连接前超时2秒
#释放处理 # 释放处理
client.on 'close', (had_error) -> client.on 'close', (had_error) ->
#log.info "client closed", client.name, had_error #log.info "client closed", client.name, had_error
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
#log.info "disconnect", client.ip, ROOM_connected_ip[client.ip]
tribute(client) tribute(client)
unless client.closed unless client.closed
client.closed = true client.closed = true
client.room.disconnect(client) if client.room room.disconnect(client) if room
server.end() server.destroy()
return return
client.on 'error', (error)-> client.on 'error', (error)->
#log.info "client error", client.name, error #log.info "client error", client.name, error
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
#log.info "err disconnect", client.ip, ROOM_connected_ip[client.ip]
tribute(client) tribute(client)
unless client.closed unless client.closed
client.closed = error client.closed = error
client.room.disconnect(client, error) if client.room room.disconnect(client, error) if room
server.end() server.destroy()
return return
client.on 'timeout', ()-> client.on 'timeout', ()->
server.end() server.destroy()
return return
server.on 'close', (had_error) -> server.on 'close', (had_error) ->
#log.info "server closed", client.name, had_error #log.info "server closed", client.name, had_error
room=ROOM_all[client.rid]
#log.info "server close", client.ip, ROOM_connected_ip[client.ip]
tribute(server) tribute(server)
client.room.disconnector = 'server' room.disconnector = 'server' if room
server.closed = true unless server.closed server.closed = true unless server.closed
unless client.closed unless client.closed
ygopro.stoc_send_chat(client, "服务器关闭了连接", 11) ygopro.stoc_send_chat(client, "服务器关闭了连接", ygopro.constants.COLORS.RED)
client.end() client.destroy()
return return
server.on 'error', (error)-> server.on 'error', (error)->
#log.info "server error", client.name, error #log.info "server error", client.name, error
room=ROOM_all[client.rid]
#log.info "server err close", client.ip, ROOM_connected_ip[client.ip]
tribute(server) tribute(server)
client.room.disconnector = 'server' room.disconnector = 'server' if room
server.closed = error server.closed = error
unless client.closed unless client.closed
ygopro.stoc_send_chat(client, "服务器错误: #{error}", 11) ygopro.stoc_send_chat(client, "服务器错误: #{error}", ygopro.constants.COLORS.RED)
client.end() client.destroy()
return return
###
client.open_cloud_replay= (err, replay)-> if ROOM_bad_ip[client.ip] > 5 or ROOM_connected_ip[client.ip] > 10
if err or !replay log.info 'BAD IP', client.ip
ygopro.stoc_send_chat(client,"没有找到录像", 11) client.destroy()
ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1
code: 2
}
client.end()
return
replay_buffer=new Buffer(replay.replay_buffer,'binary')
ygopro.stoc_send_chat(client,"正在观看云录像:R##{replay.replay_id} #{replay.player_names} #{replay.date_time}", 14)
client.write replay_buffer
client.end()
return return
###
#需要重构
#客户端到服务端(ctos)协议分析
ctos_buffer = new Buffer(0)
ctos_message_length = 0
ctos_proto = 0
if settings.modules.enable_cloud_replay
client.open_cloud_replay= (err, replay)->
if err or !replay
ygopro.stoc_die(client, "没有找到录像")
return
redisdb.expire("replay:"+replay.replay_id, 60*60*48)
buffer=new Buffer(replay.replay_buffer,'binary')
zlib.unzip buffer, (err, replay_buffer) ->
if err
log.info "cloud replay unzip error: " + err
ygopro.stoc_send_chat(client, "播放录像出错", ygopro.constants.COLORS.RED)
client.destroy()
return
ygopro.stoc_send_chat(client, "正在观看云录像:R##{replay.replay_id} #{replay.player_names} #{replay.date_time}", ygopro.constants.COLORS.BABYBLUE)
client.write replay_buffer
setTimeout (()->
client.destroy()
return), 5000
return
return
# 需要重构
# 客户端到服务端(ctos)协议分析
client.pre_establish_buffers = new Array() client.pre_establish_buffers = new Array()
client.on 'data', (data) -> client.on 'data', (ctos_buffer) ->
if client.is_post_watcher if client.is_post_watcher
client.room.watcher.write data room=ROOM_all[client.rid]
room.watcher.write ctos_buffer if room
else else
ctos_buffer = Buffer.concat([ctos_buffer, data], ctos_buffer.length + data.length) #buffer的错误使用方式,好孩子不要学 #ctos_buffer = new Buffer(0)
ctos_message_length = 0
ctos_proto = 0
#ctos_buffer = Buffer.concat([ctos_buffer, data], ctos_buffer.length + data.length) #buffer的错误使用方式,好孩子不要学
datas = [] datas = []
...@@ -157,15 +659,17 @@ net.createServer (client) -> ...@@ -157,15 +659,17 @@ net.createServer (client) ->
if ctos_buffer.length >= 2 if ctos_buffer.length >= 2
ctos_message_length = ctos_buffer.readUInt16LE(0) ctos_message_length = ctos_buffer.readUInt16LE(0)
else else
log.warn("bad ctos_buffer length", client.ip) unless ctos_buffer.length == 0
break break
else if ctos_proto == 0 else if ctos_proto == 0
if ctos_buffer.length >= 3 if ctos_buffer.length >= 3
ctos_proto = ctos_buffer.readUInt8(2) ctos_proto = ctos_buffer.readUInt8(2)
else else
log.warn("bad ctos_proto length", client.ip)
break break
else else
if ctos_buffer.length >= 2 + ctos_message_length if ctos_buffer.length >= 2 + ctos_message_length
#console.log "CTOS", ygopro.constants.CTOS[ctos_proto] #console.log "CTOS", ygopro.constants.CTOS[ctos_proto]
cancel = false cancel = false
if ygopro.ctos_follows[ctos_proto] if ygopro.ctos_follows[ctos_proto]
b = ctos_buffer.slice(3, ctos_message_length - 1 + 3) b = ctos_buffer.slice(3, ctos_message_length - 1 + 3)
...@@ -182,13 +686,19 @@ net.createServer (client) -> ...@@ -182,13 +686,19 @@ net.createServer (client) ->
ctos_message_length = 0 ctos_message_length = 0
ctos_proto = 0 ctos_proto = 0
else else
log.warn("bad ctos_message length", client.ip, ctos_buffer.length, ctos_message_length, ctos_proto) if ctos_message_length != 17735
break break
looplimit++ looplimit++
#log.info(looplimit) #log.info(looplimit)
if looplimit > 800 if looplimit > 800 or ROOM_bad_ip[client.ip] > 5
log.info("error ctos", client.name) log.info("error ctos", client.name, client.ip)
server.end() 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 break
if client.established if client.established
...@@ -198,16 +708,16 @@ net.createServer (client) -> ...@@ -198,16 +708,16 @@ net.createServer (client) ->
return return
#服务端到客户端(stoc) # 服务端到客户端(stoc)
stoc_buffer = new Buffer(0) server.on 'data', (stoc_buffer)->
stoc_message_length = 0 #stoc_buffer = new Buffer(0)
stoc_proto = 0 stoc_message_length = 0
stoc_proto = 0
server.on 'data', (data)-> #stoc_buffer = Buffer.concat([stoc_buffer, data], stoc_buffer.length + data.length) #buffer的错误使用方式,好孩子不要学
stoc_buffer = Buffer.concat([stoc_buffer, data], stoc_buffer.length + data.length) #buffer的错误使用方式,好孩子不要学
#unless ygopro.stoc_follows[stoc_proto] and ygopro.stoc_follows[stoc_proto].synchronous #unless ygopro.stoc_follows[stoc_proto] and ygopro.stoc_follows[stoc_proto].synchronous
client.write data #client.write data
datas = []
looplimit = 0 looplimit = 0
...@@ -216,46 +726,61 @@ net.createServer (client) -> ...@@ -216,46 +726,61 @@ net.createServer (client) ->
if stoc_buffer.length >= 2 if stoc_buffer.length >= 2
stoc_message_length = stoc_buffer.readUInt16LE(0) stoc_message_length = stoc_buffer.readUInt16LE(0)
else else
log.warn("bad stoc_buffer length", client.ip) unless stoc_buffer.length == 0
break break
else if stoc_proto == 0 else if stoc_proto == 0
if stoc_buffer.length >= 3 if stoc_buffer.length >= 3
stoc_proto = stoc_buffer.readUInt8(2) stoc_proto = stoc_buffer.readUInt8(2)
else else
log.warn("bad stoc_proto length", client.ip)
break break
else else
if stoc_buffer.length >= 2 + stoc_message_length if stoc_buffer.length >= 2 + stoc_message_length
#console.log "STOC", ygopro.constants.STOC[stoc_proto] #console.log "STOC", ygopro.constants.STOC[stoc_proto]
cancel = false
stanzas = stoc_proto stanzas = stoc_proto
if ygopro.stoc_follows[stoc_proto] if ygopro.stoc_follows[stoc_proto]
b = stoc_buffer.slice(3, stoc_message_length - 1 + 3) b = stoc_buffer.slice(3, stoc_message_length - 1 + 3)
if struct = ygopro.structs[ygopro.proto_structs.STOC[ygopro.constants.STOC[stoc_proto]]] if struct = ygopro.structs[ygopro.proto_structs.STOC[ygopro.constants.STOC[stoc_proto]]]
struct._setBuff(b) struct._setBuff(b)
ygopro.stoc_follows[stoc_proto].callback b, _.clone(struct.fields), client, server if ygopro.stoc_follows[stoc_proto].synchronous
cancel = ygopro.stoc_follows[stoc_proto].callback b, _.clone(struct.fields), client, server
else
ygopro.stoc_follows[stoc_proto].callback b, _.clone(struct.fields), client, server
else else
ygopro.stoc_follows[stoc_proto].callback b, null, client, server if ygopro.stoc_follows[stoc_proto].synchronous
cancel = ygopro.stoc_follows[stoc_proto].callback b, null, client, server
else
ygopro.stoc_follows[stoc_proto].callback b, null, client, server
datas.push stoc_buffer.slice(0, 2 + stoc_message_length) unless cancel
stoc_buffer = stoc_buffer.slice(2 + stoc_message_length) stoc_buffer = stoc_buffer.slice(2 + stoc_message_length)
stoc_message_length = 0 stoc_message_length = 0
stoc_proto = 0 stoc_proto = 0
else else
log.warn("bad stoc_message length", client.ip)
break break
looplimit++ looplimit++
#log.info(looplimit) #log.info(looplimit)
if looplimit > 800 if looplimit > 800
log.info("error stoc", client.name) log.info("error stoc", client.name)
server.end() server.destroy()
break break
client.write buffer for buffer in datas
return return
return return
.listen settings.port, -> .listen settings.port, ->
log.info "server started", settings.port log.info "server started", settings.port
return return
#功能模块 # 功能模块
# return true to cancel a synchronous message
ygopro.ctos_follow 'PLAYER_INFO', true, (buffer, info, client, server)-> ygopro.ctos_follow 'PLAYER_INFO', true, (buffer, info, client, server)->
name = info.name.split("$")[0]; # checkmate use username$password, but here don't
# so remove the password
name = info.name.split("$")[0]
struct = ygopro.structs["CTOS_PlayerInfo"] struct = ygopro.structs["CTOS_PlayerInfo"]
struct._setBuff(buffer) struct._setBuff(buffer)
struct.set("name", name) struct.set("name", name)
...@@ -266,107 +791,69 @@ ygopro.ctos_follow 'PLAYER_INFO', true, (buffer, info, client, server)-> ...@@ -266,107 +791,69 @@ ygopro.ctos_follow 'PLAYER_INFO', true, (buffer, info, client, server)->
ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)->
#log.info info #log.info info
if settings.modules.stop if settings.modules.stop
ygopro.stoc_send_chat(client, settings.modules.stop, 11) ygopro.stoc_die(client, settings.modules.stop)
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1 else if info.pass.toUpperCase()=="R" and settings.modules.enable_cloud_replay
code: 2 ygopro.stoc_send_chat(client,"以下是您近期的云录像,密码处输入 R#录像编号 即可观看", ygopro.constants.COLORS.BABYBLUE)
} redisdb.lrange client.ip+":replays", 0, 2, (err, result)->
client.end() _.each result, (replay_id,id)->
redisdb.hgetall "replay:"+replay_id, (err, replay)->
### if err or !replay
else if info.pass.toUpperCase()=="R" log.info "cloud replay getall error: " + err if err
ygopro.stoc_send_chat(client,"以下是您近期的云录像,密码处输入 R#录像编号 即可观看", 14) return
redisdb.lrange client.remoteAddress+":replays", 0, 2, (err, result)=> ygopro.stoc_send_chat(client,"<#{id-0+1}> R##{replay_id} #{replay.player_names} #{replay.date_time}", ygopro.constants.COLORS.BABYBLUE)
_.each result, (replay_id,id)=>
redisdb.hgetall "replay:"+replay_id, (err, replay)=>
ygopro.stoc_send_chat(client,"<#{id-0+1}> R##{replay_id} #{replay.player_names} #{replay.date_time}", 14)
return return
return return
return return
#强行等待异步执行完毕_(:з」∠)_ # 强行等待异步执行完毕_(:з」∠)_
setTimeout (()=> setTimeout (()->
ygopro.stoc_send client, 'ERROR_MSG',{ ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1 msg: 1
code: 2 code: 2
} }
client.end()), 500 client.destroy()
return), 500
else if info.pass[0...2].toUpperCase()=="R#" else if info.pass[0...2].toUpperCase()=="R#" and settings.modules.enable_cloud_replay
replay_id=info.pass.split("#")[1] replay_id=info.pass.split("#")[1]
if (replay_id>0 and replay_id<=3) if (replay_id>0 and replay_id<=9)
redisdb.lindex client.remoteAddress+":replays", replay_id-1, (err, replay_id)=> redisdb.lindex client.ip+":replays", replay_id-1, (err, replay_id)->
if err or !replay_id
log.info "cloud replay replayid error: " + err if err
ygopro.stoc_die(client, "没有找到录像")
return
redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
return return
else if replay_id else if replay_id
redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
else else
ygopro.stoc_send_chat(client,"没有找到录像", 11) ygopro.stoc_die(client, "没有找到录像")
ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1 else if info.pass.toUpperCase()=="W" and settings.modules.enable_cloud_replay
code: 2 replay_id=Cloud_replay_ids[Math.floor(Math.random()*Cloud_replay_ids.length)]
} redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
client.end()
###
else if info.version != settings.version else if info.version != settings.version and (info.version != 4921 or settings.version != 4922) #YGOMobile不更新,强行兼容
ygopro.stoc_send_chat(client, settings.modules.update, 11) ygopro.stoc_send_chat(client, settings.modules.update, ygopro.constants.COLORS.RED)
ygopro.stoc_send client, 'ERROR_MSG', { ygopro.stoc_send client, 'ERROR_MSG', {
msg: 4 msg: 4
code: settings.version code: settings.version
} }
client.end() client.destroy()
else if !info.pass.length and !settings.modules.enable_random_duel else if !info.pass.length and !settings.modules.enable_random_duel and !settings.modules.enable_windbot
ygopro.stoc_send_chat(client, "房间名为空,请填写主机密码", 11) ygopro.stoc_die(client, "房间名为空,请在主机密码处填写房间名")
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
else if settings.modules.windbot and info.pass[0...2] == 'AI'
if info.pass.length > 3 and info.pass[0...3] == 'AI#' or info.pass[0...3] == 'AI_'
name = info.pass.slice(3)
windbot = _.sample _.filter settings.modules.windbot, (w)->
w.name == name or w.deck == name
if !windbot
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Windbot Name)', 11)
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return
else
windbot = _.sample settings.modules.windbot
room = Room.find_or_create_by_name('AI#' + Math.random().toString()) # 这个 AI# 没有特殊作用, 仅作为标记 else if info.pass.length and settings.modules.mycard_auth and info.pass[0...2] != 'AI'
room.windbot = windbot ygopro.stoc_send_chat(client, '正在读取用户信息...', ygopro.constants.COLORS.BABYBLUE)
room.private = true
client.room = room
client.room.connect(client)
else if info.pass.length and settings.modules.mycard_auth
ygopro.stoc_send_chat(client, '正在读取用户信息...', 11)
if info.pass.length <= 8 if info.pass.length <= 8
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Length)', 11) ygopro.stoc_die(client, '主机密码不正确 (Invalid Length)')
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return return
buffer = new Buffer(info.pass[0...8], 'base64') buffer = new Buffer(info.pass[0...8], 'base64')
if buffer.length != 6 if buffer.length != 6
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Payload Length)', 11) ygopro.stoc_die(client, '主机密码不正确 (Invalid Payload Length)')
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return return
check = (buf)-> check = (buf)->
...@@ -378,12 +865,7 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ...@@ -378,12 +865,7 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)->
finish = (buffer)-> finish = (buffer)->
action = buffer.readUInt8(1) >> 4 action = buffer.readUInt8(1) >> 4
if buffer != decrypted_buffer and action in [1, 2, 4] if buffer != decrypted_buffer and action in [1, 2, 4]
ygopro.stoc_send_chat(client, '主机密码不正确 (Unauthorized)', 11) ygopro.stoc_die(client, '主机密码不正确 (Unauthorized)')
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return return
# 1 create public room # 1 create public room
...@@ -392,14 +874,9 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ...@@ -392,14 +874,9 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)->
# 4 join match # 4 join match
switch action switch action
when 1,2 when 1,2
name = crypto.createHash('md5').update(info.pass + client.name).digest('base64')[0...10].replace('+', '-').replace('/', '_'); name = crypto.createHash('md5').update(info.pass + client.name).digest('base64')[0...10].replace('+', '-').replace('/', '_')
if Room.find_by_name(name) if ROOM_find_by_name(name)
ygopro.stoc_send_chat(client, '主机密码不正确 (Already Existed)', 11) ygopro.stoc_die(client, '主机密码不正确 (Already Existed)')
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return return
opt1 = buffer.readUInt8(2) opt1 = buffer.readUInt8(2)
...@@ -423,31 +900,30 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ...@@ -423,31 +900,30 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)->
room.private = action == 2 room.private = action == 2
when 3 when 3
name = info.pass.slice(8) name = info.pass.slice(8)
room = Room.find_by_name(name) room = ROOM_find_by_name(name)
if(!room) if(!room)
ygopro.stoc_send_chat(client, '主机密码不正确 (Not Found)', 11) ygopro.stoc_die(client, '主机密码不正确 (Not Found)')
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return return
when 4 when 4
room = Room.find_or_create_by_name('M#' + info.pass.slice(8)) room = ROOM_find_or_create_by_name('M#' + info.pass.slice(8))
room.private = true room.private = true
else else
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Action)', 11) ygopro.stoc_die(client, '主机密码不正确 (Invalid Action)')
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return return
client.room = room
client.room.connect(client) if !room
ygopro.stoc_die(client, "服务器已经爆满,请稍候再试")
else if room.error
ygopro.stoc_die(client, room.error)
else
#client.room = room
client.setTimeout(300000) #连接后超时5分钟
client.rid = _.indexOf(ROOM_all, room)
room.connect(client)
return
if id = users_cache[client.name] if id = users_cache[client.name]
secret = id % 65535 + 1; secret = id % 65535 + 1
decrypted_buffer = new Buffer(6) decrypted_buffer = new Buffer(6)
for i in [0, 2, 4] for i in [0, 2, 4]
decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i) decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i)
...@@ -459,13 +935,13 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ...@@ -459,13 +935,13 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)->
baseUrl: settings.modules.mycard_auth, baseUrl: settings.modules.mycard_auth,
url: '/users/' + encodeURIComponent(client.name) + '.json', url: '/users/' + encodeURIComponent(client.name) + '.json',
qs: qs:
api_key: 'dc7298a754828b3d26b709f035a0eeceb43e73cbd8c4fa8dec18951f8a95d2bc', api_key: process.env.MYCARD_AUTH_KEY,
api_username: client.name, api_username: client.name,
skip_track_visit: true skip_track_visit: true
json: true json: true
, (error, response, body)-> , (error, response, body)->
if body and body.user if body and body.user
secret = body.user.id % 65535 + 1; secret = body.user.id % 65535 + 1
decrypted_buffer = new Buffer(6) decrypted_buffer = new Buffer(6)
for i in [0, 2, 4] for i in [0, 2, 4]
decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i) decrypted_buffer.writeUInt16LE(buffer.readUInt16LE(i) ^ secret, i)
...@@ -475,102 +951,119 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ...@@ -475,102 +951,119 @@ ygopro.ctos_follow 'JOIN_GAME', false, (buffer, info, client, server)->
# buffer != decrypted_buffer ==> auth failed # buffer != decrypted_buffer ==> auth failed
if !check(buffer) if !check(buffer)
ygopro.stoc_send_chat(client, '主机密码不正确 (Checksum Failed)', 11) ygopro.stoc_die(client, '主机密码不正确 (Checksum Failed)')
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
return return
users_cache[client.name] = body.user.id users_cache[client.name] = body.user.id
finish(buffer) finish(buffer)
else if info.pass.length && !Room.validate(info.pass) else if !client.name or client.name==""
#ygopro.stoc_send client, 'ERROR_MSG',{ ygopro.stoc_die(client, "请输入正确的用户名")
# msg: 1
# code: 1 #这返错有问题,直接双ygopro直连怎么都正常,在这里就经常弹不出提示 else if ROOM_connected_ip[client.ip] > 5
#} log.warn("MULTI LOGIN", client.name, client.ip)
ygopro.stoc_send_chat(client, "房间密码不正确", 11) ygopro.stoc_die(client, "同时开启的客户端数量过多 " + client.ip)
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1 else if _.indexOf(settings.ban.banned_user, client.name) > -1 #账号被封
code: 2 settings.ban.banned_ip.push(client.ip)
} log.warn("BANNED USER LOGIN", client.name, client.ip)
client.end() ygopro.stoc_die(client, "您的账号已被封禁")
else if client.name == '[INCORRECT]' #模拟用户验证 else if _.indexOf(settings.ban.banned_ip, client.ip) > -1 #IP被封
ygopro.stoc_send client, 'ERROR_MSG', { log.warn("BANNED IP LOGIN", client.name, client.ip)
msg: 1 ygopro.stoc_die(client, "您的账号已被封禁")
code: 2
} else if _.any(settings.ban.badword_level3, (badword) ->
client.end() regexp = new RegExp(badword, 'i')
return name.match(regexp)
else if _.indexOf(settings.BANNED_user, client.name) > -1 #账号被封 , name = client.name)
settings.BANNED_IP.push(client.remoteAddress) log.warn("BAD NAME LEVEL 3", client.name, client.ip)
log.info("BANNED USER LOGIN", client.name, client.remoteAddress) ygopro.stoc_die(client, "您的用户名存在不适当的内容")
ygopro.stoc_send_chat(client, "您的账号已被封禁", 11)
ygopro.stoc_send client, 'ERROR_MSG', { else if _.any(settings.ban.badword_level2, (badword) ->
msg: 1 regexp = new RegExp(badword, 'i')
code: 2 return name.match(regexp)
} , name = client.name)
client.end() log.warn("BAD NAME LEVEL 2", client.name, client.ip)
ygopro.stoc_die(client, "您的用户名存在不适当的内容")
else if _.indexOf(settings.BANNED_IP, client.remoteAddress) > -1 #IP被封
log.info("BANNED IP LOGIN", client.name, client.remoteAddress) else if _.any(settings.ban.badword_level1, (badword) ->
ygopro.stoc_send_chat(client, "您的账号已被封禁", 11) regexp = new RegExp(badword, 'i')
ygopro.stoc_send client, 'ERROR_MSG', { return name.match(regexp)
msg: 1 , name = client.name)
code: 2 log.warn("BAD NAME LEVEL 1", client.name, client.ip)
} ygopro.stoc_die(client, "您的用户名存在不适当的内容,请注意更改")
client.end()
else if info.pass.length && !ROOM_validate(info.pass)
ygopro.stoc_die(client, "房间密码不正确")
else else
#log.info 'join_game',info.pass, client.name if info.version == 4921 and settings.version == 4922 #YGOMobile不更新,强行兼容
room = Room.find_or_create_by_name(info.pass, client.remoteAddress) info.version = settings.version
struct = ygopro.structs["CTOS_JoinGame"]
struct._setBuff(buffer)
struct.set("version", info.version)
buffer = struct.buffer
#ygopro.stoc_send_chat(client, "您的版本号过低,可能出现未知问题,电脑用户请升级版本,YGOMobile用户请等待作者更新", ygopro.constants.COLORS.BABYBLUE)
#log.info 'join_game',info.pass, client.name
room = ROOM_find_or_create_by_name(info.pass, client.ip)
if !room if !room
ygopro.stoc_send_chat(client, "服务器已经爆满,请稍候再试", 11) ygopro.stoc_die(client, "服务器已经爆满,请稍候再试")
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
else if room.error else if room.error
ygopro.stoc_send_chat(client, room.error, 11) ygopro.stoc_die(client, room.error)
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
else if room.started else if room.started
if settings.modules.post_start_watching if settings.modules.enable_halfway_watch
client.room = room client.setTimeout(300000) #连接后超时5分钟
client.rid = _.indexOf(ROOM_all, room)
client.is_post_watcher = true client.is_post_watcher = true
ygopro.stoc_send_chat_to_room client.room, "#{client.name} 加入了观战" ygopro.stoc_send_chat_to_room(room, "#{client.name} 加入了观战")
client.room.watchers.push client room.watchers.push client
ygopro.stoc_send_chat client, "观战中", 14 ygopro.stoc_send_chat(client, "观战中", ygopro.constants.COLORS.BABYBLUE)
for buffer in client.room.watcher_buffers for buffer in room.watcher_buffers
client.write buffer client.write buffer
else else
ygopro.stoc_send_chat(client, "决斗已开始,不允许观战", 11) ygopro.stoc_die(client, "决斗已开始,不允许观战")
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
}
client.end()
else else
client.room = room client.setTimeout(300000) #连接后超时5分钟
client.room.connect(client) client.rid = _.indexOf(ROOM_all, room)
room.connect(client)
return return
ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server)->
#欢迎信息 #欢迎信息
return unless client.room room=ROOM_all[client.rid]
return unless room
if settings.modules.welcome if settings.modules.welcome
ygopro.stoc_send_chat client, settings.modules.welcome ygopro.stoc_send_chat(client, settings.modules.welcome, ygopro.constants.COLORS.GREEN)
if client.room.welcome if room.welcome
ygopro.stoc_send_chat client, client.room.welcome, 14 ygopro.stoc_send_chat(client, room.welcome, ygopro.constants.COLORS.BABYBLUE)
#log.info(ROOM_all)
if !room.recorder
room.recorder = recorder = net.connect room.port, ->
ygopro.ctos_send recorder, 'PLAYER_INFO', {
name: "Marshtomp"
}
ygopro.ctos_send recorder, 'JOIN_GAME', {
version: settings.version,
gameid: 2577,
some_unknown_mysterious_fucking_thing: 0
pass: ""
}
ygopro.ctos_send recorder, 'HS_TOOBSERVER'
return
if settings.modules.post_start_watching and !client.room.watcher recorder.on 'data', (data)->
client.room.watcher = watcher = net.connect client.room.port, -> room=ROOM_all[client.rid]
return unless room and settings.modules.enable_cloud_replay
room.recorder_buffers.push data
return
recorder.on 'error', (error)->
return
if settings.modules.enable_halfway_watch and !room.watcher
room.watcher = watcher = net.connect room.port, ->
ygopro.ctos_send watcher, 'PLAYER_INFO', { ygopro.ctos_send watcher, 'PLAYER_INFO', {
name: "the Big Brother" name: "the Big Brother"
} }
...@@ -584,9 +1077,10 @@ ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ...@@ -584,9 +1077,10 @@ ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server)->
return return
watcher.on 'data', (data)-> watcher.on 'data', (data)->
return unless client.room room=ROOM_all[client.rid]
client.room.watcher_buffers.push data return unless room
for w in client.room.watchers room.watcher_buffers.push data
for w in room.watchers
w.write data if w #a WTF fix w.write data if w #a WTF fix
return return
...@@ -595,9 +1089,8 @@ ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server)-> ...@@ -595,9 +1089,8 @@ ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server)->
return return
return return
#登场台词 # 登场台词
if settings.modules.dialogues load_dialogues = () ->
dialogues = {}
request request
url: settings.modules.dialogues url: settings.modules.dialogues
json: true json: true
...@@ -607,102 +1100,116 @@ if settings.modules.dialogues ...@@ -607,102 +1100,116 @@ if settings.modules.dialogues
else if error or !body else if error or !body
log.warn 'dialogues error', error, response log.warn 'dialogues error', error, response
else else
#log.info "dialogues loaded", _.size body nconf.myset(settings, "dialogues", body)
dialogues = body log.info "dialogues loaded", _.size body
return return
return
if settings.modules.dialogues
load_dialogues()
ygopro.stoc_follow 'GAME_MSG', false, (buffer, info, client, server)-> ygopro.stoc_follow 'GAME_MSG', false, (buffer, info, client, server)->
room=ROOM_all[client.rid]
return unless room
msg = buffer.readInt8(0) msg = buffer.readInt8(0)
if msg >= 10 and msg < 30 #SELECT开头的消息 if msg >= 10 and msg < 30 #SELECT开头的消息
client.room.waiting_for_player = client room.waiting_for_player = client
client.room.last_active_time = moment() room.last_active_time = moment()
#log.info("#{ygopro.constants.MSG[msg]}等待#{client.room.waiting_for_player.name}") #log.info("#{ygopro.constants.MSG[msg]}等待#{room.waiting_for_player.name}")
#log.info 'MSG', ygopro.constants.MSG[msg] #log.info 'MSG', ygopro.constants.MSG[msg]
if ygopro.constants.MSG[msg] == 'START' if ygopro.constants.MSG[msg] == 'START'
playertype = buffer.readUInt8(1) playertype = buffer.readUInt8(1)
client.is_first = !(playertype & 0xf); client.is_first = !(playertype & 0xf)
client.lp = client.room.hostinfo.start_lp client.lp = room.hostinfo.start_lp
#ygopro.stoc_send_chat_to_room(client.room, "LP跟踪调试信息: #{client.name} 初始LP #{client.lp}") #ygopro.stoc_send_chat_to_room(room, "LP跟踪调试信息: #{client.name} 初始LP #{client.lp}")
###
if ygopro.constants.MSG[msg] == 'WIN' and _.startsWith(client.room.name, 'M#') and client.is_host if ygopro.constants.MSG[msg] == 'WIN' and client.is_host
pos = buffer.readUInt8(1) pos = buffer.readUInt8(1)
pos = 1 - pos unless client.is_first or pos == 2 pos = 1 - pos unless client.is_first or pos == 2
reason = buffer.readUInt8(2) reason = buffer.readUInt8(2)
#log.info {winner: pos, reason: reason} #log.info {winner: pos, reason: reason}
client.room.duels.push {winner: pos, reason: reason} #room.duels.push {winner: pos, reason: reason}
### room.winner = pos
#lp跟踪 #lp跟踪
if ygopro.constants.MSG[msg] == 'DAMAGE' and client.is_host if ygopro.constants.MSG[msg] == 'DAMAGE' and client.is_host
pos = buffer.readUInt8(1) pos = buffer.readUInt8(1)
pos = 1 - pos unless client.is_first pos = 1 - pos unless client.is_first
val = buffer.readInt32LE(2) val = buffer.readInt32LE(2)
client.room.dueling_players[pos].lp -= val room.dueling_players[pos].lp -= val
if 0 < client.room.dueling_players[pos].lp <= 100 if 0 < room.dueling_players[pos].lp <= 100
ygopro.stoc_send_chat_to_room(client.room, "你的生命已经如风中残烛了!", 15) ygopro.stoc_send_chat_to_room(room, "你的生命已经如风中残烛了!", ygopro.constants.COLORS.PINK)
if ygopro.constants.MSG[msg] == 'RECOVER' and client.is_host if ygopro.constants.MSG[msg] == 'RECOVER' and client.is_host
pos = buffer.readUInt8(1) pos = buffer.readUInt8(1)
pos = 1 - pos unless client.is_first pos = 1 - pos unless client.is_first
val = buffer.readInt32LE(2) val = buffer.readInt32LE(2)
client.room.dueling_players[pos].lp += val room.dueling_players[pos].lp += val
if ygopro.constants.MSG[msg] == 'LPUPDATE' and client.is_host if ygopro.constants.MSG[msg] == 'LPUPDATE' and client.is_host
pos = buffer.readUInt8(1) pos = buffer.readUInt8(1)
pos = 1 - pos unless client.is_first pos = 1 - pos unless client.is_first
val = buffer.readInt32LE(2) val = buffer.readInt32LE(2)
client.room.dueling_players[pos].lp = val room.dueling_players[pos].lp = val
if ygopro.constants.MSG[msg] == 'PAY_LPCOST' and client.is_host if ygopro.constants.MSG[msg] == 'PAY_LPCOST' and client.is_host
pos = buffer.readUInt8(1) pos = buffer.readUInt8(1)
pos = 1 - pos unless client.is_first pos = 1 - pos unless client.is_first
val = buffer.readInt32LE(2) val = buffer.readInt32LE(2)
client.room.dueling_players[pos].lp -= val room.dueling_players[pos].lp -= val
if 0 < client.room.dueling_players[pos].lp <= 100 if 0 < room.dueling_players[pos].lp <= 100
ygopro.stoc_send_chat_to_room(client.room, "背水一战!", 15) ygopro.stoc_send_chat_to_room(room, "背水一战!", ygopro.constants.COLORS.PINK)
#登场台词 #登场台词
if settings.modules.dialogues if settings.modules.dialogues
if ygopro.constants.MSG[msg] == 'SUMMONING' or ygopro.constants.MSG[msg] == 'SPSUMMONING' if ygopro.constants.MSG[msg] == 'SUMMONING' or ygopro.constants.MSG[msg] == 'SPSUMMONING'
card = buffer.readUInt32LE(1) card = buffer.readUInt32LE(1)
if dialogues[card] if settings.dialogues[card]
for line in _.lines dialogues[card][Math.floor(Math.random() * dialogues[card].length)] for line in _.lines settings.dialogues[card][Math.floor(Math.random() * settings.dialogues[card].length)]
ygopro.stoc_send_chat client, line, 15 ygopro.stoc_send_chat(client, line, ygopro.constants.COLORS.PINK)
return return
#房间管理 #房间管理
ygopro.ctos_follow 'HS_KICK', true, (buffer, info, client, server)-> ygopro.ctos_follow 'HS_KICK', true, (buffer, info, client, server)->
return unless client.room room=ROOM_all[client.rid]
for player in client.room.players return unless room
for player in room.players
if player and player.pos == info.pos and player != client if player and player.pos == info.pos and player != client
ygopro.stoc_send_chat_to_room client.room, "#{player.name} 被请出了房间", 11 client.kick_count = if client.kick_count then client.kick_count+1 else 1
if client.kick_count>=5
ygopro.stoc_send_chat_to_room(room, "#{client.name} 被系统请出了房间", ygopro.constants.COLORS.RED)
ROOM_ban_player(player.name, player.ip, "挂房间")
client.destroy()
return true
ygopro.stoc_send_chat_to_room(room, "#{player.name} 被请出了房间", ygopro.constants.COLORS.RED)
return false return false
ygopro.stoc_follow 'TYPE_CHANGE', false, (buffer, info, client, server)-> ygopro.stoc_follow 'TYPE_CHANGE', false, (buffer, info, client, server)->
selftype = info.type & 0xf; selftype = info.type & 0xf
is_host = ((info.type >> 4) & 0xf) != 0; is_host = ((info.type >> 4) & 0xf) != 0
client.is_host = is_host client.is_host = is_host
client.pos = selftype client.pos = selftype
#console.log "TYPE_CHANGE to #{client.name}:", info, selftype, is_host #console.log "TYPE_CHANGE to #{client.name}:", info, selftype, is_host
return return
ygopro.stoc_follow 'HS_PLAYER_CHANGE', false, (buffer, info, client, server)-> ygopro.stoc_follow 'HS_PLAYER_CHANGE', false, (buffer, info, client, server)->
return unless client.room and client.room.max_player and client.is_host room=ROOM_all[client.rid]
pos = info.status >> 4; return unless room and room.max_player and client.is_host
is_ready = (info.status & 0xf) == 9; pos = info.status >> 4
if pos < client.room.max_player is_ready = (info.status & 0xf) == 9
client.room.ready_player_count_without_host = 0 if pos < room.max_player
for player in client.room.players room.ready_player_count_without_host = 0
for player in room.players
if player.pos == pos if player.pos == pos
player.is_ready = is_ready player.is_ready = is_ready
unless player.is_host unless player.is_host
client.room.ready_player_count_without_host += player.is_ready room.ready_player_count_without_host += player.is_ready
if client.room.ready_player_count_without_host >= client.room.max_player - 1 if room.ready_player_count_without_host >= room.max_player - 1
#log.info "all ready" #log.info "all ready"
setTimeout (()-> wait_room_start(client.room, 20);return), 1000 setTimeout (()-> wait_room_start(ROOM_all[client.rid], 20);return), 1000
return return
wait_room_start = (room, time)-> wait_room_start = (room, time)->
...@@ -710,154 +1217,327 @@ wait_room_start = (room, time)-> ...@@ -710,154 +1217,327 @@ wait_room_start = (room, time)->
time -= 1 time -= 1
if time if time
unless time % 5 unless time % 5
ygopro.stoc_send_chat_to_room room, "#{if time <= 9 then ' ' else ''}#{time}秒后房主若不开始游戏将被请出房间", if time <= 9 then 11 else 8 ygopro.stoc_send_chat_to_room(room, "#{if time <= 9 then ' ' else ''}#{time}秒后房主若不开始游戏将被请出房间", if time <= 9 then ygopro.constants.COLORS.RED else ygopro.constants.COLORS.LIGHTBLUE)
setTimeout (()-> wait_room_start(room, time);return), 1000 setTimeout (()-> wait_room_start(room, time);return), 1000
else else
for player in room.players for player in room.players
if player and player.is_host if player and player.is_host
Room.ban_player(player.name, player.ip, "挂房间") ROOM_ban_player(player.name, player.ip, "挂房间")
ygopro.stoc_send_chat_to_room room, "#{player.name} 被系统请出了房间", 11 ygopro.stoc_send_chat_to_room(room, "#{player.name} 被系统请出了房间", ygopro.constants.COLORS.RED)
player.end() player.destroy()
return return
#tip #tip
ygopro.stoc_send_random_tip = (client)-> ygopro.stoc_send_random_tip = (client)->
ygopro.stoc_send_chat client, "Tip: " + tips[Math.floor(Math.random() * tips.length)] if tips ygopro.stoc_send_chat(client, "Tip: " + settings.tips[Math.floor(Math.random() * settings.tips.length)]) if settings.modules.tips
return return
ygopro.stoc_send_random_tip_to_room = (room)-> ygopro.stoc_send_random_tip_to_room = (room)->
ygopro.stoc_send_chat_to_room room, "Tip: " + tips[Math.floor(Math.random() * tips.length)] if tips ygopro.stoc_send_chat_to_room(room, "Tip: " + settings.tips[Math.floor(Math.random() * settings.tips.length)]) if settings.modules.tips
return return
setInterval ()-> load_tips = ()->
for room in Room.all
ygopro.stoc_send_random_tip_to_room(room) unless room and room.started
return
, 30000
tips = null
if settings.modules.tips
request request
url: settings.modules.tips url: settings.modules.tips
json: true json: true
, (error, response, body)-> , (error, response, body)->
tips = body if _.isString body
#log.info "tips loaded", tips.length log.warn "tips bad json", body
else if error or !body
log.warn 'tips error', error, response
else
nconf.myset(settings, "tips", body)
log.info "tips loaded", settings.tips.length
return return
return
if settings.modules.tips
load_tips()
setInterval ()->
for room in ROOM_all when room and room.established
ygopro.stoc_send_random_tip_to_room(room) unless room and room.started
return
, 30000
if settings.modules.mycard_auth and process.env.MYCARD_AUTH_DATABASE if settings.modules.mycard_auth and process.env.MYCARD_AUTH_DATABASE
pg = require('pg'); pg = require('pg')
pg.connect process.env.MYCARD_AUTH_DATABASE, (error, client, done)-> pg.connect process.env.MYCARD_AUTH_DATABASE, (error, client, done)->
throw error if error throw error if error
client.query 'SELECT username, id from users', (error, result)-> client.query 'SELECT username, id from users', (error, result)->
throw error if error throw error if error
done(); done()
for row in result.rows for row in result.rows
users_cache[row.username] = row.id users_cache[row.username] = row.id
console.log("users loaded", _.keys(users_cache).length) console.log("users loaded", _.keys(users_cache).length)
return
return
ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server)-> ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server)->
return unless client.room room=ROOM_all[client.rid]
unless client.room.started #first start return unless room
client.room.started = true unless room.started #first start
roomlist.delete client.room.name if settings.modules.enable_websocket_roomlist and not client.room.private room.started = true
#client.room.duels = [] roomlist.delete room.name if settings.modules.enable_websocket_roomlist and not room.private
client.room.dueling_players = [] #room.duels = []
for player in client.room.players when player.pos != 7 room.dueling_players = []
client.room.dueling_players[player.pos] = player for player in room.players when player.pos != 7
client.room.player_datas.push ip: player.remoteAddress, name: player.name room.dueling_players[player.pos] = player
if client.room.windbot room.player_datas.push ip: player.ip, name: player.name
client.room.dueling_players[1 - player.pos] = {}
if settings.modules.tips if settings.modules.tips
ygopro.stoc_send_random_tip(client) ygopro.stoc_send_random_tip(client)
if settings.modules.enable_deck_log and client.main and client.main.length and not client.deck_saved
deck_text = '#ygosrv233 deck log\r\n#main\r\n' + client.main.join('\r\n') + '\r\n!side\r\n' + client.side.join('\r\n') + '\r\n'
deck_name = moment().format('YYYY-MM-DD HH-mm-ss') + ' ' + room.port + ' ' + client.pos + ' ' + client.name.replace(/\//g, '_')
fs.writeFile 'decks_save\/' + deck_name + '.ydk', deck_text, 'utf-8', (err) ->
if err
log.warn 'DECK SAVE ERROR', err
client.deck_saved = true
return return
ygopro.ctos_follow 'CHAT', true, (buffer, info, client, server)-> ygopro.ctos_follow 'CHAT', true, (buffer, info, client, server)->
cancel = _.startsWith(_.trim(info.msg), "/") room=ROOM_all[client.rid]
client.room.last_active_time = moment() unless cancel or not client.room.random_type return unless room
switch _.trim(info.msg) msg = _.trim(info.msg)
when '/ping' cancel = _.startsWith(msg, "/")
execFile 'ss', ['-it', "dst #{client.remoteAddress}:#{client.remotePort}"], (error, stdout, stderr)-> room.last_active_time = moment() unless cancel or not room.random_type
if error cmd = msg.split(' ')
ygopro.stoc_send_chat_to_room client.room, error switch cmd[0]
else
line = _.lines(stdout)[2]
if line.indexOf('rtt') != -1
ygopro.stoc_send_chat_to_room client.room, line
else
#log.warn 'ping', stdout
ygopro.stoc_send_chat_to_room client.room, stdout
return
when '/help' when '/help'
ygopro.stoc_send_chat(client, "YGOSrv233 指令帮助") ygopro.stoc_send_chat(client, "YGOSrv233 指令帮助")
ygopro.stoc_send_chat(client, "/help 显示这个帮助信息") ygopro.stoc_send_chat(client, "/help 显示这个帮助信息")
ygopro.stoc_send_chat(client, "/roomname 显示当前房间的名字") ygopro.stoc_send_chat(client, "/roomname 显示当前房间的名字")
ygopro.stoc_send_chat(client, "/ai 添加一个AI,/ai 角色名 可指定添加的角色") if settings.modules.enable_windbot
ygopro.stoc_send_chat(client, "/tip 显示一条提示") if settings.modules.tips ygopro.stoc_send_chat(client, "/tip 显示一条提示") if settings.modules.tips
when '/tip' when '/tip'
ygopro.stoc_send_random_tip(client) if settings.modules.tips ygopro.stoc_send_random_tip(client) if settings.modules.tips
when '/roomname' when '/ai'
ygopro.stoc_send_chat(client, "您当前的房间名是 " + client.room.name) if client.room if settings.modules.enable_windbot
if name = cmd[1]
when '/test' windbot = _.sample _.filter settings.modules.windbots, (w)->
ygopro.stoc_send_hint_card_to_room(client.room, 2333365) w.name == name or w.deck == name
if !windbot
ygopro.stoc_send_chat(client, "未找到该AI角色或卡组", ygopro.constants.COLORS.RED)
return
else
windbot = _.sample settings.modules.windbots
room.add_windbot(windbot)
when '/roomname'
ygopro.stoc_send_chat(client, "您当前的房间名是 " + room.name, ygopro.constants.COLORS.BABYBLUE) if room
#when '/test'
# ygopro.stoc_send_hint_card_to_room(room, 2333365)
if !(room and room.random_type)
return cancel
if client.abuse_count>=5
log.warn "BANNED CHAT", client.name, client.ip, msg
ygopro.stoc_send_chat(client, "您已被禁言!", ygopro.constants.COLORS.RED)
return true
oldmsg = msg
if (_.any(settings.ban.badword_level3, (badword) ->
regexp = new RegExp(badword, 'i')
return msg.match(regexp)
, msg))
log.warn "BAD WORD LEVEL 3", client.name, client.ip, oldmsg
cancel = true
if client.abuse_count>0
ygopro.stoc_send_chat(client, "您的发言存在严重不适当的内容,禁止您使用随机对战功能!", ygopro.constants.COLORS.RED)
ROOM_ban_player(client.name, client.ip, "发言违规")
ROOM_ban_player(client.name, client.ip, "发言违规", 3)
client.destroy()
return true
else
client.abuse_count=client.abuse_count+4
ygopro.stoc_send_chat(client, "您的发言存在不适当的内容,发送失败!", ygopro.constants.COLORS.RED)
else if (_.any(settings.ban.badword_level2, (badword) ->
regexp = new RegExp(badword, 'i')
return msg.match(regexp)
, msg))
log.warn "BAD WORD LEVEL 2", client.name, client.ip, oldmsg
client.abuse_count=client.abuse_count+3
ygopro.stoc_send_chat(client, "您的发言存在不适当的内容,发送失败!", ygopro.constants.COLORS.RED)
cancel = true
else
_.each(settings.ban.badword_level1, (badword) ->
#log.info msg
regexp = new RegExp(badword, "ig")
msg = msg.replace(regexp, "**")
return
, msg)
if oldmsg != msg
log.warn "BAD WORD LEVEL 1", client.name, client.ip, oldmsg
client.abuse_count=client.abuse_count+1
ygopro.stoc_send_chat(client, "请使用文明用语!")
struct = ygopro.structs["chat"]
struct._setBuff(buffer)
struct.set("msg", msg)
buffer = struct.buffer
else if (_.any(settings.ban.badword_level0, (badword) ->
regexp = new RegExp(badword, 'i')
return msg.match(regexp)
, msg))
log.info "BAD WORD LEVEL 0", client.name, client.ip, oldmsg
if client.abuse_count>=5
ygopro.stoc_send_chat_to_room(room, "#{client.name} 已被禁言!", ygopro.constants.COLORS.RED)
ROOM_ban_player(client.name, client.ip, "发言违规")
return cancel return cancel
ygopro.ctos_follow 'UPDATE_DECK', false, (buffer, info, client, server)-> ygopro.ctos_follow 'UPDATE_DECK', true, (buffer, info, client, server)->
#log.info info room=ROOM_all[client.rid]
main = (info.deckbuf[i] for i in [0...info.mainc]) return false unless room
side = (info.deckbuf[i] for i in [info.mainc...info.mainc + info.sidec]) #log.info info
client.main = main buff_main = (info.deckbuf[i] for i in [0...info.mainc])
client.side = side buff_side = (info.deckbuf[i] for i in [info.mainc...info.mainc + info.sidec])
return client.main = buff_main
client.side = buff_side
if room.random_type
if client.is_host
room.waiting_for_player = room.waiting_for_player2
room.last_active_time = moment()
else if !room.started and room.hostinfo.mode == 1 and settings.modules.tournament_mode.enabled
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 deck in decks
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 line in deck_array
if line.indexOf("!side")>=0
current_deck=deck_side
card=parseInt(line)
current_deck.push(card) unless isNaN(card)
if _.isEqual(buff_main, deck_main) and _.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
#log.info("deck ok: " + client.name)
ygopro.stoc_send_chat(client, "成功使用卡组 #{found_deck} 参加比赛。", ygopro.constants.COLORS.BABYBLUE)
else
#log.info("bad deck: " + client.name + " / " + buff_main + " / " + buff_side)
ygopro.stoc_send_chat(client, "您的卡组与报名卡组 #{found_deck} 不符。注意卡组不能有包括卡片顺序在内的任何修改。", ygopro.constants.COLORS.RED)
else
#log.info("player deck not found: " + client.name)
ygopro.stoc_send_chat(client, "#{client.name},没有找到您的报名信息,请确定您使用昵称与报名ID一致。", ygopro.constants.COLORS.RED)
return false
ygopro.ctos_follow 'RESPONSE', false, (buffer, info, client, server)-> ygopro.ctos_follow 'RESPONSE', false, (buffer, info, client, server)->
return unless client.room and client.room.random_type room=ROOM_all[client.rid]
client.room.last_active_time = moment() return unless room and room.random_type
room.last_active_time = moment()
return return
ygopro.ctos_follow 'HAND_RESULT', false, (buffer, info, client, server)-> ygopro.ctos_follow 'HAND_RESULT', false, (buffer, info, client, server)->
return unless client.room and client.room.random_type room=ROOM_all[client.rid]
return unless room and room.random_type
if client.is_host if client.is_host
client.room.waiting_for_player = client.room.waiting_for_player2 room.waiting_for_player = room.waiting_for_player2
client.room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's') room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's')
return return
ygopro.ctos_follow 'TP_RESULT', false, (buffer, info, client, server)-> ygopro.ctos_follow 'TP_RESULT', false, (buffer, info, client, server)->
return unless client.room and client.room.random_type room=ROOM_all[client.rid]
client.room.last_active_time = moment() return unless room and room.random_type
room.last_active_time = moment()
return return
ygopro.stoc_follow 'SELECT_HAND', false, (buffer, info, client, server)-> ygopro.stoc_follow 'SELECT_HAND', false, (buffer, info, client, server)->
return unless client.room and client.room.random_type room=ROOM_all[client.rid]
return unless room and room.random_type
if client.is_host if client.is_host
client.room.waiting_for_player = client room.waiting_for_player = client
else else
client.room.waiting_for_player2 = client room.waiting_for_player2 = client
client.room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's') room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's')
return return
ygopro.stoc_follow 'SELECT_TP', false, (buffer, info, client, server)-> ygopro.stoc_follow 'SELECT_TP', false, (buffer, info, client, server)->
return unless client.room and client.room.random_type room=ROOM_all[client.rid]
client.room.waiting_for_player = client return unless room and room.random_type
client.room.last_active_time = moment() room.waiting_for_player = client
room.last_active_time = moment()
return return
setInterval ()-> ygopro.stoc_follow 'CHANGE_SIDE', false, (buffer, info, client, server)->
for room in Room.all when room and room.started and room.random_type and room.last_active_time and room.waiting_for_player room=ROOM_all[client.rid]
time_passed = Math.floor((moment() - room.last_active_time) / 1000) return unless room and room.random_type
#log.info time_passed if client.is_host
if time_passed >= settings.modules.hang_timeout room.waiting_for_player = client
room.last_active_time = moment() else
Room.ban_player(room.waiting_for_player.name, room.waiting_for_player.ip, "挂机") room.waiting_for_player2 = client
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} 被系统请出了房间", 11) room.last_active_time = moment()
room.waiting_for_player.server.end()
else if time_passed >= (settings.modules.hang_timeout - 20) and not (time_passed % 10)
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} 已经很久没有操作了,若继续挂机,将于#{settings.modules.hang_timeout - time_passed}秒后被请出房间", 11)
return return
, 1000
ygopro.stoc_follow 'REPLAY', true, (buffer, info, client, server)->
room=ROOM_all[client.rid]
return settings.modules.tournament_mode.enabled unless room
if settings.modules.enable_cloud_replay and room.random_type
Cloud_replay_ids.push room.cloud_replay_id
if settings.modules.tournament_mode.enabled
if client.is_host
duellog = {
time: moment().format('YYYY-MM-DD HH:mm:ss'),
name: room.name,
roomid: room.port.toString(),
cloud_replay_id: "R#"+room.cloud_replay_id,
players: (for player in room.players
name: player.name,
winner: player.pos == room.winner
)
}
settings.modules.tournament_mode.duel_log.push duellog
nconf.myset(settings, "modules:tournament_mode:duel_log", settings.modules.tournament_mode.duel_log)
if settings.modules.enable_cloud_replay
ygopro.stoc_send_chat(client, "本场比赛云录像:R##{room.cloud_replay_id}。将于本局结束后可播放。", ygopro.constants.COLORS.BABYBLUE)
return true
else
return false
if settings.modules.enable_random_duel
setInterval ()->
for room in ROOM_all when room and room.started and room.random_type and room.last_active_time and room.waiting_for_player
time_passed = Math.floor((moment() - room.last_active_time) / 1000)
#log.info time_passed
if time_passed >= settings.modules.hang_timeout
room.last_active_time = moment()
ROOM_ban_player(room.waiting_for_player.name, room.waiting_for_player.ip, "挂机")
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} 被系统请出了房间", ygopro.constants.COLORS.RED)
room.waiting_for_player.server.destroy()
else if time_passed >= (settings.modules.hang_timeout - 20) and not (time_passed % 10)
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} 已经很久没有操作了,若继续挂机,将于#{settings.modules.hang_timeout - time_passed}秒后被请出房间", ygopro.constants.COLORS.RED)
return
, 1000
if settings.modules.spawn_windbot
windbot_process = spawn 'mono', ['WindBot.exe', settings.modules.windbot_port], {cwd: 'windbot'}
windbot_process.on 'error', (err)->
log.warn 'WindBot ERROR', err
return
windbot_process.on 'exit', (code)->
log.warn 'WindBot EXIT', code
return
windbot_process.stdout.setEncoding('utf8')
windbot_process.stdout.on 'data', (data)->
log.info 'WindBot:', data
return
windbot_process.stderr.on 'data', (data)->
log.warn 'WindBot Error:', data
return
#http #http
if settings.modules.http if settings.modules.http
...@@ -868,12 +1548,12 @@ if settings.modules.http ...@@ -868,12 +1548,12 @@ if settings.modules.http
pass_validated = u.query.pass == settings.modules.http.password pass_validated = u.query.pass == settings.modules.http.password
if u.pathname == '/api/getrooms' if u.pathname == '/api/getrooms'
if u.query.pass and !pass_validated if !pass_validated
response.writeHead(200); response.writeHead(200)
response.end(u.query.callback + '( {"rooms":[{"roomid":"0","roomname":"密码错误","needpass":"true"}]} );') response.end(u.query.callback + '( {"rooms":[{"roomid":"0","roomname":"密码错误","needpass":"true"}]} );')
else else
response.writeHead(200); response.writeHead(200)
roomsjson = JSON.stringify rooms: (for room in Room.all when room.established roomsjson = JSON.stringify rooms: (for room in ROOM_all when room and room.established
pid: room.process.pid.toString(), pid: room.process.pid.toString(),
roomid: room.port.toString(), roomid: room.port.toString(),
roomname: if pass_validated then room.name else room.name.split('$', 2)[0], roomname: if pass_validated then room.name else room.name.split('$', 2)[0],
...@@ -887,42 +1567,66 @@ if settings.modules.http ...@@ -887,42 +1567,66 @@ if settings.modules.http
) )
response.end(u.query.callback + "( " + roomsjson + " );") response.end(u.query.callback + "( " + roomsjson + " );")
else if u.pathname == '/api/duellog' and settings.modules.tournament_mode.enabled
if !pass_validated
response.writeHead(200)
response.end("密码错误")
return
else
response.writeHead(200)
duellog = JSON.stringify settings.modules.tournament_mode.duel_log
response.end(u.query.callback + "( " + duellog + " );")
else if u.pathname == '/api/message' else if u.pathname == '/api/message'
if !pass_validated if !pass_validated
response.writeHead(200); response.writeHead(200)
response.end(u.query.callback + "( '密码错误', 0 );"); response.end(u.query.callback + "( ['密码错误', 0] );")
return return
if u.query.shout if u.query.shout
for room in Room.all for room in ROOM_all when room and room.established
ygopro.stoc_send_chat_to_room(room, u.query.shout, 16) ygopro.stoc_send_chat_to_room(room, u.query.shout, ygopro.constants.COLORS.YELLOW)
response.writeHead(200) response.writeHead(200)
response.end(u.query.callback + "( 'shout ok', '" + u.query.shout + "' );") response.end(u.query.callback + "( ['shout ok', '" + u.query.shout + "'] );")
else if u.query.stop else if u.query.stop
if u.query.stop == 'false' if u.query.stop == 'false'
u.query.stop = false u.query.stop = false
settings.modules.stop = u.query.stop settings.modules.stop = u.query.stop
response.writeHead(200) response.writeHead(200)
response.end(u.query.callback + "( 'stop ok', '" + u.query.stop + "' );") response.end(u.query.callback + "( ['stop ok', '" + u.query.stop + "'] );")
else if u.query.welcome else if u.query.welcome
settings.modules.welcome = u.query.welcome nconf.myset(settings, 'modules:welcome', u.query.welcome)
response.writeHead(200)
response.end(u.query.callback + "( ['welcome ok', '" + u.query.welcome + "'] );")
else if u.query.getwelcome
response.writeHead(200)
response.end(u.query.callback + "( ['get ok', '" + settings.modules.welcome + "'] );")
else if u.query.loadtips
load_tips()
response.writeHead(200)
response.end(u.query.callback + "( ['loading tip', '" + settings.modules.tips + "'] );")
else if u.query.loaddialogues
load_dialogues()
response.writeHead(200) response.writeHead(200)
response.end(u.query.callback + "( 'welcome ok', '" + u.query.welcome + "' );") response.end(u.query.callback + "( ['loading dialogues', '" + settings.modules.dialogues + "'] );")
else if u.query.ban else if u.query.ban
settings.BANNED_user.push(u.query.ban) ban_user(u.query.ban)
response.writeHead(200) response.writeHead(200)
response.end(u.query.callback + "( 'ban ok', '" + u.query.ban + "' );") response.end(u.query.callback + "( ['ban ok', '" + u.query.ban + "'] );")
else else
response.writeHead(404); response.writeHead(404)
response.end(); response.end()
else else
response.writeHead(404); response.writeHead(404)
response.end(); response.end()
return return
http_server = http.createServer(requestListener) http_server = http.createServer(requestListener)
......
// Generated by CoffeeScript 1.10.0 // Generated by CoffeeScript 1.10.0
(function() { (function() {
var Graveyard, Room, _, bunyan, crypto, debug, dialogues, execFile, fs, http, http_server, https, https_server, list, log, moment, net, options, os, path, pg, request, requestListener, roomlist, settings, tips, tribute, url, users_cache, wait_room_start, ygopro; var Cloud_replay_ids, Graveyard, ROOM_all, ROOM_bad_ip, ROOM_ban_player, ROOM_connected_ip, ROOM_find_by_name, ROOM_find_by_port, ROOM_find_or_create_ai, ROOM_find_or_create_by_name, ROOM_find_or_create_random, ROOM_players_banned, ROOM_players_oppentlist, ROOM_validate, Room, _, ban_user, bunyan, crypto, date, defaultconfig, execFile, fs, get_memory_usage, http, http_server, https, https_server, list, load_dialogues, load_tips, log, moment, nconf, net, options, os, path, pg, redis, redisdb, request, requestListener, roomlist, settings, spawn, spawnSync, tribute, url, users_cache, wait_room_start, windbot_process, ygopro, zlib;
net = require('net'); net = require('net');
...@@ -18,6 +18,10 @@ ...@@ -18,6 +18,10 @@
execFile = require('child_process').execFile; execFile = require('child_process').execFile;
spawn = require('child_process').spawn;
spawnSync = require('child_process').spawnSync;
_ = require('underscore'); _ = require('underscore');
_.str = require('underscore.string'); _.str = require('underscore.string');
...@@ -28,15 +32,84 @@ ...@@ -28,15 +32,84 @@
bunyan = require('bunyan'); bunyan = require('bunyan');
log = bunyan.createLogger({
name: "mycard"
});
moment = require('moment'); moment = require('moment');
settings = require('./config.json'); 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');
settings.BANNED_user = []; nconf.file('./config.user.json');
settings.BANNED_IP = []; defaultconfig = require('./config.json');
settings.modules.hang_timeout = 90; 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;
}
};
ban_user = function(name) {
var bad_ip, k, l, 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 (k = 0, len = ROOM_all.length; k < len; k++) {
room = ROOM_all[k];
if (room && room.established) {
ref = room.players;
for (l = 0, len1 = ref.length; l < len1; l++) {
player = ref[l];
if (player && (player.name === name || player.ip === bad_ip)) {
bad_ip = player.ip;
ROOM_bad_ip.push(player.ip);
settings.ban.banned_ip.push(player.ip);
ygopro.stoc_send_chat_to_room(room, player.name + " 被系统请出了房间", ygopro.constants.COLORS.RED);
player.destroy();
continue;
}
}
}
}
};
settings.version = parseInt(fs.readFileSync('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16'); settings.version = parseInt(fs.readFileSync('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16');
...@@ -46,6 +119,10 @@ ...@@ -46,6 +119,10 @@
results = []; results = [];
for (k = 0, len = ref.length; k < len; k++) { for (k = 0, len = ref.length; k < len; k++) {
list = ref[k]; list = ref[k];
date = list.match(/!([\d\.]+)/);
if (!date) {
continue;
}
results.push({ results.push({
date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"),
tcg: list.indexOf('TCG') !== -1 tcg: list.indexOf('TCG') !== -1
...@@ -54,37 +131,47 @@ ...@@ -54,37 +131,47 @@
return results; return results;
})(); })();
if (settings.modules.enable_cloud_replay) {
redis = require('redis');
zlib = require('zlib');
redisdb = redis.createClient({
host: "127.0.0.1",
port: settings.modules.redis_port
});
redisdb.on('error', function(err) {
log.warn(err);
});
}
if (settings.modules.enable_windbot) { if (settings.modules.enable_windbot) {
settings.modules.windbot = require('./windbot/bots.json').windbots; settings.modules.windbots = require('./windbot/bots.json').windbots;
} }
ygopro = require('./ygopro.js'); ygopro = require('./ygopro.js');
Room = require('./room.js');
if (settings.modules.enable_websocket_roomlist) { if (settings.modules.enable_websocket_roomlist) {
roomlist = require('./roomlist.js'); roomlist = require('./roomlist.js');
} }
users_cache = {}; users_cache = {};
debug = false; get_memory_usage = function() {
var actualFree, buffers, cached, free, line, lines, percentUsed, prc_free, total;
log = null; prc_free = spawnSync("free", []);
if (prc_free.stdout) {
if (process.argv[2] === '--debug') { lines = prc_free.stdout.toString().split(/\n/g);
settings.port++; line = lines[1].split(/\s+/);
if (settings.modules.http) { total = parseInt(line[1], 10);
settings.modules.http.port++; 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;
} }
log = bunyan.createLogger({ return percentUsed;
name: "mycard-debug" };
});
} else {
log = bunyan.createLogger({
name: "mycard"
});
}
Graveyard = []; Graveyard = [];
...@@ -111,81 +198,639 @@ ...@@ -111,81 +198,639 @@
Graveyard = []; Graveyard = [];
}, 3000); }, 3000);
Cloud_replay_ids = [];
ROOM_all = [];
ROOM_players_oppentlist = {};
ROOM_players_banned = [];
ROOM_connected_ip = {};
ROOM_bad_ip = {};
ROOM_ban_player = function(name, ip, reason, countadd) {
var bannedplayer, bantime;
if (countadd == null) {
countadd = 1;
}
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.enable_windbot && (uname.slice(0, 2) === 'AI' || (!settings.modules.enable_random_duel && uname === ''))) {
return ROOM_find_or_create_ai(name);
}
if (settings.modules.enable_random_duel && (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": "因为您近期在游戏中多次" + (bannedplayer.reasons.join('')) + ",您已被禁止使用随机对战功能,将在" + (moment(bannedplayer.time).fromNow(true)) + "后解封"
};
}
if (bannedplayer.count > 3 && moment() < bannedplayer.time && bannedplayer.need_tip) {
bannedplayer.need_tip = false;
return {
"error": "因为您近期在游戏中" + (bannedplayer.reasons.join('')) + ",在" + (moment(bannedplayer.time).fromNow(true)) + "内您随机对战时只能遇到其他违规玩家"
};
} else if (bannedplayer.need_tip) {
bannedplayer.need_tip = false;
return {
"error": "系统检测到您近期在游戏中" + (bannedplayer.reasons.join('')) + ",若您违规超过3次,将受到惩罚"
};
} 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 && (room.get_host() === null || room.get_host().ip !== ROOM_players_oppentlist[player_ip]) && (playerbanned === room.deprecated);
});
if (result) {
result.welcome = '对手已经在等你了,开始决斗吧!';
} 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 = '已建立随机对战房间,正在等待对手!';
result.deprecated = playerbanned;
} else {
return null;
}
if (result.random_type === 'M') {
result.welcome = result.welcome + '\n您进入了比赛模式的房间,我们推荐使用竞技卡组!';
}
return result;
};
ROOM_find_or_create_ai = function(name) {
var ainame, namea, result, room, windbot;
if (name === '') {
name = 'AI';
}
if (name.slice(0, 3) === 'AI_') {
name = 'AI#' + name.slice(3);
}
namea = name.split('#');
if (room = ROOM_find_by_name(name)) {
return room;
} else if (name === '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": "未找到该AI角色或卡组"
};
}
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": "AI房间名过长,请在建立房间后输入 /ai 来添加AI"
};
}
result = new Room(name);
result.windbot = windbot;
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_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 = (function() {
function Room(name, hostinfo) {
var draw_count, error1, 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 = '';
ROOM_all.push(this);
this.hostinfo || (this.hostinfo = {
lflist: settings.lflist.length ? 0 : -1,
rule: settings.modules.enable_TCG_as_default ? 2 : 0,
mode: 0,
enable_priority: false,
no_check_deck: false,
no_shuffle_deck: false,
start_lp: 8000,
start_hand: 5,
draw_count: 1,
time_limit: 180,
replay_mode: settings.modules.tournament_mode.enabled ? 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 ((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('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["private"] && settings.modules.enable_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, k, len, ref;
ref = player.pre_establish_buffers;
for (k = 0, len = ref.length; k < len; k++) {
buffer = ref[k];
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 = "建立房间失败,请重试";
}
}
Room.prototype["delete"] = function() {
var index, log_rep_id, player_ips, player_names, recorder_buffer, replay_id;
if (this.deleted) {
return;
}
if (this.player_datas.length && settings.modules.enable_cloud_replay) {
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["private"] && !this.started && this.established && settings.modules.enable_websocket_roomlist) {
roomlist["delete"](this.name);
}
};
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, response);
ygopro.stoc_send_chat_to_room(_this, "添加AI失败,可尝试输入 /ai 重新添加", 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["private"] && !this.started && settings.modules.enable_websocket_roomlist) {
roomlist.update(this);
}
client.server.connect(this.port, '127.0.0.1', function() {
var buffer, k, len, ref;
ref = client.pre_establish_buffers;
for (k = 0, len = ref.length; k < len; k++) {
buffer = ref[k];
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 + " 退出了观战") + (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' && this.random_type && (client.pos < 4 || client.is_host)) {
ROOM_ban_player(client.name, client.ip, "强退");
}
if (this.players.length && !(this.windbot && client.is_host)) {
ygopro.stoc_send_chat_to_room(this, (client.name + " 离开了游戏") + (error ? ": " + error : ''));
if (!this["private"] && !this.started && settings.modules.enable_websocket_roomlist) {
roomlist.update(this);
}
} else {
this.process.kill();
this["delete"]();
}
}
};
return Room;
})();
net.createServer(function(client) { net.createServer(function(client) {
var ctos_buffer, ctos_message_length, ctos_proto, server, stoc_buffer, stoc_message_length, stoc_proto; var connect_count, server;
client.ip = client.remoteAddress;
connect_count = ROOM_connected_ip[client.ip] || 0;
if (client.ip !== '::ffff:127.0.0.1') {
connect_count++;
}
ROOM_connected_ip[client.ip] = connect_count;
server = new net.Socket(); server = new net.Socket();
client.server = server; client.server = server;
client.setTimeout(300000); client.setTimeout(2000);
client.on('close', function(had_error) { 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;
tribute(client); tribute(client);
if (!client.closed) { if (!client.closed) {
client.closed = true; client.closed = true;
if (client.room) { if (room) {
client.room.disconnect(client); room.disconnect(client);
} }
} }
server.end(); server.destroy();
}); });
client.on('error', function(error) { 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;
tribute(client); tribute(client);
if (!client.closed) { if (!client.closed) {
client.closed = error; client.closed = error;
if (client.room) { if (room) {
client.room.disconnect(client, error); room.disconnect(client, error);
} }
} }
server.end(); server.destroy();
}); });
client.on('timeout', function() { client.on('timeout', function() {
server.end(); server.destroy();
}); });
server.on('close', function(had_error) { server.on('close', function(had_error) {
var room;
room = ROOM_all[client.rid];
tribute(server); tribute(server);
client.room.disconnector = 'server'; if (room) {
room.disconnector = 'server';
}
if (!server.closed) { if (!server.closed) {
server.closed = true; server.closed = true;
} }
if (!client.closed) { if (!client.closed) {
ygopro.stoc_send_chat(client, "服务器关闭了连接", 11); ygopro.stoc_send_chat(client, "服务器关闭了连接", ygopro.constants.COLORS.RED);
client.end(); client.destroy();
} }
}); });
server.on('error', function(error) { server.on('error', function(error) {
var room;
room = ROOM_all[client.rid];
tribute(server); tribute(server);
client.room.disconnector = 'server'; if (room) {
room.disconnector = 'server';
}
server.closed = error; server.closed = error;
if (!client.closed) { if (!client.closed) {
ygopro.stoc_send_chat(client, "服务器错误: " + error, 11); ygopro.stoc_send_chat(client, "服务器错误: " + error, ygopro.constants.COLORS.RED);
client.end(); client.destroy();
} }
}); });
if (ROOM_bad_ip[client.ip] > 5 || ROOM_connected_ip[client.ip] > 10) {
/* log.info('BAD IP', client.ip);
client.open_cloud_replay= (err, replay)-> client.destroy();
if err or !replay return;
ygopro.stoc_send_chat(client,"没有找到录像", 11) }
ygopro.stoc_send client, 'ERROR_MSG',{ if (settings.modules.enable_cloud_replay) {
msg: 1 client.open_cloud_replay = function(err, replay) {
code: 2 var buffer;
if (err || !replay) {
ygopro.stoc_die(client, "没有找到录像");
return;
} }
client.end() redisdb.expire("replay:" + replay.replay_id, 60 * 60 * 48);
return buffer = new Buffer(replay.replay_buffer, 'binary');
replay_buffer=new Buffer(replay.replay_buffer,'binary') zlib.unzip(buffer, function(err, replay_buffer) {
ygopro.stoc_send_chat(client,"正在观看云录像:R##{replay.replay_id} #{replay.player_names} #{replay.date_time}", 14) if (err) {
client.write replay_buffer log.info("cloud replay unzip error: " + err);
client.end() ygopro.stoc_send_chat(client, "播放录像出错", ygopro.constants.COLORS.RED);
return client.destroy();
*/ return;
ctos_buffer = new Buffer(0); }
ctos_message_length = 0; ygopro.stoc_send_chat(client, "正在观看云录像:R#" + replay.replay_id + " " + replay.player_names + " " + replay.date_time, ygopro.constants.COLORS.BABYBLUE);
ctos_proto = 0; client.write(replay_buffer);
setTimeout((function() {
client.destroy();
}), 5000);
});
};
}
client.pre_establish_buffers = new Array(); client.pre_establish_buffers = new Array();
client.on('data', function(data) { client.on('data', function(ctos_buffer) {
var b, buffer, cancel, datas, k, l, len, len1, looplimit, struct; var b, bad_ip_count, buffer, cancel, ctos_message_length, ctos_proto, datas, k, l, len, len1, looplimit, room, struct;
if (client.is_post_watcher) { if (client.is_post_watcher) {
client.room.watcher.write(data); room = ROOM_all[client.rid];
if (room) {
room.watcher.write(ctos_buffer);
}
} else { } else {
ctos_buffer = Buffer.concat([ctos_buffer, data], ctos_buffer.length + data.length); ctos_message_length = 0;
ctos_proto = 0;
datas = []; datas = [];
looplimit = 0; looplimit = 0;
while (true) { while (true) {
...@@ -193,12 +838,16 @@ ...@@ -193,12 +838,16 @@
if (ctos_buffer.length >= 2) { if (ctos_buffer.length >= 2) {
ctos_message_length = ctos_buffer.readUInt16LE(0); ctos_message_length = ctos_buffer.readUInt16LE(0);
} else { } else {
if (ctos_buffer.length !== 0) {
log.warn("bad ctos_buffer length", client.ip);
}
break; break;
} }
} else if (ctos_proto === 0) { } else if (ctos_proto === 0) {
if (ctos_buffer.length >= 3) { if (ctos_buffer.length >= 3) {
ctos_proto = ctos_buffer.readUInt8(2); ctos_proto = ctos_buffer.readUInt8(2);
} else { } else {
log.warn("bad ctos_proto length", client.ip);
break; break;
} }
} else { } else {
...@@ -224,13 +873,22 @@ ...@@ -224,13 +873,22 @@
ctos_message_length = 0; ctos_message_length = 0;
ctos_proto = 0; ctos_proto = 0;
} else { } else {
if (ctos_message_length !== 17735) {
log.warn("bad ctos_message length", client.ip, ctos_buffer.length, ctos_message_length, ctos_proto);
}
break; break;
} }
} }
looplimit++; looplimit++;
if (looplimit > 800) { if (looplimit > 800 || ROOM_bad_ip[client.ip] > 5) {
log.info("error ctos", client.name); log.info("error ctos", client.name, client.ip);
server.end(); 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; break;
} }
} }
...@@ -247,53 +905,72 @@ ...@@ -247,53 +905,72 @@
} }
} }
}); });
stoc_buffer = new Buffer(0); server.on('data', function(stoc_buffer) {
stoc_message_length = 0; var b, buffer, cancel, datas, k, len, looplimit, stanzas, stoc_message_length, stoc_proto, struct;
stoc_proto = 0; stoc_message_length = 0;
server.on('data', function(data) { stoc_proto = 0;
var b, looplimit, stanzas, struct; datas = [];
stoc_buffer = Buffer.concat([stoc_buffer, data], stoc_buffer.length + data.length);
client.write(data);
looplimit = 0; looplimit = 0;
while (true) { while (true) {
if (stoc_message_length === 0) { if (stoc_message_length === 0) {
if (stoc_buffer.length >= 2) { if (stoc_buffer.length >= 2) {
stoc_message_length = stoc_buffer.readUInt16LE(0); stoc_message_length = stoc_buffer.readUInt16LE(0);
} else { } else {
if (stoc_buffer.length !== 0) {
log.warn("bad stoc_buffer length", client.ip);
}
break; break;
} }
} else if (stoc_proto === 0) { } else if (stoc_proto === 0) {
if (stoc_buffer.length >= 3) { if (stoc_buffer.length >= 3) {
stoc_proto = stoc_buffer.readUInt8(2); stoc_proto = stoc_buffer.readUInt8(2);
} else { } else {
log.warn("bad stoc_proto length", client.ip);
break; break;
} }
} else { } else {
if (stoc_buffer.length >= 2 + stoc_message_length) { if (stoc_buffer.length >= 2 + stoc_message_length) {
cancel = false;
stanzas = stoc_proto; stanzas = stoc_proto;
if (ygopro.stoc_follows[stoc_proto]) { if (ygopro.stoc_follows[stoc_proto]) {
b = stoc_buffer.slice(3, stoc_message_length - 1 + 3); b = stoc_buffer.slice(3, stoc_message_length - 1 + 3);
if (struct = ygopro.structs[ygopro.proto_structs.STOC[ygopro.constants.STOC[stoc_proto]]]) { if (struct = ygopro.structs[ygopro.proto_structs.STOC[ygopro.constants.STOC[stoc_proto]]]) {
struct._setBuff(b); struct._setBuff(b);
ygopro.stoc_follows[stoc_proto].callback(b, _.clone(struct.fields), client, server); if (ygopro.stoc_follows[stoc_proto].synchronous) {
cancel = ygopro.stoc_follows[stoc_proto].callback(b, _.clone(struct.fields), client, server);
} else {
ygopro.stoc_follows[stoc_proto].callback(b, _.clone(struct.fields), client, server);
}
} else { } else {
ygopro.stoc_follows[stoc_proto].callback(b, null, client, server); if (ygopro.stoc_follows[stoc_proto].synchronous) {
cancel = ygopro.stoc_follows[stoc_proto].callback(b, null, client, server);
} else {
ygopro.stoc_follows[stoc_proto].callback(b, null, client, server);
}
} }
} }
if (!cancel) {
datas.push(stoc_buffer.slice(0, 2 + stoc_message_length));
}
stoc_buffer = stoc_buffer.slice(2 + stoc_message_length); stoc_buffer = stoc_buffer.slice(2 + stoc_message_length);
stoc_message_length = 0; stoc_message_length = 0;
stoc_proto = 0; stoc_proto = 0;
} else { } else {
log.warn("bad stoc_message length", client.ip);
break; break;
} }
} }
looplimit++; looplimit++;
if (looplimit > 800) { if (looplimit > 800) {
log.info("error stoc", client.name); log.info("error stoc", client.name);
server.end(); server.destroy();
break; break;
} }
} }
for (k = 0, len = datas.length; k < len; k++) {
buffer = datas[k];
client.write(buffer);
}
}); });
}).listen(settings.port, function() { }).listen(settings.port, function() {
log.info("server started", settings.port); log.info("server started", settings.port);
...@@ -311,105 +988,70 @@ ...@@ -311,105 +988,70 @@
}); });
ygopro.ctos_follow('JOIN_GAME', false, function(buffer, info, client, server) { ygopro.ctos_follow('JOIN_GAME', false, function(buffer, info, client, server) {
var check, decrypted_buffer, finish, i, id, k, l, len, len1, name, ref, ref1, room, secret, windbot; var check, decrypted_buffer, finish, i, id, k, l, len, len1, name, ref, ref1, replay_id, room, secret, struct;
if (settings.modules.stop) { if (settings.modules.stop) {
ygopro.stoc_send_chat(client, settings.modules.stop, 11); ygopro.stoc_die(client, settings.modules.stop);
ygopro.stoc_send(client, 'ERROR_MSG', { } else if (info.pass.toUpperCase() === "R" && settings.modules.enable_cloud_replay) {
msg: 1, ygopro.stoc_send_chat(client, "以下是您近期的云录像,密码处输入 R#录像编号 即可观看", ygopro.constants.COLORS.BABYBLUE);
code: 2 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);
});
});
}); });
client.end(); setTimeout((function() {
ygopro.stoc_send(client, 'ERROR_MSG', {
/* msg: 1,
else if info.pass.toUpperCase()=="R"
ygopro.stoc_send_chat(client,"以下是您近期的云录像,密码处输入 R#录像编号 即可观看", 14)
redisdb.lrange client.remoteAddress+":replays", 0, 2, (err, result)=>
_.each result, (replay_id,id)=>
redisdb.hgetall "replay:"+replay_id, (err, replay)=>
ygopro.stoc_send_chat(client,"<#{id-0+1}> R##{replay_id} #{replay.player_names} #{replay.date_time}", 14)
return
return
return
#强行等待异步执行完毕_(:з」∠)_
setTimeout (()=>
ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1
code: 2
}
client.end()), 500
else if info.pass[0...2].toUpperCase()=="R#"
replay_id=info.pass.split("#")[1]
if (replay_id>0 and replay_id<=3)
redisdb.lindex client.remoteAddress+":replays", replay_id-1, (err, replay_id)=>
redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
return
else if replay_id
redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
else
ygopro.stoc_send_chat(client,"没有找到录像", 11)
ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1
code: 2 code: 2
} });
client.end() client.destroy();
*/ }), 500);
} else if (info.version !== settings.version) { } else if (info.pass.slice(0, 2).toUpperCase() === "R#" && settings.modules.enable_cloud_replay) {
ygopro.stoc_send_chat(client, settings.modules.update, 11); 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, "没有找到录像");
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, "没有找到录像");
}
} else if (info.pass.toUpperCase() === "W" && settings.modules.enable_cloud_replay) {
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 && (info.version !== 4921 || settings.version !== 4922)) {
ygopro.stoc_send_chat(client, settings.modules.update, ygopro.constants.COLORS.RED);
ygopro.stoc_send(client, 'ERROR_MSG', { ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 4, msg: 4,
code: settings.version code: settings.version
}); });
client.end(); client.destroy();
} else if (!info.pass.length && !settings.modules.enable_random_duel) { } else if (!info.pass.length && !settings.modules.enable_random_duel && !settings.modules.enable_windbot) {
ygopro.stoc_send_chat(client, "房间名为空,请填写主机密码", 11); ygopro.stoc_die(client, "房间名为空,请在主机密码处填写房间名");
ygopro.stoc_send(client, 'ERROR_MSG', { } else if (info.pass.length && settings.modules.mycard_auth && info.pass.slice(0, 2) !== 'AI') {
msg: 1, ygopro.stoc_send_chat(client, '正在读取用户信息...', ygopro.constants.COLORS.BABYBLUE);
code: 2
});
client.end();
} else if (settings.modules.windbot && info.pass.slice(0, 2) === 'AI') {
if (info.pass.length > 3 && info.pass.slice(0, 3) === 'AI#' || info.pass.slice(0, 3) === 'AI_') {
name = info.pass.slice(3);
windbot = _.sample(_.filter(settings.modules.windbot, function(w) {
return w.name === name || w.deck === name;
}));
if (!windbot) {
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Windbot Name)', 11);
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return;
}
} else {
windbot = _.sample(settings.modules.windbot);
}
room = Room.find_or_create_by_name('AI#' + Math.random().toString());
room.windbot = windbot;
room["private"] = true;
client.room = room;
client.room.connect(client);
} else if (info.pass.length && settings.modules.mycard_auth) {
ygopro.stoc_send_chat(client, '正在读取用户信息...', 11);
if (info.pass.length <= 8) { if (info.pass.length <= 8) {
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Length)', 11); ygopro.stoc_die(client, '主机密码不正确 (Invalid Length)');
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return; return;
} }
buffer = new Buffer(info.pass.slice(0, 8), 'base64'); buffer = new Buffer(info.pass.slice(0, 8), 'base64');
if (buffer.length !== 6) { if (buffer.length !== 6) {
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Payload Length)', 11); ygopro.stoc_die(client, '主机密码不正确 (Invalid Payload Length)');
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return; return;
} }
check = function(buf) { check = function(buf) {
...@@ -421,28 +1063,18 @@ ...@@ -421,28 +1063,18 @@
return (checksum & 0xFF) === 0; return (checksum & 0xFF) === 0;
}; };
finish = function(buffer) { finish = function(buffer) {
var action, opt1, opt2, opt3, options; var action, name, opt1, opt2, opt3, options, room;
action = buffer.readUInt8(1) >> 4; action = buffer.readUInt8(1) >> 4;
if (buffer !== decrypted_buffer && (action === 1 || action === 2 || action === 4)) { if (buffer !== decrypted_buffer && (action === 1 || action === 2 || action === 4)) {
ygopro.stoc_send_chat(client, '主机密码不正确 (Unauthorized)', 11); ygopro.stoc_die(client, '主机密码不正确 (Unauthorized)');
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return; return;
} }
switch (action) { switch (action) {
case 1: case 1:
case 2: case 2:
name = crypto.createHash('md5').update(info.pass + client.name).digest('base64').slice(0, 10).replace('+', '-').replace('/', '_'); name = crypto.createHash('md5').update(info.pass + client.name).digest('base64').slice(0, 10).replace('+', '-').replace('/', '_');
if (Room.find_by_name(name)) { if (ROOM_find_by_name(name)) {
ygopro.stoc_send_chat(client, '主机密码不正确 (Already Existed)', 11); ygopro.stoc_die(client, '主机密码不正确 (Already Existed)');
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return; return;
} }
opt1 = buffer.readUInt8(2); opt1 = buffer.readUInt8(2);
...@@ -469,32 +1101,29 @@ ...@@ -469,32 +1101,29 @@
break; break;
case 3: case 3:
name = info.pass.slice(8); name = info.pass.slice(8);
room = Room.find_by_name(name); room = ROOM_find_by_name(name);
if (!room) { if (!room) {
ygopro.stoc_send_chat(client, '主机密码不正确 (Not Found)', 11); ygopro.stoc_die(client, '主机密码不正确 (Not Found)');
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return; return;
} }
break; break;
case 4: case 4:
room = Room.find_or_create_by_name('M#' + info.pass.slice(8)); room = ROOM_find_or_create_by_name('M#' + info.pass.slice(8));
room["private"] = true; room["private"] = true;
break; break;
default: default:
ygopro.stoc_send_chat(client, '主机密码不正确 (Invalid Action)', 11); ygopro.stoc_die(client, '主机密码不正确 (Invalid Action)');
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return; return;
} }
client.room = room; if (!room) {
return client.room.connect(client); ygopro.stoc_die(client, "服务器已经爆满,请稍候再试");
} else if (room.error) {
ygopro.stoc_die(client, room.error);
} else {
client.setTimeout(300000);
client.rid = _.indexOf(ROOM_all, room);
room.connect(client);
}
}; };
if (id = users_cache[client.name]) { if (id = users_cache[client.name]) {
secret = id % 65535 + 1; secret = id % 65535 + 1;
...@@ -512,7 +1141,7 @@ ...@@ -512,7 +1141,7 @@
baseUrl: settings.modules.mycard_auth, baseUrl: settings.modules.mycard_auth,
url: '/users/' + encodeURIComponent(client.name) + '.json', url: '/users/' + encodeURIComponent(client.name) + '.json',
qs: { qs: {
api_key: 'dc7298a754828b3d26b709f035a0eeceb43e73cbd8c4fa8dec18951f8a95d2bc', api_key: process.env.MYCARD_AUTH_KEY,
api_username: client.name, api_username: client.name,
skip_track_visit: true skip_track_visit: true
}, },
...@@ -532,103 +1161,120 @@ ...@@ -532,103 +1161,120 @@
} }
} }
if (!check(buffer)) { if (!check(buffer)) {
ygopro.stoc_send_chat(client, '主机密码不正确 (Checksum Failed)', 11); ygopro.stoc_die(client, '主机密码不正确 (Checksum Failed)');
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
return; return;
} }
users_cache[client.name] = body.user.id; users_cache[client.name] = body.user.id;
return finish(buffer); return finish(buffer);
}); });
} else if (info.pass.length && !Room.validate(info.pass)) { } else if (!client.name || client.name === "") {
ygopro.stoc_send_chat(client, "房间密码不正确", 11); ygopro.stoc_die(client, "请输入正确的用户名");
ygopro.stoc_send(client, 'ERROR_MSG', { } else if (ROOM_connected_ip[client.ip] > 5) {
msg: 1, log.warn("MULTI LOGIN", client.name, client.ip);
code: 2 ygopro.stoc_die(client, "同时开启的客户端数量过多 " + client.ip);
}); } else if (_.indexOf(settings.ban.banned_user, client.name) > -1) {
client.end(); settings.ban.banned_ip.push(client.ip);
} else if (client.name === '[INCORRECT]') { log.warn("BANNED USER LOGIN", client.name, client.ip);
ygopro.stoc_send(client, 'ERROR_MSG', { ygopro.stoc_die(client, "您的账号已被封禁");
msg: 1, } else if (_.indexOf(settings.ban.banned_ip, client.ip) > -1) {
code: 2 log.warn("BANNED IP LOGIN", client.name, client.ip);
}); ygopro.stoc_die(client, "您的账号已被封禁");
client.end(); } else if (_.any(settings.ban.badword_level3, function(badword) {
} else if (_.indexOf(settings.BANNED_user, client.name) > -1) { var regexp;
settings.BANNED_IP.push(client.remoteAddress); regexp = new RegExp(badword, 'i');
log.info("BANNED USER LOGIN", client.name, client.remoteAddress); return name.match(regexp);
ygopro.stoc_send_chat(client, "您的账号已被封禁", 11); }, name = client.name)) {
ygopro.stoc_send(client, 'ERROR_MSG', { log.warn("BAD NAME LEVEL 3", client.name, client.ip);
msg: 1, ygopro.stoc_die(client, "您的用户名存在不适当的内容");
code: 2 } else if (_.any(settings.ban.badword_level2, function(badword) {
}); var regexp;
client.end(); regexp = new RegExp(badword, 'i');
} else if (_.indexOf(settings.BANNED_IP, client.remoteAddress) > -1) { return name.match(regexp);
log.info("BANNED IP LOGIN", client.name, client.remoteAddress); }, name = client.name)) {
ygopro.stoc_send_chat(client, "您的账号已被封禁", 11); log.warn("BAD NAME LEVEL 2", client.name, client.ip);
ygopro.stoc_send(client, 'ERROR_MSG', { ygopro.stoc_die(client, "您的用户名存在不适当的内容");
msg: 1, } else if (_.any(settings.ban.badword_level1, function(badword) {
code: 2 var regexp;
}); regexp = new RegExp(badword, 'i');
client.end(); return name.match(regexp);
}, name = client.name)) {
log.warn("BAD NAME LEVEL 1", client.name, client.ip);
ygopro.stoc_die(client, "您的用户名存在不适当的内容,请注意更改");
} else if (info.pass.length && !ROOM_validate(info.pass)) {
ygopro.stoc_die(client, "房间密码不正确");
} else { } else {
room = Room.find_or_create_by_name(info.pass, client.remoteAddress); if (info.version === 4921 && settings.version === 4922) {
info.version = settings.version;
struct = ygopro.structs["CTOS_JoinGame"];
struct._setBuff(buffer);
struct.set("version", info.version);
buffer = struct.buffer;
}
room = ROOM_find_or_create_by_name(info.pass, client.ip);
if (!room) { if (!room) {
ygopro.stoc_send_chat(client, "服务器已经爆满,请稍候再试", 11); ygopro.stoc_die(client, "服务器已经爆满,请稍候再试");
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
} else if (room.error) { } else if (room.error) {
ygopro.stoc_send_chat(client, room.error, 11); ygopro.stoc_die(client, room.error);
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
} else if (room.started) { } else if (room.started) {
if (settings.modules.post_start_watching) { if (settings.modules.enable_halfway_watch) {
client.room = room; client.setTimeout(300000);
client.rid = _.indexOf(ROOM_all, room);
client.is_post_watcher = true; client.is_post_watcher = true;
ygopro.stoc_send_chat_to_room(client.room, client.name + " 加入了观战"); ygopro.stoc_send_chat_to_room(room, client.name + " 加入了观战");
client.room.watchers.push(client); room.watchers.push(client);
ygopro.stoc_send_chat(client, "观战中", 14); ygopro.stoc_send_chat(client, "观战中", ygopro.constants.COLORS.BABYBLUE);
ref1 = client.room.watcher_buffers; ref1 = room.watcher_buffers;
for (l = 0, len1 = ref1.length; l < len1; l++) { for (l = 0, len1 = ref1.length; l < len1; l++) {
buffer = ref1[l]; buffer = ref1[l];
client.write(buffer); client.write(buffer);
} }
} else { } else {
ygopro.stoc_send_chat(client, "决斗已开始,不允许观战", 11); ygopro.stoc_die(client, "决斗已开始,不允许观战");
ygopro.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
client.end();
} }
} else { } else {
client.room = room; client.setTimeout(300000);
client.room.connect(client); client.rid = _.indexOf(ROOM_all, room);
room.connect(client);
} }
} }
}); });
ygopro.stoc_follow('JOIN_GAME', false, function(buffer, info, client, server) { ygopro.stoc_follow('JOIN_GAME', false, function(buffer, info, client, server) {
var watcher; var recorder, room, watcher;
if (!client.room) { room = ROOM_all[client.rid];
if (!room) {
return; return;
} }
if (settings.modules.welcome) { if (settings.modules.welcome) {
ygopro.stoc_send_chat(client, settings.modules.welcome); ygopro.stoc_send_chat(client, settings.modules.welcome, ygopro.constants.COLORS.GREEN);
} }
if (client.room.welcome) { if (room.welcome) {
ygopro.stoc_send_chat(client, client.room.welcome, 14); ygopro.stoc_send_chat(client, room.welcome, ygopro.constants.COLORS.BABYBLUE);
} }
if (settings.modules.post_start_watching && !client.room.watcher) { if (!room.recorder) {
client.room.watcher = watcher = net.connect(client.room.port, function() { 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,
gameid: 2577,
some_unknown_mysterious_fucking_thing: 0,
pass: ""
});
ygopro.ctos_send(recorder, 'HS_TOOBSERVER');
});
recorder.on('data', function(data) {
room = ROOM_all[client.rid];
if (!(room && settings.modules.enable_cloud_replay)) {
return;
}
room.recorder_buffers.push(data);
});
recorder.on('error', function(error) {});
}
if (settings.modules.enable_halfway_watch && !room.watcher) {
room.watcher = watcher = net.connect(room.port, function() {
ygopro.ctos_send(watcher, 'PLAYER_INFO', { ygopro.ctos_send(watcher, 'PLAYER_INFO', {
name: "the Big Brother" name: "the Big Brother"
}); });
...@@ -642,11 +1288,12 @@ ...@@ -642,11 +1288,12 @@
}); });
watcher.on('data', function(data) { watcher.on('data', function(data) {
var k, len, ref, w; var k, len, ref, w;
if (!client.room) { room = ROOM_all[client.rid];
if (!room) {
return; return;
} }
client.room.watcher_buffers.push(data); room.watcher_buffers.push(data);
ref = client.room.watchers; ref = room.watchers;
for (k = 0, len = ref.length; k < len; k++) { for (k = 0, len = ref.length; k < len; k++) {
w = ref[k]; w = ref[k];
if (w) { if (w) {
...@@ -658,8 +1305,7 @@ ...@@ -658,8 +1305,7 @@
} }
}); });
if (settings.modules.dialogues) { load_dialogues = function() {
dialogues = {};
request({ request({
url: settings.modules.dialogues, url: settings.modules.dialogues,
json: true json: true
...@@ -669,41 +1315,49 @@ ...@@ -669,41 +1315,49 @@
} else if (error || !body) { } else if (error || !body) {
log.warn('dialogues error', error, response); log.warn('dialogues error', error, response);
} else { } else {
dialogues = body; nconf.myset(settings, "dialogues", body);
log.info("dialogues loaded", _.size(body));
} }
}); });
};
if (settings.modules.dialogues) {
load_dialogues();
} }
ygopro.stoc_follow('GAME_MSG', false, function(buffer, info, client, server) { ygopro.stoc_follow('GAME_MSG', false, function(buffer, info, client, server) {
var card, k, len, line, msg, playertype, pos, ref, ref1, ref2, val; var card, k, len, line, msg, playertype, pos, reason, ref, ref1, ref2, room, val;
room = ROOM_all[client.rid];
if (!room) {
return;
}
msg = buffer.readInt8(0); msg = buffer.readInt8(0);
if (msg >= 10 && msg < 30) { if (msg >= 10 && msg < 30) {
client.room.waiting_for_player = client; room.waiting_for_player = client;
client.room.last_active_time = moment(); room.last_active_time = moment();
} }
if (ygopro.constants.MSG[msg] === 'START') { if (ygopro.constants.MSG[msg] === 'START') {
playertype = buffer.readUInt8(1); playertype = buffer.readUInt8(1);
client.is_first = !(playertype & 0xf); client.is_first = !(playertype & 0xf);
client.lp = client.room.hostinfo.start_lp; client.lp = room.hostinfo.start_lp;
}
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 ygopro.constants.MSG[msg] == 'WIN' and _.startsWith(client.room.name, 'M#') and client.is_host
pos = buffer.readUInt8(1)
pos = 1 - pos unless client.is_first or pos == 2
reason = buffer.readUInt8(2)
#log.info {winner: pos, reason: reason}
client.room.duels.push {winner: pos, reason: reason}
*/
if (ygopro.constants.MSG[msg] === 'DAMAGE' && client.is_host) { if (ygopro.constants.MSG[msg] === 'DAMAGE' && client.is_host) {
pos = buffer.readUInt8(1); pos = buffer.readUInt8(1);
if (!client.is_first) { if (!client.is_first) {
pos = 1 - pos; pos = 1 - pos;
} }
val = buffer.readInt32LE(2); val = buffer.readInt32LE(2);
client.room.dueling_players[pos].lp -= val; room.dueling_players[pos].lp -= val;
if ((0 < (ref = client.room.dueling_players[pos].lp) && ref <= 100)) { if ((0 < (ref = room.dueling_players[pos].lp) && ref <= 100)) {
ygopro.stoc_send_chat_to_room(client.room, "你的生命已经如风中残烛了!", 15); ygopro.stoc_send_chat_to_room(room, "你的生命已经如风中残烛了!", ygopro.constants.COLORS.PINK);
} }
} }
if (ygopro.constants.MSG[msg] === 'RECOVER' && client.is_host) { if (ygopro.constants.MSG[msg] === 'RECOVER' && client.is_host) {
...@@ -712,7 +1366,7 @@ ...@@ -712,7 +1366,7 @@
pos = 1 - pos; pos = 1 - pos;
} }
val = buffer.readInt32LE(2); val = buffer.readInt32LE(2);
client.room.dueling_players[pos].lp += val; room.dueling_players[pos].lp += val;
} }
if (ygopro.constants.MSG[msg] === 'LPUPDATE' && client.is_host) { if (ygopro.constants.MSG[msg] === 'LPUPDATE' && client.is_host) {
pos = buffer.readUInt8(1); pos = buffer.readUInt8(1);
...@@ -720,7 +1374,7 @@ ...@@ -720,7 +1374,7 @@
pos = 1 - pos; pos = 1 - pos;
} }
val = buffer.readInt32LE(2); val = buffer.readInt32LE(2);
client.room.dueling_players[pos].lp = val; room.dueling_players[pos].lp = val;
} }
if (ygopro.constants.MSG[msg] === 'PAY_LPCOST' && client.is_host) { if (ygopro.constants.MSG[msg] === 'PAY_LPCOST' && client.is_host) {
pos = buffer.readUInt8(1); pos = buffer.readUInt8(1);
...@@ -728,19 +1382,19 @@ ...@@ -728,19 +1382,19 @@
pos = 1 - pos; pos = 1 - pos;
} }
val = buffer.readInt32LE(2); val = buffer.readInt32LE(2);
client.room.dueling_players[pos].lp -= val; room.dueling_players[pos].lp -= val;
if ((0 < (ref1 = client.room.dueling_players[pos].lp) && ref1 <= 100)) { if ((0 < (ref1 = room.dueling_players[pos].lp) && ref1 <= 100)) {
ygopro.stoc_send_chat_to_room(client.room, "背水一战!", 15); ygopro.stoc_send_chat_to_room(room, "背水一战!", ygopro.constants.COLORS.PINK);
} }
} }
if (settings.modules.dialogues) { if (settings.modules.dialogues) {
if (ygopro.constants.MSG[msg] === 'SUMMONING' || ygopro.constants.MSG[msg] === 'SPSUMMONING') { if (ygopro.constants.MSG[msg] === 'SUMMONING' || ygopro.constants.MSG[msg] === 'SPSUMMONING') {
card = buffer.readUInt32LE(1); card = buffer.readUInt32LE(1);
if (dialogues[card]) { if (settings.dialogues[card]) {
ref2 = _.lines(dialogues[card][Math.floor(Math.random() * dialogues[card].length)]); ref2 = _.lines(settings.dialogues[card][Math.floor(Math.random() * settings.dialogues[card].length)]);
for (k = 0, len = ref2.length; k < len; k++) { for (k = 0, len = ref2.length; k < len; k++) {
line = ref2[k]; line = ref2[k];
ygopro.stoc_send_chat(client, line, 15); ygopro.stoc_send_chat(client, line, ygopro.constants.COLORS.PINK);
} }
} }
} }
...@@ -748,15 +1402,23 @@ ...@@ -748,15 +1402,23 @@
}); });
ygopro.ctos_follow('HS_KICK', true, function(buffer, info, client, server) { ygopro.ctos_follow('HS_KICK', true, function(buffer, info, client, server) {
var k, len, player, ref; var k, len, player, ref, room;
if (!client.room) { room = ROOM_all[client.rid];
if (!room) {
return; return;
} }
ref = client.room.players; ref = room.players;
for (k = 0, len = ref.length; k < len; k++) { for (k = 0, len = ref.length; k < len; k++) {
player = ref[k]; player = ref[k];
if (player && player.pos === info.pos && player !== client) { if (player && player.pos === info.pos && player !== client) {
ygopro.stoc_send_chat_to_room(client.room, player.name + " 被请出了房间", 11); client.kick_count = client.kick_count ? client.kick_count + 1 : 1;
if (client.kick_count >= 5) {
ygopro.stoc_send_chat_to_room(room, client.name + " 被系统请出了房间", ygopro.constants.COLORS.RED);
ROOM_ban_player(player.name, player.ip, "挂房间");
client.destroy();
return true;
}
ygopro.stoc_send_chat_to_room(room, player.name + " 被请出了房间", ygopro.constants.COLORS.RED);
} }
} }
return false; return false;
...@@ -771,27 +1433,28 @@ ...@@ -771,27 +1433,28 @@
}); });
ygopro.stoc_follow('HS_PLAYER_CHANGE', false, function(buffer, info, client, server) { ygopro.stoc_follow('HS_PLAYER_CHANGE', false, function(buffer, info, client, server) {
var is_ready, k, len, player, pos, ref; var is_ready, k, len, player, pos, ref, room;
if (!(client.room && client.room.max_player && client.is_host)) { room = ROOM_all[client.rid];
if (!(room && room.max_player && client.is_host)) {
return; return;
} }
pos = info.status >> 4; pos = info.status >> 4;
is_ready = (info.status & 0xf) === 9; is_ready = (info.status & 0xf) === 9;
if (pos < client.room.max_player) { if (pos < room.max_player) {
client.room.ready_player_count_without_host = 0; room.ready_player_count_without_host = 0;
ref = client.room.players; ref = room.players;
for (k = 0, len = ref.length; k < len; k++) { for (k = 0, len = ref.length; k < len; k++) {
player = ref[k]; player = ref[k];
if (player.pos === pos) { if (player.pos === pos) {
player.is_ready = is_ready; player.is_ready = is_ready;
} }
if (!player.is_host) { if (!player.is_host) {
client.room.ready_player_count_without_host += player.is_ready; room.ready_player_count_without_host += player.is_ready;
} }
} }
if (client.room.ready_player_count_without_host >= client.room.max_player - 1) { if (room.ready_player_count_without_host >= room.max_player - 1) {
setTimeout((function() { setTimeout((function() {
wait_room_start(client.room, 20); wait_room_start(ROOM_all[client.rid], 20);
}), 1000); }), 1000);
} }
} }
...@@ -803,7 +1466,7 @@ ...@@ -803,7 +1466,7 @@
time -= 1; time -= 1;
if (time) { if (time) {
if (!(time % 5)) { if (!(time % 5)) {
ygopro.stoc_send_chat_to_room(room, "" + (time <= 9 ? ' ' : '') + time + "秒后房主若不开始游戏将被请出房间", time <= 9 ? 11 : 8); ygopro.stoc_send_chat_to_room(room, "" + (time <= 9 ? ' ' : '') + time + "秒后房主若不开始游戏将被请出房间", time <= 9 ? ygopro.constants.COLORS.RED : ygopro.constants.COLORS.LIGHTBLUE);
} }
setTimeout((function() { setTimeout((function() {
wait_room_start(room, time); wait_room_start(room, time);
...@@ -813,9 +1476,9 @@ ...@@ -813,9 +1476,9 @@
for (k = 0, len = ref.length; k < len; k++) { for (k = 0, len = ref.length; k < len; k++) {
player = ref[k]; player = ref[k];
if (player && player.is_host) { if (player && player.is_host) {
Room.ban_player(player.name, player.ip, "挂房间"); ROOM_ban_player(player.name, player.ip, "挂房间");
ygopro.stoc_send_chat_to_room(room, player.name + " 被系统请出了房间", 11); ygopro.stoc_send_chat_to_room(room, player.name + " 被系统请出了房间", ygopro.constants.COLORS.RED);
player.end(); player.destroy();
} }
} }
} }
...@@ -823,37 +1486,46 @@ ...@@ -823,37 +1486,46 @@
}; };
ygopro.stoc_send_random_tip = function(client) { ygopro.stoc_send_random_tip = function(client) {
if (tips) { if (settings.modules.tips) {
ygopro.stoc_send_chat(client, "Tip: " + tips[Math.floor(Math.random() * 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) { ygopro.stoc_send_random_tip_to_room = function(room) {
if (tips) { if (settings.modules.tips) {
ygopro.stoc_send_chat_to_room(room, "Tip: " + tips[Math.floor(Math.random() * tips.length)]); ygopro.stoc_send_chat_to_room(room, "Tip: " + settings.tips[Math.floor(Math.random() * settings.tips.length)]);
} }
}; };
setInterval(function() { load_tips = function() {
var k, len, ref, room;
ref = Room.all;
for (k = 0, len = ref.length; k < len; k++) {
room = ref[k];
if (!(room && room.started)) {
ygopro.stoc_send_random_tip_to_room(room);
}
}
}, 30000);
tips = null;
if (settings.modules.tips) {
request({ request({
url: settings.modules.tips, url: settings.modules.tips,
json: true json: true
}, function(error, response, body) { }, function(error, response, body) {
tips = 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) {
load_tips();
setInterval(function() {
var k, len, room;
for (k = 0, len = ROOM_all.length; k < len; k++) {
room = ROOM_all[k];
if (room && room.established) {
if (!(room && room.started)) {
ygopro.stoc_send_random_tip_to_room(room);
}
}
}
}, 30000);
} }
if (settings.modules.mycard_auth && process.env.MYCARD_AUTH_DATABASE) { if (settings.modules.mycard_auth && process.env.MYCARD_AUTH_DATABASE) {
...@@ -862,7 +1534,7 @@ ...@@ -862,7 +1534,7 @@
if (error) { if (error) {
throw error; throw error;
} }
return client.query('SELECT username, id from users', function(error, result) { client.query('SELECT username, id from users', function(error, result) {
var k, len, ref, row; var k, len, ref, row;
if (error) { if (error) {
throw error; throw error;
...@@ -873,69 +1545,71 @@ ...@@ -873,69 +1545,71 @@
row = ref[k]; row = ref[k];
users_cache[row.username] = row.id; users_cache[row.username] = row.id;
} }
return console.log("users loaded", _.keys(users_cache).length); console.log("users loaded", _.keys(users_cache).length);
}); });
}); });
} }
ygopro.stoc_follow('DUEL_START', false, function(buffer, info, client, server) { ygopro.stoc_follow('DUEL_START', false, function(buffer, info, client, server) {
var k, len, player, ref; var deck_name, deck_text, k, len, player, ref, room;
if (!client.room) { room = ROOM_all[client.rid];
if (!room) {
return; return;
} }
if (!client.room.started) { if (!room.started) {
client.room.started = true; room.started = true;
if (settings.modules.enable_websocket_roomlist && !client.room["private"]) { if (settings.modules.enable_websocket_roomlist && !room["private"]) {
roomlist["delete"](client.room.name); roomlist["delete"](room.name);
} }
client.room.dueling_players = []; room.dueling_players = [];
ref = client.room.players; ref = room.players;
for (k = 0, len = ref.length; k < len; k++) { for (k = 0, len = ref.length; k < len; k++) {
player = ref[k]; player = ref[k];
if (!(player.pos !== 7)) { if (!(player.pos !== 7)) {
continue; continue;
} }
client.room.dueling_players[player.pos] = player; room.dueling_players[player.pos] = player;
client.room.player_datas.push({ room.player_datas.push({
ip: player.remoteAddress, ip: player.ip,
name: player.name name: player.name
}); });
if (client.room.windbot) {
client.room.dueling_players[1 - player.pos] = {};
}
} }
} }
if (settings.modules.tips) { if (settings.modules.tips) {
ygopro.stoc_send_random_tip(client); ygopro.stoc_send_random_tip(client);
} }
if (settings.modules.enable_deck_log && client.main && client.main.length && !client.deck_saved) {
deck_text = '#ygosrv233 deck log\r\n#main\r\n' + client.main.join('\r\n') + '\r\n!side\r\n' + client.side.join('\r\n') + '\r\n';
deck_name = moment().format('YYYY-MM-DD HH-mm-ss') + ' ' + room.port + ' ' + client.pos + ' ' + client.name.replace(/\//g, '_');
fs.writeFile('decks_save\/' + deck_name + '.ydk', deck_text, 'utf-8', function(err) {
if (err) {
return log.warn('DECK SAVE ERROR', err);
}
});
client.deck_saved = true;
}
}); });
ygopro.ctos_follow('CHAT', true, function(buffer, info, client, server) { ygopro.ctos_follow('CHAT', true, function(buffer, info, client, server) {
var cancel; var cancel, cmd, msg, name, oldmsg, room, struct, windbot;
cancel = _.startsWith(_.trim(info.msg), "/"); room = ROOM_all[client.rid];
if (!(cancel || !client.room.random_type)) { if (!room) {
client.room.last_active_time = moment(); return;
} }
switch (_.trim(info.msg)) { msg = _.trim(info.msg);
case '/ping': cancel = _.startsWith(msg, "/");
execFile('ss', ['-it', "dst " + client.remoteAddress + ":" + client.remotePort], function(error, stdout, stderr) { if (!(cancel || !room.random_type)) {
var line; room.last_active_time = moment();
if (error) { }
ygopro.stoc_send_chat_to_room(client.room, error); cmd = msg.split(' ');
} else { switch (cmd[0]) {
line = _.lines(stdout)[2];
if (line.indexOf('rtt') !== -1) {
ygopro.stoc_send_chat_to_room(client.room, line);
} else {
ygopro.stoc_send_chat_to_room(client.room, stdout);
}
}
});
break;
case '/help': case '/help':
ygopro.stoc_send_chat(client, "YGOSrv233 指令帮助"); ygopro.stoc_send_chat(client, "YGOSrv233 指令帮助");
ygopro.stoc_send_chat(client, "/help 显示这个帮助信息"); ygopro.stoc_send_chat(client, "/help 显示这个帮助信息");
ygopro.stoc_send_chat(client, "/roomname 显示当前房间的名字"); ygopro.stoc_send_chat(client, "/roomname 显示当前房间的名字");
if (settings.modules.enable_windbot) {
ygopro.stoc_send_chat(client, "/ai 添加一个AI,/ai 角色名 可指定添加的角色");
}
if (settings.modules.tips) { if (settings.modules.tips) {
ygopro.stoc_send_chat(client, "/tip 显示一条提示"); ygopro.stoc_send_chat(client, "/tip 显示一条提示");
} }
...@@ -945,20 +1619,98 @@ ...@@ -945,20 +1619,98 @@
ygopro.stoc_send_random_tip(client); ygopro.stoc_send_random_tip(client);
} }
break; break;
case '/roomname': case '/ai':
if (client.room) { if (settings.modules.enable_windbot) {
ygopro.stoc_send_chat(client, "您当前的房间名是 " + client.room.name); 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, "未找到该AI角色或卡组", ygopro.constants.COLORS.RED);
return;
}
} else {
windbot = _.sample(settings.modules.windbots);
}
room.add_windbot(windbot);
} }
break; break;
case '/test': case '/roomname':
ygopro.stoc_send_hint_card_to_room(client.room, 2333365); if (room) {
ygopro.stoc_send_chat(client, "您当前的房间名是 " + room.name, ygopro.constants.COLORS.BABYBLUE);
}
}
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, "您已被禁言!", 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);
cancel = true;
if (client.abuse_count > 0) {
ygopro.stoc_send_chat(client, "您的发言存在严重不适当的内容,禁止您使用随机对战功能!", ygopro.constants.COLORS.RED);
ROOM_ban_player(client.name, client.ip, "发言违规");
ROOM_ban_player(client.name, client.ip, "发言违规", 3);
client.destroy();
return true;
} else {
client.abuse_count = client.abuse_count + 4;
ygopro.stoc_send_chat(client, "您的发言存在不适当的内容,发送失败!", ygopro.constants.COLORS.RED);
}
} 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);
client.abuse_count = client.abuse_count + 3;
ygopro.stoc_send_chat(client, "您的发言存在不适当的内容,发送失败!", 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);
client.abuse_count = client.abuse_count + 1;
ygopro.stoc_send_chat(client, "请使用文明用语!");
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);
}
}
if (client.abuse_count >= 5) {
ygopro.stoc_send_chat_to_room(room, client.name + " 已被禁言!", ygopro.constants.COLORS.RED);
ROOM_ban_player(client.name, client.ip, "发言违规");
} }
return cancel; return cancel;
}); });
ygopro.ctos_follow('UPDATE_DECK', false, function(buffer, info, client, server) { ygopro.ctos_follow('UPDATE_DECK', true, function(buffer, info, client, server) {
var i, main, side; var buff_main, buff_side, card, current_deck, deck, deck_array, deck_main, deck_side, deck_text, deckbuf, decks, found_deck, i, k, l, len, len1, line, room, struct;
main = (function() { room = ROOM_all[client.rid];
if (!room) {
return false;
}
buff_main = (function() {
var k, ref, results; var k, ref, results;
results = []; results = [];
for (i = k = 0, ref = info.mainc; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) { for (i = k = 0, ref = info.mainc; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
...@@ -966,7 +1718,7 @@ ...@@ -966,7 +1718,7 @@
} }
return results; return results;
})(); })();
side = (function() { buff_side = (function() {
var k, ref, ref1, results; var k, ref, ref1, results;
results = []; results = [];
for (i = k = ref = info.mainc, ref1 = info.mainc + info.sidec; ref <= ref1 ? k < ref1 : k > ref1; i = ref <= ref1 ? ++k : --k) { for (i = k = ref = info.mainc, ref1 = info.mainc + info.sidec; ref <= ref1 ? k < ref1 : k > ref1; i = ref <= ref1 ? ++k : --k) {
...@@ -974,105 +1726,246 @@ ...@@ -974,105 +1726,246 @@
} }
return results; return results;
})(); })();
client.main = main; client.main = buff_main;
client.side = side; client.side = buff_side;
if (room.random_type) {
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) {
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 (k = 0, len = decks.length; k < len; k++) {
deck = decks[k];
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 (l = 0, len1 = deck_array.length; l < len1; l++) {
line = deck_array[l];
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, "成功使用卡组 " + found_deck + " 参加比赛。", ygopro.constants.COLORS.BABYBLUE);
} else {
ygopro.stoc_send_chat(client, "您的卡组与报名卡组 " + found_deck + " 不符。注意卡组不能有包括卡片顺序在内的任何修改。", ygopro.constants.COLORS.RED);
}
} else {
ygopro.stoc_send_chat(client, client.name + ",没有找到您的报名信息,请确定您使用昵称与报名ID一致。", ygopro.constants.COLORS.RED);
}
}
return false;
}); });
ygopro.ctos_follow('RESPONSE', false, function(buffer, info, client, server) { ygopro.ctos_follow('RESPONSE', false, function(buffer, info, client, server) {
if (!(client.room && client.room.random_type)) { var room;
room = ROOM_all[client.rid];
if (!(room && room.random_type)) {
return; return;
} }
client.room.last_active_time = moment(); room.last_active_time = moment();
}); });
ygopro.ctos_follow('HAND_RESULT', false, function(buffer, info, client, server) { ygopro.ctos_follow('HAND_RESULT', false, function(buffer, info, client, server) {
if (!(client.room && client.room.random_type)) { var room;
room = ROOM_all[client.rid];
if (!(room && room.random_type)) {
return; return;
} }
if (client.is_host) { if (client.is_host) {
client.room.waiting_for_player = client.room.waiting_for_player2; room.waiting_for_player = room.waiting_for_player2;
} }
client.room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's'); room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's');
}); });
ygopro.ctos_follow('TP_RESULT', false, function(buffer, info, client, server) { ygopro.ctos_follow('TP_RESULT', false, function(buffer, info, client, server) {
if (!(client.room && client.room.random_type)) { var room;
room = ROOM_all[client.rid];
if (!(room && room.random_type)) {
return; return;
} }
client.room.last_active_time = moment(); room.last_active_time = moment();
}); });
ygopro.stoc_follow('SELECT_HAND', false, function(buffer, info, client, server) { ygopro.stoc_follow('SELECT_HAND', false, function(buffer, info, client, server) {
if (!(client.room && client.room.random_type)) { var room;
room = ROOM_all[client.rid];
if (!(room && room.random_type)) {
return; return;
} }
if (client.is_host) { if (client.is_host) {
client.room.waiting_for_player = client; room.waiting_for_player = client;
} else { } else {
client.room.waiting_for_player2 = client; room.waiting_for_player2 = client;
} }
client.room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's'); room.last_active_time = moment().subtract(settings.modules.hang_timeout - 19, 's');
}); });
ygopro.stoc_follow('SELECT_TP', false, function(buffer, info, client, server) { ygopro.stoc_follow('SELECT_TP', false, function(buffer, info, client, server) {
if (!(client.room && client.room.random_type)) { var room;
room = ROOM_all[client.rid];
if (!(room && room.random_type)) {
return; return;
} }
client.room.waiting_for_player = client; room.waiting_for_player = client;
client.room.last_active_time = moment(); room.last_active_time = moment();
}); });
setInterval(function() { ygopro.stoc_follow('CHANGE_SIDE', false, function(buffer, info, client, server) {
var k, len, ref, room, time_passed; var room;
ref = Room.all; room = ROOM_all[client.rid];
for (k = 0, len = ref.length; k < len; k++) { if (!(room && room.random_type)) {
room = ref[k]; return;
if (!(room && room.started && room.random_type && room.last_active_time && room.waiting_for_player)) { }
continue; 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, player, room;
room = ROOM_all[client.rid];
if (!room) {
return settings.modules.tournament_mode.enabled;
}
if (settings.modules.enable_cloud_replay && room.random_type) {
Cloud_replay_ids.push(room.cloud_replay_id);
}
if (settings.modules.tournament_mode.enabled) {
if (client.is_host) {
duellog = {
time: moment().format('YYYY-MM-DD HH:mm:ss'),
name: room.name,
roomid: room.port.toString(),
cloud_replay_id: "R#" + room.cloud_replay_id,
players: (function() {
var k, len, ref, results;
ref = room.players;
results = [];
for (k = 0, len = ref.length; k < len; k++) {
player = ref[k];
results.push({
name: player.name,
winner: player.pos === room.winner
});
}
return results;
})()
};
settings.modules.tournament_mode.duel_log.push(duellog);
nconf.myset(settings, "modules:tournament_mode:duel_log", settings.modules.tournament_mode.duel_log);
} }
time_passed = Math.floor((moment() - room.last_active_time) / 1000); if (settings.modules.enable_cloud_replay) {
if (time_passed >= settings.modules.hang_timeout) { ygopro.stoc_send_chat(client, "本场比赛云录像:R#" + room.cloud_replay_id + "。将于本局结束后可播放。", ygopro.constants.COLORS.BABYBLUE);
room.last_active_time = moment();
Room.ban_player(room.waiting_for_player.name, room.waiting_for_player.ip, "挂机");
ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " 被系统请出了房间", 11);
room.waiting_for_player.server.end();
} else if (time_passed >= (settings.modules.hang_timeout - 20) && !(time_passed % 10)) {
ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " 已经很久没有操作了,若继续挂机,将于" + (settings.modules.hang_timeout - time_passed) + "秒后被请出房间", 11);
} }
return true;
} else {
return false;
} }
}, 1000); });
if (settings.modules.enable_random_duel) {
setInterval(function() {
var k, len, room, time_passed;
for (k = 0, len = ROOM_all.length; k < len; k++) {
room = ROOM_all[k];
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.hang_timeout) {
room.last_active_time = moment();
ROOM_ban_player(room.waiting_for_player.name, room.waiting_for_player.ip, "挂机");
ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " 被系统请出了房间", ygopro.constants.COLORS.RED);
room.waiting_for_player.server.destroy();
} else if (time_passed >= (settings.modules.hang_timeout - 20) && !(time_passed % 10)) {
ygopro.stoc_send_chat_to_room(room, room.waiting_for_player.name + " 已经很久没有操作了,若继续挂机,将于" + (settings.modules.hang_timeout - time_passed) + "秒后被请出房间", ygopro.constants.COLORS.RED);
}
}
}, 1000);
}
if (settings.modules.spawn_windbot) {
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.on('data', function(data) {
log.warn('WindBot Error:', data);
});
}
if (settings.modules.http) { if (settings.modules.http) {
requestListener = function(request, response) { requestListener = function(request, response) {
var k, len, parseQueryString, pass_validated, player, ref, room, roomsjson, u; var duellog, k, len, parseQueryString, pass_validated, player, room, roomsjson, u;
parseQueryString = true; parseQueryString = true;
u = url.parse(request.url, parseQueryString); u = url.parse(request.url, parseQueryString);
pass_validated = u.query.pass === settings.modules.http.password; pass_validated = u.query.pass === settings.modules.http.password;
if (u.pathname === '/api/getrooms') { if (u.pathname === '/api/getrooms') {
if (u.query.pass && !pass_validated) { if (!pass_validated) {
response.writeHead(200); response.writeHead(200);
response.end(u.query.callback + '( {"rooms":[{"roomid":"0","roomname":"密码错误","needpass":"true"}]} );'); response.end(u.query.callback + '( {"rooms":[{"roomid":"0","roomname":"密码错误","needpass":"true"}]} );');
} else { } else {
response.writeHead(200); response.writeHead(200);
roomsjson = JSON.stringify({ roomsjson = JSON.stringify({
rooms: (function() { rooms: (function() {
var k, len, ref, results; var k, len, results;
ref = Room.all;
results = []; results = [];
for (k = 0, len = ref.length; k < len; k++) { for (k = 0, len = ROOM_all.length; k < len; k++) {
room = ref[k]; room = ROOM_all[k];
if (room.established) { if (room && room.established) {
results.push({ results.push({
pid: room.process.pid.toString(), pid: room.process.pid.toString(),
roomid: room.port.toString(), roomid: room.port.toString(),
roomname: pass_validated ? room.name : room.name.split('$', 2)[0], roomname: pass_validated ? room.name : room.name.split('$', 2)[0],
needpass: (room.name.indexOf('$') !== -1).toString(), needpass: (room.name.indexOf('$') !== -1).toString(),
users: (function() { users: (function() {
var l, len1, ref1, results1; var l, len1, ref, results1;
ref1 = room.players; ref = room.players;
results1 = []; results1 = [];
for (l = 0, len1 = ref1.length; l < len1; l++) { for (l = 0, len1 = ref.length; l < len1; l++) {
player = ref1[l]; player = ref[l];
if (player.pos != null) { if (player.pos != null) {
results1.push({ results1.push({
id: (-1).toString(), id: (-1).toString(),
...@@ -1092,35 +1985,57 @@ ...@@ -1092,35 +1985,57 @@
}); });
response.end(u.query.callback + "( " + roomsjson + " );"); response.end(u.query.callback + "( " + roomsjson + " );");
} }
} else if (u.pathname === '/api/duellog' && settings.modules.tournament_mode.enabled) {
if (!pass_validated) {
response.writeHead(200);
response.end("密码错误");
return;
} else {
response.writeHead(200);
duellog = JSON.stringify(settings.modules.tournament_mode.duel_log);
response.end(u.query.callback + "( " + duellog + " );");
}
} else if (u.pathname === '/api/message') { } else if (u.pathname === '/api/message') {
if (!pass_validated) { if (!pass_validated) {
response.writeHead(200); response.writeHead(200);
response.end(u.query.callback + "( '密码错误', 0 );"); response.end(u.query.callback + "( ['密码错误', 0] );");
return; return;
} }
if (u.query.shout) { if (u.query.shout) {
ref = Room.all; for (k = 0, len = ROOM_all.length; k < len; k++) {
for (k = 0, len = ref.length; k < len; k++) { room = ROOM_all[k];
room = ref[k]; if (room && room.established) {
ygopro.stoc_send_chat_to_room(room, u.query.shout, 16); ygopro.stoc_send_chat_to_room(room, u.query.shout, ygopro.constants.COLORS.YELLOW);
}
} }
response.writeHead(200); response.writeHead(200);
response.end(u.query.callback + "( 'shout ok', '" + u.query.shout + "' );"); response.end(u.query.callback + "( ['shout ok', '" + u.query.shout + "'] );");
} else if (u.query.stop) { } else if (u.query.stop) {
if (u.query.stop === 'false') { if (u.query.stop === 'false') {
u.query.stop = false; u.query.stop = false;
} }
settings.modules.stop = u.query.stop; settings.modules.stop = u.query.stop;
response.writeHead(200); response.writeHead(200);
response.end(u.query.callback + "( 'stop ok', '" + u.query.stop + "' );"); response.end(u.query.callback + "( ['stop ok', '" + u.query.stop + "'] );");
} else if (u.query.welcome) { } else if (u.query.welcome) {
settings.modules.welcome = u.query.welcome; nconf.myset(settings, 'modules:welcome', u.query.welcome);
response.writeHead(200);
response.end(u.query.callback + "( ['welcome ok', '" + u.query.welcome + "'] );");
} else if (u.query.getwelcome) {
response.writeHead(200);
response.end(u.query.callback + "( ['get ok', '" + settings.modules.welcome + "'] );");
} else if (u.query.loadtips) {
load_tips();
response.writeHead(200);
response.end(u.query.callback + "( ['loading tip', '" + settings.modules.tips + "'] );");
} else if (u.query.loaddialogues) {
load_dialogues();
response.writeHead(200); response.writeHead(200);
response.end(u.query.callback + "( 'welcome ok', '" + u.query.welcome + "' );"); response.end(u.query.callback + "( ['loading dialogues', '" + settings.modules.dialogues + "'] );");
} else if (u.query.ban) { } else if (u.query.ban) {
settings.BANNED_user.push(u.query.ban); ban_user(u.query.ban);
response.writeHead(200); response.writeHead(200);
response.end(u.query.callback + "( 'ban ok', '" + u.query.ban + "' );"); response.end(u.query.callback + "( ['ban ok', '" + u.query.ban + "'] );");
} else { } else {
response.writeHead(404); response.writeHead(404);
response.end(); response.end();
......
/*
ygopro-tournament.js
ygopro tournament util
Author: mercury233
License: MIT
不带参数运行时,会建立一个服务器,调用API执行对应操作
*/
var http = require('http');
var fs = require('fs');
var url = require('url');
var request = require('request');
var formidable = require('formidable');
var _ = require('underscore');
_.str = require('underscore.string');
_.mixin(_.str.exports());
var nconf = require('nconf');
nconf.file('./config.user.json');
var defaultconfig = require('./config.json');
nconf.defaults(defaultconfig);
var settings = nconf.get();
config=settings.modules.tournament_mode;
//http长连接
var responder;
config.wallpapers=[""];
request({
url: "http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=zh-CN",
json: true
}, function(error, response, body) {
if (_.isString(body)) {
console.log("wallpapers bad json", body);
}
else if (error || !body) {
console.log('wallpapers error', error, response);
}
else {
config.wallpapers=[];
for (var i in body.images) {
var wallpaper=body.images[i];
var img={
"url": "http://s.cn.bing.net"+wallpaper.urlbase+"_768x1366.jpg",
"desc": wallpaper.copyright
}
config.wallpapers.push(img);
}
}
});
//输出反馈信息,如有http长连接则输出到http,否则输出到控制台
var sendResponse = function(text) {
text=""+text;
if (responder) {
text=text.replace(/\n/g,"<br>");
responder.write("data: " + text + "\n\n");
}
else {
console.log(text);
}
}
//读取指定卡组
var readDeck = function(deck_name, deck_full_path) {
var deck={};
deck.name=deck_name;
deck_text = fs.readFileSync(deck_full_path, { encoding: "ASCII" });
deck_array = deck_text.split("\n");
deck.main = [];
deck.extra = [];
deck.side = [];
current_deck = deck.main;
for (l in deck_array) {
line = deck_array[l];
if (line.indexOf("#extra") >= 0) {
current_deck = deck.extra;
}
if (line.indexOf("!side") >= 0) {
current_deck = deck.side;
}
card = parseInt(line);
if (!isNaN(card)) {
current_deck.push(card);
}
}
return deck;
}
//读取指定文件夹中所有卡组
var getDecks = function() {
var decks=[];
var decks_list = fs.readdirSync(config.deck_path);
for (var k in decks_list) {
var deck_name = decks_list[k];
if (_.endsWith(deck_name, ".ydk")) {
var deck = readDeck(deck_name, config.deck_path+deck_name);
decks.push(deck);
}
}
return decks;
}
var delDeck = function(deck_name) {
var result=0;
try {
fs.unlinkSync(config.deck_path+deck_name);
result="已删除"+deck_name+"";
}
catch(e) {
result=e.toString();
}
finally {
return result;
}
}
var clearDecks = function() {
var decks_list = fs.readdirSync(config.deck_path);
for (var k in decks_list) {
var deck_name = decks_list[k];
if (_.endsWith(deck_name, ".ydk")) {
delDeck(deck_name);
}
}
}
var receiveDecks = function(files) {
var result=[];
for (var i in files) {
var file=files[i];
if (_.endsWith(file.name, ".ydk")) {
var deck=readDeck(file.name, file.path);
if (deck.main.length>=40) {
fs.createReadStream(file.path).pipe(fs.createWriteStream(config.deck_path+file.name));
result.push({
file: file.name,
status: "OK"
});
}
else {
result.push({
file: file.name,
status: "卡组不合格"
});
}
}
else {
result.push({
file: file.name,
status: "不是卡组文件"
});
}
}
return result;
}
//建立一个http服务器,接收API操作
http.createServer(function (req, res) {
var u = url.parse(req.url, true);
if (u.query.password !== config.password) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
if (u.pathname === '/api/upload_decks' && req.method.toLowerCase() == 'post') {
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
res.writeHead(200, {
"Access-Control-Allow-origin": "*",
'content-type': 'text/plain'
});
var result=receiveDecks(files);
//console.log(files);
res.end(JSON.stringify(result));
});
}
else if (u.pathname === '/api/msg') {
res.writeHead(200, {
"Access-Control-Allow-origin": "*",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
});
res.on("close", function(){
responder = null;
});
responder = res;
sendResponse("已连接。");
}
else if (u.pathname === '/api/get_bg') {
res.writeHead(200);
res.end(u.query.callback+'('+JSON.stringify(config.wallpapers[Math.floor(Math.random() * config.wallpapers.length)])+');');
}
else if (u.pathname === '/api/get_decks') {
res.writeHead(200);
var decklist=getDecks();
res.end(u.query.callback+'('+JSON.stringify(decklist)+');');
}
else if (u.pathname === '/api/del_deck') {
res.writeHead(200);
var result=delDeck(u.query.msg);
res.end(u.query.callback+'("'+result+'");');
}
else if (u.pathname === '/api/clear_decks') {
res.writeHead(200);
clearDecks();
res.end(u.query.callback+'("已删除全部卡组。");');
}
else {
res.writeHead(400);
res.end("400");
}
}).listen(config.port);
_ = require 'underscore' _ = require 'underscore'
_.str = require 'underscore.string' _.str = require 'underscore.string'
_.mixin(_.str.exports()); _.mixin(_.str.exports())
Struct = require('struct').Struct Struct = require('./struct.js').Struct
#常量/类型声明 #常量/类型声明
structs_declaration = require './structs.json' #结构体声明 structs_declaration = require './structs.json' #结构体声明
...@@ -137,17 +137,26 @@ for name, declaration of structs_declaration ...@@ -137,17 +137,26 @@ for name, declaration of structs_declaration
console.log "err stoc_send_hint_card_to_room" console.log "err stoc_send_hint_card_to_room"
return return
for client in room.players for client in room.players
@stoc_send client, 'GAME_MSG',{ @stoc_send client, 'GAME_MSG', {
curmsg: 2, curmsg: 2,
type: 10, type: 10,
player: 0, player: 0,
data: card data: card
} if client } if client
for client in room.watchers for client in room.watchers
@stoc_send client, 'GAME_MSG',{ @stoc_send client, 'GAME_MSG', {
curmsg: 2, curmsg: 2,
type: 10, type: 10,
player: 0, player: 0,
data: card data: card
} if client } if client
return return
@stoc_die = (client, msg)->
@stoc_send_chat(client, msg, @constants.COLORS.RED)
@stoc_send client, 'ERROR_MSG', {
msg: 1
code: 2
} if client
client.destroy() if client
return
\ No newline at end of file
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
_.mixin(_.str.exports()); _.mixin(_.str.exports());
Struct = require('struct').Struct; Struct = require('./struct.js').Struct;
structs_declaration = require('./structs.json'); structs_declaration = require('./structs.json');
...@@ -245,4 +245,17 @@ ...@@ -245,4 +245,17 @@
} }
}; };
this.stoc_die = function(client, msg) {
this.stoc_send_chat(client, msg, this.constants.COLORS.RED);
if (client) {
this.stoc_send(client, 'ERROR_MSG', {
msg: 1,
code: 2
});
}
if (client) {
client.destroy();
}
};
}).call(this); }).call(this);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment