Commit 2cc864c5 authored by 神楽坂玲奈's avatar 神楽坂玲奈

nothing

parent 1d260837
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
*.rb text
*.yml text
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.html text
*.js text
*.coffee text
*.css text
*.less text
*.png binary
*.jpg binary
\ No newline at end of file
#游戏适配器的抽象类
require_relative 'game_event'
require_relative 'action'
require_relative 'user'
require_relative 'room'
require_relative 'server'
class Game
attr_reader :users, :rooms, :servers, :filter
attr_accessor :user, :room, :player_field, :opponent_field, :turn, :turn_player, :phase
def initialize
@users = []
@rooms = []
@servers = []
@filter = {servers: [], waiting_only: false, normal_only: false}
end
def login(username, password=nil)
end
def refresh
end
def host(room_name, room_config)
end
def join(room)
end
def watch(room)
end
def leave
end
def action(action)
end
def chat(chatmessage)
end
def exit
$scene = Scene_Login.new if $scene
end
def watching?
@room and @room.include? @user
end
def self.deck_edit
require_relative 'window_deck'
@deck_window = Window_Deck.new
end
def refresh_interval
5
end
def show_chat_self
false
end
end
#游戏适配器的抽象类
require_relative 'game_event'
require_relative 'action'
require_relative 'user'
require_relative 'room'
require_relative 'server'
class Game
attr_reader :users, :rooms, :servers, :filter
attr_accessor :user, :room, :player_field, :opponent_field, :turn, :turn_player, :phase
def initialize
@users = []
@rooms = []
@servers = []
@filter = {servers: [], waiting_only: false, normal_only: false}
end
def login(username, password=nil)
end
def refresh
end
def host(room_name, room_config)
end
def join(room)
end
def watch(room)
end
def leave
end
def action(action)
end
def chat(chatmessage)
end
def exit
$scene = Scene_Login.new if $scene
end
def watching?
@room and @room.include? @user
end
def self.deck_edit
require_relative 'window_deck'
@deck_window = Window_Deck.new
end
def refresh_interval
5
end
def show_chat_self
false
end
end
#!/usr/bin/env ruby
begin
Windows = RUBY_PLATFORM["win"] || RUBY_PLATFORM["ming"]
Dir.glob('post_update_*.rb').sort.each { |file| load file }
Thread.abort_on_exception = true
require_relative 'resolution'
require_relative 'announcement'
require_relative 'config'
require_relative 'association'
#i18n
require 'i18n'
require 'locale'
I18n.load_path += Dir['locales/*.yml']
I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
#读取配置文件
$config = Config.load
Config.save
#读取命令行参数
log = "log.log"
log_level = "INFO"
profile = nil
ARGV.each do |arg|
arg = arg.dup.force_encoding("UTF-8")
arg.force_encoding("GBK") unless arg.valid_encoding?
case arg
when /--log=(.*)/
log.replace $1
when /--log-level=(.*)/
log_level.replace $1
when /--profile=(.*)/
profile = $1
when /^mycard:.*|\.ydk$|\.yrp$|\.deck$/
require_relative 'quickstart'
$scene = false
when /register_association/
Association.register
$scene = false
end
end
unless $scene == false
#加载文件
require 'logger'
require 'sdl'
include SDL
require_relative 'dialog'
require_relative 'graphics'
require_relative 'window'
require_relative 'widget_msgbox'
#日志
if log == "STDOUT" #调试用
log = STDOUT
end
$log = Logger.new(log, 1, 1024000)
$log.level = Logger.const_get log_level
#性能分析
if profile
if profile == "STDOUT"
profile = STDOUT
else
profile = open(profile, 'w')
end
require 'profiler'
RubyVM::InstructionSequence.compile_option = {
:trace_instruction => true,
:specialized_instruction => false
}
Profiler__::start_profile
end
SDL::Event::APPMOUSEFOCUS = 1
SDL::Event::APPINPUTFOCUS = 2
SDL::Event::APPACTIVE = 4
SDL.putenv ("SDL_VIDEO_CENTERED=1");
SDL.init(INIT_VIDEO)
WM::set_caption("MyCard", "MyCard")
WM::icon = Surface.load("graphics/system/icon.gif")
$screen = Screen.open($config['screen']['width'], $config['screen']['height'], 0, HWSURFACE | ($config['screen']['fullscreen'] ? FULLSCREEN : 0))
TTF.init
#声音
begin
SDL.init(INIT_AUDIO)
Mixer.open(Mixer::DEFAULT_FREQUENCY, Mixer::DEFAULT_FORMAT, Mixer::DEFAULT_CHANNELS, 1536)
Mixer.set_volume_music(60)
rescue
nil
end
#标题场景
require_relative 'scene_title'
$scene = Scene_Title.new
#自动更新, 加载放到SDL前面会崩, 原因不明
require_relative 'update'
Update.start
WM::set_caption("MyCard v#{Update::Version}", "MyCard")
#文件关联
Association.start
#初始化完毕
$log.info("main") { "初始化成功" }
end
rescue Exception => exception
open('error-程序出错请到论坛反馈.txt', 'w') { |f| f.write [exception.inspect, *exception.backtrace].join("\n") }
$scene = false
end
#主循环
begin
$scene.main while $scene
rescue Exception => exception
exception.backtrace.each { |backtrace| break if backtrace =~ /^(.*)\.rb:\d+:in `.*'"$/ } #由于脚本是从main.rb开始执行的,总会有个能匹配成功的文件
$log.fatal($1) { [exception.inspect, *exception.backtrace].collect { |str| str.force_encoding("UTF-8") }.join("\n") }
$game.exit if $game
require_relative 'scene_error'
$scene = Scene_Error.new
retry
ensure
if profile
Profiler__::print_profile(profile)
profile.close
end
$log.close rescue nil
#!/usr/bin/env ruby
begin
Windows = RUBY_PLATFORM["win"] || RUBY_PLATFORM["ming"]
Dir.glob('post_update_*.rb').sort.each { |file| load file }
Thread.abort_on_exception = true
require_relative 'resolution'
require_relative 'announcement'
require_relative 'config'
require_relative 'association'
#i18n
require 'i18n'
require 'locale'
I18n.load_path += Dir['locales/*.yml']
I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
#读取配置文件
$config = Config.load
Config.save
#读取命令行参数
log = "log.log"
log_level = "INFO"
profile = nil
ARGV.each do |arg|
arg = arg.dup.force_encoding("UTF-8")
arg.force_encoding("GBK") unless arg.valid_encoding?
case arg
when /--log=(.*)/
log.replace $1
when /--log-level=(.*)/
log_level.replace $1
when /--profile=(.*)/
profile = $1
when /^mycard:.*|\.ydk$|\.yrp$|\.deck$/
require_relative 'quickstart'
$scene = false
when /register_association/
Association.register
$scene = false
end
end
unless $scene == false
#加载文件
require 'logger'
require 'sdl'
include SDL
require_relative 'dialog'
require_relative 'graphics'
require_relative 'window'
require_relative 'widget_msgbox'
#日志
if log == "STDOUT" #调试用
log = STDOUT
end
$log = Logger.new(log, 1, 1024000)
$log.level = Logger.const_get log_level
#性能分析
if profile
if profile == "STDOUT"
profile = STDOUT
else
profile = open(profile, 'w')
end
require 'profiler'
RubyVM::InstructionSequence.compile_option = {
:trace_instruction => true,
:specialized_instruction => false
}
Profiler__::start_profile
end
SDL::Event::APPMOUSEFOCUS = 1
SDL::Event::APPINPUTFOCUS = 2
SDL::Event::APPACTIVE = 4
SDL.putenv ("SDL_VIDEO_CENTERED=1");
SDL.init(INIT_VIDEO)
WM::set_caption("MyCard", "MyCard")
WM::icon = Surface.load("graphics/system/icon.gif")
$screen = Screen.open($config['screen']['width'], $config['screen']['height'], 0, HWSURFACE | ($config['screen']['fullscreen'] ? FULLSCREEN : 0))
TTF.init
#声音
begin
SDL.init(INIT_AUDIO)
Mixer.open(Mixer::DEFAULT_FREQUENCY, Mixer::DEFAULT_FORMAT, Mixer::DEFAULT_CHANNELS, 1536)
Mixer.set_volume_music(60)
rescue
nil
end
#标题场景
require_relative 'scene_title'
$scene = Scene_Title.new
#自动更新, 加载放到SDL前面会崩, 原因不明
require_relative 'update'
Update.start
WM::set_caption("MyCard v#{Update::Version}", "MyCard")
#文件关联
Association.start
#初始化完毕
$log.info("main") { "初始化成功" }
end
rescue Exception => exception
open('error-程序出错请到论坛反馈.txt', 'w') { |f| f.write [exception.inspect, *exception.backtrace].join("\n") }
$scene = false
end
#主循环
begin
$scene.main while $scene
rescue Exception => exception
exception.backtrace.each { |backtrace| break if backtrace =~ /^(.*)\.rb:\d+:in `.*'"$/ } #由于脚本是从main.rb开始执行的,总会有个能匹配成功的文件
$log.fatal($1) { [exception.inspect, *exception.backtrace].collect { |str| str.force_encoding("UTF-8") }.join("\n") }
$game.exit if $game
require_relative 'scene_error'
$scene = Scene_Error.new
retry
ensure
if profile
Profiler__::print_profile(profile)
profile.close
end
$log.close rescue nil
end
\ No newline at end of file
require 'open-uri'
require "fileutils"
require_relative 'card'
module Update
Version = '0.8.5'
URL = "http://my-card.in/mycard/update.json?version=#{Version}"
class <<self
attr_reader :thumbnails, :images, :status
def start
Dir.glob("mycard-update-*-*.zip") do |file|
file =~ /mycard-update-(.+?)-(.+?)\.zip/
if $1 <= Version and $2 > Version
$log.info('安装更新'){file}
WM::set_caption("MyCard - 正在更新 #{Version} -> #{$2}", "MyCard")
require 'zip/zip'
Zip::ZipFile::open(file) do |zip|
zip.each do |f|
if !File.directory?(f.name)
FileUtils.mkdir_p(File.dirname(f.name))
end
f.extract{true}
end
end rescue $log.error('安装更新出错'){file+$!.inspect+$!.backtrace.inspect}
Version.replace $2
File.delete file
@updated = true
end
end
if @updated
IO.popen('./mycard')
$scene = nil
end
@images = []
@thumbnails = []
@status = '正在检查更新'
@updated = false
Thread.new do
open(URL) do |file|
require 'json'
reply = file.read
$log.info('下载更新-服务器回传'){reply}
reply = JSON.parse(reply)
$log.info('下载更新-解析后'){reply.inspect}
reply.each do |fil|
name = File.basename fil
@status.replace "正在下载更新#{name}"
open(fil, 'rb') do |fi|
$log.info('下载完毕'){name}
@updated = true
open(name, 'wb') do |f|
f.write fi.read
end
end rescue $log.error('下载更新'){'下载更新失败'}
end
end rescue $log.error('检查更新'){'检查更新失败'}
if @updated
require_relative 'widget_msgbox'
Widget_Msgbox.new('mycard', '下载更新完毕,点击确定重新运行mycard并安装更新', :ok => "确定"){IO.popen('./mycard'); $scene = nil}
end
if File.file? "ygocore/cards.cdb"
require 'sqlite3'
db = SQLite3::Database.new( "ygocore/cards.cdb" )
db.execute( "select id from datas" ) do |row|
@thumbnails << row[0]
end
@images.replace @thumbnails
if !File.directory?('ygocore/pics/thumbnail')
FileUtils.mkdir_p('ygocore/pics/thumbnail')
end
existed_thumbnails = []
Dir.foreach("ygocore/pics/thumbnail") do |file|
if file =~ /(\d+)\.jpg/
existed_thumbnails << $1.to_i
end
end
@thumbnails -= existed_thumbnails
existed_images = []
Dir.foreach("ygocore/pics") do |file|
if file =~ /(\d+)\.jpg/
existed_images << $1.to_i
end
end
@images -= existed_images
existed_images = []
if (!@images.empty? or !@thumbnails.empty?) and File.file?("#{Card::PicPath}/1.jpg")
db_mycard = SQLite3::Database.new( "data/data.sqlite" )
db_mycard.execute( "select id, number from `yu-gi-oh` where number in (#{(@images+@thumbnails).uniq.collect{|number|"'%08d'" % number}.join(',')})" ) do |row|
id = row[0]
number = row[1].to_i
src = "#{Card::PicPath}/#{id}.jpg"
dest = "ygocore/pics/#{number}.jpg"
dest_thumb = "ygocore/pics/thumbnail/#{number}.jpg"
if File.file?(src)
@status.replace "检测到存在iDuel卡图 正在导入 #{id}.jpg"
existed_images << number
if !File.exist?(dest)
FileUtils.copy_file(src, dest)
FileUtils.copy_file(src, dest_thumb)
end
end
end
end
@images -= existed_images
@thumbnails -= existed_images
@thumbnails = (@thumbnails & @images) + (@thumbnails - @images)
unless @thumbnails.empty? and @images.empty?
$log.info('待下载的完整卡图'){@images.inspect}
$log.info('待下载的缩略卡图'){@thumbnails.inspect}
threads = 5.times.collect do
thread = Thread.new do
while number = @thumbnails.pop
@status.replace "正在下载缩略卡图 (剩余#{@thumbnails.size}张)"
open("http://my-card.in/images/cards/ygocore/thumbnail/#{number}.jpg", 'rb') do |remote|
next if File.file? "ygocore/pics/thumbnail/#{number}.jpg"
#$log.debug('下载缩略卡图'){"http://my-card.in/images/cards/ygocore/thumbnail/#{number}.jpg 到 ygocore/pics/thumbnail/#{number}.jpg" }
open("ygocore/pics/thumbnail/#{number}.jpg", 'wb') do |local|
local.write remote.read
end
end rescue $log.error('下载缩略出错'){"http://my-card.in/images/cards/ygocore/thumbnail/#{number}.jpg 到 ygocore/pics/thumbnail/#{number}.jpg" }
end
while number = @images.pop
@status.replace "正在下载完整卡图 (剩余#{@images.size}张)"
#$log.debug('下载完整卡图'){"http://my-card.in/images/cards/ygocore/#{number}.jpg 到 ygocore/pics/#{number}.jpg" }
open("http://my-card.in/images/cards/ygocore/#{number}.jpg", 'rb') do |remote|
next if File.file? "ygocore/pics/#{number}.jpg"
open("ygocore/pics/#{number}.jpg", 'wb') do |local|
local.write remote.read
end
end rescue $log.error('下载完整卡图出错'){"http://my-card.in/images/cards/ygocore/#{number}.jpg 到 ygocore/pics/#{number}.jpg" }
end
end
thread.priority = -1
thread
end
threads.each{|thread|thread.join}
end
end rescue $log.error('卡图更新'){'找不到ygocore卡片数据库'}
@status = nil
end.priority = -1
end
end
end
require 'open-uri'
require "fileutils"
require_relative 'card'
module Update
Version = '0.8.6'
URL = "http://my-card.in/mycard/update.json?version=#{Version}"
class <<self
attr_reader :thumbnails, :images, :status
def start
Dir.glob("mycard-update-*-*.zip") do |file|
file =~ /mycard-update-(.+?)-(.+?)\.zip/
if $1 <= Version and $2 > Version
$log.info('安装更新'){file}
WM::set_caption("MyCard - 正在更新 #{Version} -> #{$2}", "MyCard")
require 'zip/zip'
Zip::ZipFile::open(file) do |zip|
zip.each do |f|
if !File.directory?(f.name)
FileUtils.mkdir_p(File.dirname(f.name))
end
f.extract{true}
end
end rescue $log.error('安装更新出错'){file+$!.inspect+$!.backtrace.inspect}
Version.replace $2
File.delete file
@updated = true
end
end
if @updated
IO.popen('./mycard')
$scene = nil
end
@images = []
@thumbnails = []
@status = '正在检查更新'
@updated = false
Thread.new do
open(URL) do |file|
require 'json'
reply = file.read
$log.info('下载更新-服务器回传'){reply}
reply = JSON.parse(reply)
$log.info('下载更新-解析后'){reply.inspect}
reply.each do |fil|
name = File.basename fil
@status.replace "正在下载更新#{name}"
open(fil, 'rb') do |fi|
$log.info('下载完毕'){name}
@updated = true
open(name, 'wb') do |f|
f.write fi.read
end
end rescue $log.error('下载更新'){'下载更新失败'}
end
end rescue $log.error('检查更新'){'检查更新失败'}
if @updated
require_relative 'widget_msgbox'
Widget_Msgbox.new('mycard', '下载更新完毕,点击确定重新运行mycard并安装更新', :ok => "确定"){IO.popen('./mycard'); $scene = nil}
end
if File.file? "ygocore/cards.cdb"
require 'sqlite3'
db = SQLite3::Database.new( "ygocore/cards.cdb" )
db.execute( "select id from datas" ) do |row|
@thumbnails << row[0]
end
@images.replace @thumbnails
if !File.directory?('ygocore/pics/thumbnail')
FileUtils.mkdir_p('ygocore/pics/thumbnail')
end
existed_thumbnails = []
Dir.foreach("ygocore/pics/thumbnail") do |file|
if file =~ /(\d+)\.jpg/
existed_thumbnails << $1.to_i
end
end
@thumbnails -= existed_thumbnails
existed_images = []
Dir.foreach("ygocore/pics") do |file|
if file =~ /(\d+)\.jpg/
existed_images << $1.to_i
end
end
@images -= existed_images
existed_images = []
if (!@images.empty? or !@thumbnails.empty?) and File.file?("#{Card::PicPath}/1.jpg")
db_mycard = SQLite3::Database.new( "data/data.sqlite" )
db_mycard.execute( "select id, number from `yu-gi-oh` where number in (#{(@images+@thumbnails).uniq.collect{|number|"'%08d'" % number}.join(',')})" ) do |row|
id = row[0]
number = row[1].to_i
src = "#{Card::PicPath}/#{id}.jpg"
dest = "ygocore/pics/#{number}.jpg"
dest_thumb = "ygocore/pics/thumbnail/#{number}.jpg"
if File.file?(src)
@status.replace "检测到存在iDuel卡图 正在导入 #{id}.jpg"
existed_images << number
if !File.exist?(dest)
FileUtils.copy_file(src, dest)
FileUtils.copy_file(src, dest_thumb)
end
end
end
end
@images -= existed_images
@thumbnails -= existed_images
@thumbnails = (@thumbnails & @images) + (@thumbnails - @images)
unless @thumbnails.empty? and @images.empty?
$log.info('待下载的完整卡图'){@images.inspect}
$log.info('待下载的缩略卡图'){@thumbnails.inspect}
threads = 5.times.collect do
thread = Thread.new do
while number = @thumbnails.pop
@status.replace "正在下载缩略卡图 (剩余#{@thumbnails.size}张)"
open("http://my-card.in/images/cards/ygocore/thumbnail/#{number}.jpg", 'rb') do |remote|
next if File.file? "ygocore/pics/thumbnail/#{number}.jpg"
#$log.debug('下载缩略卡图'){"http://my-card.in/images/cards/ygocore/thumbnail/#{number}.jpg 到 ygocore/pics/thumbnail/#{number}.jpg" }
open("ygocore/pics/thumbnail/#{number}.jpg", 'wb') do |local|
local.write remote.read
end
end rescue $log.error('下载缩略出错'){"http://my-card.in/images/cards/ygocore/thumbnail/#{number}.jpg 到 ygocore/pics/thumbnail/#{number}.jpg" }
end
while number = @images.pop
@status.replace "正在下载完整卡图 (剩余#{@images.size}张)"
#$log.debug('下载完整卡图'){"http://my-card.in/images/cards/ygocore/#{number}.jpg 到 ygocore/pics/#{number}.jpg" }
open("http://my-card.in/images/cards/ygocore/#{number}.jpg", 'rb') do |remote|
next if File.file? "ygocore/pics/#{number}.jpg"
open("ygocore/pics/#{number}.jpg", 'wb') do |local|
local.write remote.read
end
end rescue $log.error('下载完整卡图出错'){"http://my-card.in/images/cards/ygocore/#{number}.jpg 到 ygocore/pics/#{number}.jpg" }
end
end
thread.priority = -1
thread
end
threads.each{|thread|thread.join}
end
end rescue $log.error('卡图更新'){'找不到ygocore卡片数据库'}
@status = nil
end.priority = -1
end
end
end
#==============================================================================
# ■ Scene_Title
#------------------------------------------------------------------------------
#  title
#==============================================================================
require_relative 'widget_scrollbar'
require_relative 'widget_inputbox'
require_relative 'chatmessage'
require_relative 'window_scrollable'
class Window_Chat < Window_Scrollable
WLH=16
def initialize(x, y, width, height)
super(x,y,width,height)
if @width > 600 #判断大厅还是房间,这个判据比较囧,待优化
@chat_background = Surface.load("graphics/system/chat.png").display_format
else
@chat_background = Surface.load("graphics/system/chat_room.png").display_format
end
@background = @contents.copy_rect(0,0,@contents.w,@contents.h) #new而已。。
@background.fill_rect(0,0,@background.w, @background.h, 0xFFb2cefe)
@background.put(@chat_background,0,31-4)
@tab = Surface.load "graphics/system/tab.png"
@chat_input = Widget_InputBox.new(@x+8, @y+@height-24-10, @width-14, 24) do |key|
case key
when :ENTER
if !@chat_input.value.empty?
chatmessage = ChatMessage.new($game.user, @chat_input.value, @channel)
$game.chat chatmessage
Game_Event.push Game_Event::Chat.new(chatmessage) if !$game.is_a? Ygocore
true
end
end
end
@chat_input.refresh
@font = TTF.open("fonts/wqy-microhei.ttc", 14)
@scrollbar = Widget_ScrollBar.new(self,@x+@width-20-8,@y+31+3,@height-68)
@page_size = (@height-68)/WLH
@@list ||= {}
@list_splited = {}
@@list.each_pair do |channel, chatmessages|
chatmessages.each do |chatmessage|
add_split(chatmessage)
end
end
@channels = []
self.channel = :lobby
end
def add(chatmessage)
@@list[chatmessage.channel] ||= []
unless @channels.include? chatmessage.channel
@channels << chatmessage.channel
refresh
end
@@list[chatmessage.channel] << chatmessage
scroll_bottom = @items.size - self.scroll <= @page_size
add_split(chatmessage)
if chatmessage.channel == @channel
@scroll = [@items.size - @page_size, 0].max if scroll_bottom
refresh
end
end
def add_split(chatmessage)
@list_splited[chatmessage.channel] ||= []
@list_splited[chatmessage.channel] << [chatmessage, ""]
width = name_width(chatmessage)
line = 0
chatmessage.message.each_char do |char|
if char == "\n"
line += 1
width = 0
@list_splited[chatmessage.channel] << [chatmessage.message_color, ""]
else
char_width = @font.text_size(char)[0]
if char_width + width > @width-14-20
line += 1
width = char_width
@list_splited[chatmessage.channel] << [chatmessage.message_color, char]
else
@list_splited[chatmessage.channel].last[1] << char
width += char_width
end
end
end
end
def mousemoved(x,y)
if y-@y < 31 and (x-@x) < @channels.size * 100
self.index = @channels[(x-@x) / 100]
else
self.index = nil
end
end
def clicked
case @index
when nil
when Integer
else
self.channel = @index
end
end
def channel=(channel)
return if @channel == channel
@channel = channel
@channels << channel unless @channels.include? channel
@list_splited[channel] ||= []
@items = @list_splited[channel]
@scroll = [@items.size - @page_size, 0].max
refresh
end
def draw_item(index, status=0)
case index
when nil
when Integer #描绘聊天消息
draw_item_chatmessage(index, status)
else #描绘频道标签
draw_item_channel(index, status)
end
end
def draw_item_channel(channel, status)
index = @channels.index(channel)
Surface.blit(@tab,0,@channel == channel ? 0 : 31,100,31,@contents,index*100+3,0)
channel_name = ChatMessage.channel_name channel
x = index*100+(100 - @font.text_size(channel_name)[0])/2
draw_stroked_text(channel_name,x,8,1,@font, [255,255,255], ChatMessage.channel_color(channel))
end
def draw_item_chatmessage(index, status)
x,y = item_rect_chatmessage(index)
chatmessage, message = @items[index]
if chatmessage.is_a? ChatMessage
@font.draw_blended_utf8(@contents, chatmessage.user.name+':', x, y, *chatmessage.name_color) if chatmessage.name_visible?
@font.draw_blended_utf8(@contents, message, x+name_width(chatmessage), y, *chatmessage.message_color) unless chatmessage.message.empty?
else
@font.draw_blended_utf8(@contents, message, x, y, *chatmessage) unless message.empty?
end
end
def item_rect(index)
case index
when nil
when Integer #描绘聊天消息
item_rect_chatmessage(index)
else #描绘频道标签
item_rect_channel(index)
end
end
def item_rect_channel(channel)
[@channels.index(channel)*100+3, 0, 100, 31]
end
def item_rect_chatmessage(index)
[8, (index-@scroll)*WLH+31+3, @width, self.class::WLH]
end
def refresh
super
@channels.each {|channel|draw_item_channel(channel, @index==channel)}
end
def name_width(chatmessage)
chatmessage.name_visible? ? @font.text_size(chatmessage.user.name+':')[0] : 0
end
def index_legal?(index)
case index
when nil,Integer
super
else
@channels.include? index
end
end
def scroll_up
self.scroll -= 1
end
def scroll_down
self.scroll += 1
end
def update
@chat_input.update
end
#==============================================================================
# ■ Scene_Title
#------------------------------------------------------------------------------
#  title
#==============================================================================
require_relative 'widget_scrollbar'
require_relative 'widget_inputbox'
require_relative 'chatmessage'
require_relative 'window_scrollable'
class Window_Chat < Window_Scrollable
WLH=16
def initialize(x, y, width, height)
super(x,y,width,height)
if @width > 600 #判断大厅还是房间,这个判据比较囧,待优化
@chat_background = Surface.load("graphics/system/chat.png").display_format
else
@chat_background = Surface.load("graphics/system/chat_room.png").display_format
end
@background = @contents.copy_rect(0,0,@contents.w,@contents.h) #new而已。。
@background.fill_rect(0,0,@background.w, @background.h, 0xFFb2cefe)
@background.put(@chat_background,0,31-4)
@tab = Surface.load "graphics/system/tab.png"
@chat_input = Widget_InputBox.new(@x+8, @y+@height-24-10, @width-14, 24) do |key|
case key
when :ENTER
if !@chat_input.value.empty?
chatmessage = ChatMessage.new($game.user, @chat_input.value, @channel)
$game.chat chatmessage
Game_Event.push Game_Event::Chat.new(chatmessage) if !$game.is_a? Ygocore
true
end
end
end
@chat_input.refresh
@font = TTF.open("fonts/wqy-microhei.ttc", 14)
@scrollbar = Widget_ScrollBar.new(self,@x+@width-20-8,@y+31+3,@height-68)
@page_size = (@height-68)/WLH
@@list ||= {}
@list_splited = {}
@@list.each_pair do |channel, chatmessages|
chatmessages.each do |chatmessage|
add_split(chatmessage)
end
end
@channels = []
self.channel = :lobby
end
def add(chatmessage)
@@list[chatmessage.channel] ||= []
unless @channels.include? chatmessage.channel
@channels << chatmessage.channel
refresh
end
@@list[chatmessage.channel] << chatmessage
scroll_bottom = @items.size - self.scroll <= @page_size
add_split(chatmessage)
if chatmessage.channel == @channel
@scroll = [@items.size - @page_size, 0].max if scroll_bottom
refresh
end
end
def add_split(chatmessage)
@list_splited[chatmessage.channel] ||= []
@list_splited[chatmessage.channel] << [chatmessage, ""]
width = name_width(chatmessage)
line = 0
chatmessage.message.each_char do |char|
if char == "\n"
line += 1
width = 0
@list_splited[chatmessage.channel] << [chatmessage.message_color, ""]
else
char_width = @font.text_size(char)[0]
if char_width + width > @width-14-20
line += 1
width = char_width
@list_splited[chatmessage.channel] << [chatmessage.message_color, char]
else
@list_splited[chatmessage.channel].last[1] << char
width += char_width
end
end
end
end
def mousemoved(x,y)
if y-@y < 31 and (x-@x) < @channels.size * 100
self.index = @channels[(x-@x) / 100]
else
self.index = nil
end
end
def clicked
case @index
when nil
when Integer
else
self.channel = @index
end
end
def channel=(channel)
return if @channel == channel
@channel = channel
@channels << channel unless @channels.include? channel
@list_splited[channel] ||= []
@items = @list_splited[channel]
@scroll = [@items.size - @page_size, 0].max
refresh
end
def draw_item(index, status=0)
case index
when nil
when Integer #描绘聊天消息
draw_item_chatmessage(index, status)
else #描绘频道标签
draw_item_channel(index, status)
end
end
def draw_item_channel(channel, status)
index = @channels.index(channel)
Surface.blit(@tab,0,@channel == channel ? 0 : 31,100,31,@contents,index*100+3,0)
channel_name = ChatMessage.channel_name channel
x = index*100+(100 - @font.text_size(channel_name)[0])/2
draw_stroked_text(channel_name,x,8,1,@font, [255,255,255], ChatMessage.channel_color(channel))
end
def draw_item_chatmessage(index, status)
x,y = item_rect_chatmessage(index)
chatmessage, message = @items[index]
if chatmessage.is_a? ChatMessage
@font.draw_blended_utf8(@contents, chatmessage.user.name+':', x, y, *chatmessage.name_color) if chatmessage.name_visible?
@font.draw_blended_utf8(@contents, message, x+name_width(chatmessage), y, *chatmessage.message_color) unless chatmessage.message.empty?
else
@font.draw_blended_utf8(@contents, message, x, y, *chatmessage) unless message.empty?
end
end
def item_rect(index)
case index
when nil
when Integer #描绘聊天消息
item_rect_chatmessage(index)
else #描绘频道标签
item_rect_channel(index)
end
end
def item_rect_channel(channel)
[@channels.index(channel)*100+3, 0, 100, 31]
end
def item_rect_chatmessage(index)
[8, (index-@scroll)*WLH+31+3, @width, self.class::WLH]
end
def refresh
super
@channels.each {|channel|draw_item_channel(channel, @index==channel)}
end
def name_width(chatmessage)
chatmessage.name_visible? ? @font.text_size(chatmessage.user.name+':')[0] : 0
end
def index_legal?(index)
case index
when nil,Integer
super
else
@channels.include? index
end
end
def scroll_up
self.scroll -= 1
end
def scroll_down
self.scroll += 1
end
def update
@chat_input.update
end
end
\ No newline at end of file
require_relative 'window_host'
class Window_LobbyButtons < Window_List
def initialize(x, y)
@items = [I18n.t('lobby.faq'), I18n.t('lobby.filter'), I18n.t('lobby.editdeck'), I18n.t('lobby.newroom')]
@button = Surface.load("graphics/lobby/button.png")
super(x, y, @items.size*@button.w/3+@items.size*4, 30)
@font = TTF.open("fonts/wqy-microhei.ttc", 15)
refresh
end
def draw_item(index, status=0)
x, y, width=item_rect(index)
Surface.blit(@button, status*@button.w/3, 0, @button.w/3, @button.h, @contents, x, y)
draw_stroked_text(@items[index], x+center_margin(@items[index],width,@font), y+3, 2, @font, [0xdf, 0xf1, 0xff], [0x27, 0x43, 0x59])
end
def item_rect(index)
[index*@button.w/3+(index)*4, 0, @button.w/3, @height]
end
def mousemoved(x, y)
if (x-@x) % (@button.w/3+4) >= @button.w/3
self.index = nil
else
self.index = (x-@x)/(@button.w/3+4)
end
end
def lostfocus(active_window = nil)
self.index = nil
end
def clicked
case @index
when 0 #常见问题
require_relative 'dialog'
Dialog.web "http://my-card.in/login?user[name]=#{CGI.escape $game.user.name}&user[password]=#{CGI.escape $game.password}&continue=/topics/1453"
when 1 #房间筛选
if @filter_window and !@filter_window.destroyed?
@filter_window.destroy
else
@filter_window = Window_Filter.new(678, 44)
end
when 2 #卡组编辑
require_relative 'deck'
$game.class.deck_edit
when 3 #建立房间
@host_window = Window_Host.new(300, 200)
end
end
def update
@host_window.update if @host_window and !@host_window.destroyed?
end
end
require_relative 'window_host'
class Window_LobbyButtons < Window_List
def initialize(x, y)
@items = [I18n.t('lobby.faq'), I18n.t('lobby.filter'), I18n.t('lobby.editdeck'), I18n.t('lobby.newroom')]
@button = Surface.load("graphics/lobby/button.png")
super(x, y, @items.size*@button.w/3+@items.size*4, 30)
@font = TTF.open("fonts/wqy-microhei.ttc", 15)
refresh
end
def draw_item(index, status=0)
x, y, width=item_rect(index)
Surface.blit(@button, status*@button.w/3, 0, @button.w/3, @button.h, @contents, x, y)
draw_stroked_text(@items[index], x+center_margin(@items[index],width,@font), y+3, 2, @font, [0xdf, 0xf1, 0xff], [0x27, 0x43, 0x59])
end
def item_rect(index)
[index*@button.w/3+(index)*4, 0, @button.w/3, @height]
end
def mousemoved(x, y)
if (x-@x) % (@button.w/3+4) >= @button.w/3
self.index = nil
else
self.index = (x-@x)/(@button.w/3+4)
end
end
def lostfocus(active_window = nil)
self.index = nil
end
def clicked
case @index
when 0 #常见问题
require_relative 'dialog'
Dialog.web "http://my-card.in/login?user[name]=#{CGI.escape $game.user.name}&user[password]=#{CGI.escape $game.password}&continue=/topics/1453"
when 1 #房间筛选
if @filter_window and !@filter_window.destroyed?
@filter_window.destroy
else
@filter_window = Window_Filter.new(678, 44)
end
when 2 #卡组编辑
require_relative 'deck'
$game.class.deck_edit
when 3 #建立房间
@host_window = Window_Host.new(300, 200)
end
end
def update
@host_window.update if @host_window and !@host_window.destroyed?
end
end
#encoding: UTF-8
#==============================================================================
# Window_UserInfo
#------------------------------------------------------------------------------
# 游戏大厅显示用户信息的类
#==============================================================================
class Window_UserInfo < Window
def initialize(x, y, user)
@avatar_boarder = Surface.load("graphics/lobby/avatar_boader.png")
super(x,y,280,144)
@font = TTF.open('fonts/wqy-microhei.ttc', 16)
@user = user
@background = Surface.load("graphics/lobby/userinfo.png").display_format
refresh
end
def refresh
@contents.put(@background, 0, 0)
@thread = @user.avatar(:middle) do |avatar|
clear(0,0,@avatar_boarder.w, @avatar_boarder.h)
@contents.put(avatar, 12, 12)
@contents.put(@avatar_boarder, 0, 0)
end
@font.draw_blended_utf8(@contents, @user.name, 160, 12, 0x00,0x00,0x00) unless @user.name.empty?
@font.draw_blended_utf8(@contents, @user.id.to_s , 160, 12+16*2, 0x00,0x00,0x00) unless @user.id.to_s.empty?
@font.draw_blended_utf8(@contents, "Lv: #{@user.level}" , 160, 12+16*3, 0x00,0x00,0x00) if @user.respond_to? :level and @user.level #TODO:规范化,level是iduel专属的,但是又不太想让iduel来重定义这个window
@font.draw_blended_utf8(@contents, "经验: #{@user.exp}", 160, 12+16*4, 0x00,0x00,0x00) if @user.respond_to? :exp and @user.exp
end
def dispose
@thread.exit
super
end
end
#encoding: UTF-8
#==============================================================================
# Window_UserInfo
#------------------------------------------------------------------------------
# 游戏大厅显示用户信息的类
#==============================================================================
class Window_UserInfo < Window
def initialize(x, y, user)
@avatar_boarder = Surface.load("graphics/lobby/avatar_boader.png")
super(x,y,280,144)
@font = TTF.open('fonts/wqy-microhei.ttc', 16)
@user = user
@background = Surface.load("graphics/lobby/userinfo.png").display_format
refresh
end
def refresh
@contents.put(@background, 0, 0)
@thread = @user.avatar(:middle) do |avatar|
clear(0,0,@avatar_boarder.w, @avatar_boarder.h)
@contents.put(avatar, 12, 12)
@contents.put(@avatar_boarder, 0, 0)
end
@font.draw_blended_utf8(@contents, @user.name, 160, 12, 0x00,0x00,0x00) unless @user.name.empty?
@font.draw_blended_utf8(@contents, @user.id.to_s , 160, 12+16*2, 0x00,0x00,0x00) unless @user.id.to_s.empty?
@font.draw_blended_utf8(@contents, "Lv: #{@user.level}" , 160, 12+16*3, 0x00,0x00,0x00) if @user.respond_to? :level and @user.level #TODO:规范化,level是iduel专属的,但是又不太想让iduel来重定义这个window
@font.draw_blended_utf8(@contents, "经验: #{@user.exp}", 160, 12+16*4, 0x00,0x00,0x00) if @user.respond_to? :exp and @user.exp
end
def dispose
@thread.exit
super
end
end
#encoding: UTF-8
load 'lib/ygocore/window_login.rb'
require 'eventmachine'
require 'em-http'
require 'websocket'
require 'open-uri'
require 'yaml'
class Ygocore < Game
attr_reader :username
attr_accessor :password
@@config = YAML.load_file("lib/ygocore/server.yml")
def initialize
super
load 'lib/ygocore/event.rb'
load 'lib/ygocore/user.rb'
load 'lib/ygocore/room.rb'
load 'lib/ygocore/scene_lobby.rb'
require 'json'
require 'xmpp4r/client'
require 'xmpp4r/muc'
end
def refresh_interval
60
end
def login(username, password)
@username = username
@password = password
@nickname_conflict = []
@@im = Jabber::Client.new(Jabber::JID::new(@username, 'my-card.in', 'mycard'))
@@im_room = Jabber::MUC::MUCClient.new(@@im)
Jabber.logger = $log
Jabber.debug = true
@@im.on_exception do |exception, c, where|
$log.error('聊天出错') { [exception, c, where] }
if where == :close
Game_Event.push(Game_Event::Chat.new(ChatMessage.new(User.new(:system, 'System'), '聊天服务连接中断')))
else
Game_Event.push(Game_Event::Chat.new(ChatMessage.new(User.new(:system, 'System'), '聊天服务连接中断.1')))
#sleep 5
#im_connect
end
end
@@im_room.add_message_callback do |m|
user = m.from.resource == nickname ? @user : User.new(m.from.resource.to_sym, m.from.resource)
Game_Event.push Game_Event::Chat.new ChatMessage.new(user, m.body, :lobby) rescue $log.error('收到聊天消息') { $! }
end
@@im_room.add_private_message_callback do |m|
if m.body #忽略无消息的正在输入等内容
user = m.from.resource == nickname ? @user : User.new(m.from.resource.to_sym, m.from.resource)
Game_Event.push Game_Event::Chat.new ChatMessage.new(user, m.body, user) rescue $log.error('收到私聊消息') { $! }
end
end
@@im_room.add_join_callback do |m|
Game_Event.push Game_Event::NewUser.new User.new m.from.resource.to_sym, m.from.resource
end
@@im_room.add_leave_callback do |m|
Game_Event.push Game_Event::MissingUser.new User.new m.from.resource.to_sym, m.from.resource
end
connect
im_connect
end
def nickname
return @nickname if @nickname
if @nickname_conflict.include? @username
1.upto(9) do |i|
result = "#{@username}-#{i}"
return result unless @nickname_conflict.include? result
end
raise 'can`t get available nickname'
else
@username
end
end
def connect
@recv = Thread.new do
EventMachine::run {
http = EM::HttpRequest.new("http://my-card.in/servers.json").get
http.callback {
begin
self.servers.replace JSON.parse(http.response).collect {|data| Server.new(data['id'], data['name'], data['ip'], data['port'], data['auth'])}
self.filter[:servers] = self.servers.clone
rescue
Game_Event.push Game_Event::Error.new('ygocore', '读取服务器列表失败', true)
end
#EventMachine::connect "mycard-server.my-card.in", 9997, Client
ws = WebSocket::EventMachine::Client.connect(:host => "mycard-server.my-card.in", :port => 9998);
ws.onmessage do |msg, type|
$log.info('收到websocket消息'){msg.force_encoding("UTF-8")}
Game_Event.push Game_Event::RoomsUpdate.new JSON.parse(msg).collect { |room| Game_Event.parse_room(room) }
end
ws.onclose do
$log.info('websocket连接断开')
Game_Event.push Game_Event::Error.new('ygocore', '网络连接中断.1', true)
end
}
http.errback{
Game_Event.push Game_Event::Error.new('ygocore', '读取服务器列表失败', true)
}
}
end
end
def im_connect
Thread.new {
begin
@@im.allow_tls = false
@@im.use_ssl = true
@@im.connect('chat.my-card.in', 5223) #ruby19/windows下 使用tls连接时会卡住
begin
@@im.auth(@password)
rescue Jabber::ClientAuthenticationFailure
Game_Event.push Game_Event::Error.new('登录', '用户名或密码错误')
Thread.exit
end
Game_Event.push Game_Event::Login.new User.new(@@im.jid, @username, true)
@@im.send(Jabber::Presence.new.set_type(:available))
begin
nickname = nickname()
#@@im_room.join(Jabber::JID.new(I18n.t('lobby.room'), I18n.t('lobby.server'), nickname))
@@im_room.join(Jabber::JID.new('mycard', 'conference.my-card.in', nickname))
rescue Jabber::ServerError => exception
if exception.error.error == 'conflict'
@nickname_conflict << nickname
retry
end
end
Game_Event.push Game_Event::AllUsers.new @@im_room.roster.keys.collect { |nick| User.new(nick.to_sym, nick) } rescue p $!
rescue StandardError => exception
$log.error('聊天连接出错') { exception }
Game_Event.push(Game_Event::Chat.new(ChatMessage.new(User.new(:system, 'System'), '聊天服务器连接失败')))
end
}
end
def chat(chatmessage)
case chatmessage.channel
when :lobby
msg = Jabber::Message::new(nil, chatmessage.message)
@@im_room.send msg
when User
msg = Jabber::Message::new(nil, chatmessage.message)
@@im_room.send msg, chatmessage.channel.id
#send(:chat, channel: chatmessage.channel.id, message: chatmessage.message, time: chatmessage.time)
end
end
#def chat(chatmessage)
# case chatmessage.channel
# when :lobby
# send(:chat, channel: :lobby, message: chatmessage.message, time: chatmessage.time)
# when User
# send(:chat, channel: chatmessage.channel.id, message: chatmessage.message, time: chatmessage.time)
# end
#end
def host(room_name, room_config)
room = Room.new(0, room_name)
room.pvp = room_config[:pvp]
room.match = room_config[:match]
room.tag = room_config[:tag]
room.password = room_config[:password]
room.ot = room_config[:ot]
room.lp = room_config[:lp]
room.host_server
if $game.rooms.any? { |game_room| game_room.name == room_name }
Widget_Msgbox.new("建立房间", "房间名已存在", :ok => "确定")
else
Game_Event.push Game_Event::Join.new(room)
end
end
def watch(room)
Widget_Msgbox.new("加入房间", "游戏已经开始", :ok => "确定")
end
def join(room)
Game_Event.push Game_Event::Join.new(room)
end
def refresh
#send(:refresh)
end
def send(header, data=nil)
#$log.info('发送消息') { {header: header, data: data} }
#Client::MycardChannel.push header: header, data: data
end
def exit
@recv.exit if @recv
@recv = nil
end
def ygocore_path
"ygocore/ygopro_vs.exe"
end
def self.register
Dialog.web @@config['register']
end
def server
@@config['server']
end
def port
@@config['port']
end
def server=(server)
@@config['server'] = server
end
def port=(port)
@@config['port'] = port
end
def self.run_ygocore(option, image_downloading=false)
if !image_downloading and !Update.images.empty?
return Widget_Msgbox.new("加入房间", "卡图正在下载中,可能显示不出部分卡图", :ok => "确定") { run_ygocore(option, true) }
end
path = 'ygocore/ygopro_vs.exe'
Widget_Msgbox.new("ygocore", "正在启动ygocore") rescue nil
#写入配置文件并运行ygocore
Dir.chdir(File.dirname(path)) do
case option
when Room
room = option
room_name = if room.ot != 0 or room.lp != 8000
mode = case when room.match? then
1; when room.tag? then
2
else
0
end
room_name = "#{room.ot}#{mode}FFF#{room.lp},5,1,#{room.name}"
elsif room.tag?
"T#" + room.name
elsif room.pvp? and room.match?
"PM#" + room.name
elsif room.pvp?
"P#" + room.name
elsif room.match?
"M#" + room.name
else
room.name
end
if room.password and !room.password.empty?
room_name += "$" + room.password
end
system_conf = {}
begin
IO.readlines('system.conf').each do |line|
line.force_encoding "UTF-8"
next if line[0, 1] == '#'
field, contents = line.chomp.split(' = ', 2)
system_conf[field] = contents
end
rescue
system_conf['antialias'] = 2
system_conf['textfont'] = 'c:/windows/fonts/simsun.ttc 14'
system_conf['numfont'] = 'c:/windows/fonts/arialbd.ttf'
end
system_conf['nickname'] = $game.user.name
system_conf['nickname'] += '$' + $game.password if $game.password and !$game.password.empty? and room.server.auth
$log.info room
system_conf['lastip'] = room.server.ip
system_conf['lastport'] = room.server.port.to_s
system_conf['roompass'] = room_name
open('system.conf', 'w') { |file| file.write system_conf.collect { |key, value| "#{key} = #{value}" }.join("\n") }
args = '-j'
when :replay
args = '-r'
when :deck
args = '-d'
when String
system_conf = {}
begin
IO.readlines('system.conf').each do |line|
line.force_encoding "UTF-8"
next if line[0, 1] == '#'
field, contents = line.chomp.split(' = ', 2)
system_conf[field] = contents
end
rescue
system_conf['antialias'] = 2
system_conf['textfont'] = 'c:/windows/fonts/simsun.ttc 14'
system_conf['numfont'] = 'c:/windows/fonts/arialbd.ttf'
end
system_conf['lastdeck'] = option
open('system.conf', 'w') { |file| file.write system_conf.collect { |key, value| "#{key} = #{value}" }.join("\n") }
args = '-d'
end
IO.popen("ygopro_vs.exe #{args}")
WM.iconify rescue nil
end
Widget_Msgbox.destroy rescue nil
end
def self.replay(file, skip_image_downloading = false)
require 'fileutils'
FileUtils.mv Dir.glob('ygocore/replay/*.yrp'), 'replay/'
FileUtils.copy_file(file, "ygocore/replay/#{File.basename(file)}")
run_ygocore(:replay, skip_image_downloading)
end
private
def self.get_announcements
#公告
$config['ygocore'] ||= {}
$config['ygocore']['announcements'] ||= [Announcement.new("开放注册", nil, nil)]
#Thread.new do
# begin
# open(@@config['api']) do |file|
# file.set_encoding "GBK"
# announcements = []
# file.read.encode("UTF-8").scan(/<div style="color:red" >公告:(.*?)<\/div>/).each do |title, others|
# announcements << Announcement.new(title, @@config['index'], nil)
# end
# $config['ygocore']['announcements'].replace announcements
# Config.save
# end
# rescue Exception => exception
# $log.error('公告读取失败') { [exception.inspect, *exception.backtrace].collect { |str| str.encode("UTF-8") }.join("\n") }
# end
#end
end
#module Client
# MycardChannel = EM::Channel.new
# include EM::P::ObjectProtocol
#
# def post_init
# send_object header: :login, data: {name: $game.username, password: $game.password}
# MycardChannel.subscribe { |msg| send_object(msg) }
# end
#
# def receive_object obj
# $log.info('收到消息') { obj.inspect }
# Game_Event.push Game_Event.parse obj[:header], obj[:data]
# end
#
# def unbind
# Game_Event.push Game_Event::Error.new('ygocore', '网络连接中断', true)
# end
#end
get_announcements
end
# websocket, due to the author hasn't release separate gem yet
#https://github.com/imanel/websocket-ruby/issues/12
module WebSocket
module EventMachine
class Base < ::EventMachine::Connection
###########
### API ###
###########
def onopen(&blk)
; @onopen = blk;
end
# Called when connection is opened
def onclose(&blk)
; @onclose = blk;
end
# Called when connection is closed
def onerror(&blk)
; @onerror = blk;
end
# Called when error occurs
def onmessage(&blk)
; @onmessage = blk;
end
# Called when message is received from server
def onping(&blk)
; @onping = blk;
end
# Called when ping message is received from server
def onpong(&blk)
; @onpong = blk;
end
# Called when pond message is received from server
# Send data to client
# @param data [String] Data to send
# @param args [Hash] Arguments for send
# @option args [String] :type Type of frame to send - available types are "text", "binary", "ping", "pong" and "close"
# @return [Boolean] true if data was send, otherwise call on_error if needed
def send(data, args = {})
type = args[:type] || :text
unless type == :plain
frame = outgoing_frame.new(:version => @handshake.version, :data => data, :type => type)
if !frame.supported?
trigger_onerror("Frame type '#{type}' is not supported in protocol version #{@handshake.version}")
return false
elsif !frame.require_sending?
return false
end
data = frame.to_s
end
# debug "Sending raw: ", data
send_data(data)
true
end
# Close connection
# @return [Boolean] true if connection is closed immediately, false if waiting for server to close connection
def close
if @state == :open
@state = :closing
return false if send('', :type => :close)
else
send('', :type => :close) if @state == :closing
@state = :closed
end
close_connection_after_writing
true
end
# Send ping message to client
# @return [Boolean] false if protocol version is not supporting ping requests
def ping(data = '')
send(data, :type => :ping)
end
# Send pong message to client
# @return [Boolean] false if protocol version is not supporting pong requests
def pong(data = '')
send(data, :type => :pong)
end
############################
### EventMachine methods ###
############################
def receive_data(data)
# debug "Received raw: ", data
case @state
when :connecting then
handle_connecting(data)
when :open then
handle_open(data)
when :closing then
handle_closing(data)
end
end
def unbind
unless @state == :closed
@state = :closed
close
trigger_onclose('')
end
end
#######################
### Private methods ###
#######################
private
['onopen'].each do |m|
define_method "trigger_#{m}" do
callback = instance_variable_get("@#{m}")
callback.call if callback
end
end
['onerror', 'onping', 'onpong', 'onclose'].each do |m|
define_method "trigger_#{m}" do |data|
callback = instance_variable_get("@#{m}")
callback.call(data) if callback
end
end
def trigger_onmessage(data, type)
@onmessage.call(data, type) if @onmessage
end
def handle_connecting(data)
@handshake << data
return unless @handshake.finished?
if @handshake.valid?
send(@handshake.to_s, :type => :plain) if @handshake.should_respond?
@frame = incoming_frame.new(:version => @handshake.version)
@state = :open
trigger_onopen
handle_open(@handshake.leftovers) if @handshake.leftovers
else
trigger_onerror(@handshake.error)
close
end
end
def handle_open(data)
@frame << data
while frame = @frame.next
case frame.type
when :close
@state = :closing
close
trigger_onclose(frame.to_s)
when :ping
pong(frame.to_s)
trigger_onping(frame.to_s)
when :pong
trigger_onpong(frame.to_s)
when :text
trigger_onmessage(frame.to_s, :text)
when :binary
trigger_onmessage(frame.to_s, :binary)
end
end
unbind if @frame.error?
end
def handle_closing(data)
@state = :closed
close
trigger_onclose
end
def debug(description, data)
puts(description + data.bytes.to_a.collect { |b| '\x' + b.to_s(16).rjust(2, '0') }.join) unless @state == :connecting
end
end
end
end
# Example WebSocket Client (using EventMachine)
# @example
# ws = WebSocket::EventMachine::Client.connect(:host => "0.0.0.0", :port => 8080)
# ws.onmessage { |msg| ws.send "Pong: #{msg}" }
# ws.send "data"
module WebSocket
module EventMachine
class Client < Base
# Connect to websocket server
# @param args [Hash] The request arguments
# @option args [String] :host The host IP/DNS name
# @option args [Integer] :port The port to connect too(default = 80)
# @option args [Integer] :version Version of protocol to use(default = 13)
def self.connect(args = {})
host = nil
port = nil
if args[:uri]
uri = URI.parse(args[:uri])
host = uri.host
port = uri.port
end
host = args[:host] if args[:host]
port = args[:port] if args[:port]
port ||= 80
::EventMachine.connect host, port, self, args
end
# Initialize connection
# @param args [Hash] Arguments for connection
# @option args [String] :host The host IP/DNS name
# @option args [Integer] :port The port to connect too(default = 80)
# @option args [Integer] :version Version of protocol to use(default = 13)
def initialize(args)
@args = args
end
############################
### EventMachine methods ###
############################
# Called after initialize of connection, but before connecting to server
def post_init
@state = :connecting
@handshake = WebSocket::Handshake::Client.new(@args)
end
# Called by EventMachine after connecting.
# Sends handshake to server
def connection_completed
send(@handshake.to_s, :type => :plain)
end
private
def incoming_frame
WebSocket::Frame::Incoming::Client
end
def outgoing_frame
WebSocket::Frame::Outgoing::Client
end
end
end
end
#encoding: UTF-8
load 'lib/ygocore/window_login.rb'
require 'eventmachine'
require 'em-http'
require 'websocket'
require 'open-uri'
require 'yaml'
class Ygocore < Game
attr_reader :username
attr_accessor :password
@@config = YAML.load_file("lib/ygocore/server.yml")
def initialize
super
load 'lib/ygocore/event.rb'
load 'lib/ygocore/user.rb'
load 'lib/ygocore/room.rb'
load 'lib/ygocore/scene_lobby.rb'
require 'json'
require 'xmpp4r/client'
require 'xmpp4r/muc'
end
def refresh_interval
60
end
def login(username, password)
@username = username
@password = password
@nickname_conflict = []
matched = @username.match Jabber::JID::PATTERN
if matched[1] && matched[2]
@username = matched[1]
jid = Jabber::JID::new @username, matched[2], matched[3] || 'mycard'
else
jid = Jabber::JID::new @username, 'my-card.in', 'mycard'
end
@@im = Jabber::Client.new(jid)
@@im_room = Jabber::MUC::MUCClient.new(@@im)
Jabber.logger = $log
Jabber.debug = true
@@im.on_exception do |exception, c, where|
$log.error('聊天出错') { [exception, c, where] }
Game_Event.push(Game_Event::Chat.new(ChatMessage.new(User.new(:system, 'System'), '聊天服务连接中断: ' + exception.to_s)))
end
@@im_room.add_message_callback do |m|
user = m.from.resource == nickname ? @user : User.new(m.from.resource.to_sym, m.from.resource)
Game_Event.push Game_Event::Chat.new ChatMessage.new(user, m.body, :lobby) rescue $log.error('收到聊天消息') { $! }
end
@@im_room.add_private_message_callback do |m|
if m.body #忽略无消息的正在输入等内容
user = m.from.resource == nickname ? @user : User.new(m.from.resource.to_sym, m.from.resource)
Game_Event.push Game_Event::Chat.new ChatMessage.new(user, m.body, user) rescue $log.error('收到私聊消息') { $! }
end
end
@@im_room.add_join_callback do |m|
Game_Event.push Game_Event::NewUser.new User.new m.from.resource.to_sym, m.from.resource
end
@@im_room.add_leave_callback do |m|
Game_Event.push Game_Event::MissingUser.new User.new m.from.resource.to_sym, m.from.resource
end
connect
im_connect
end
def nickname
return @nickname if @nickname
if @nickname_conflict.include? @username
1.upto(9) do |i|
result = "#{@username}-#{i}"
return result unless @nickname_conflict.include? result
end
raise 'can`t get available nickname'
else
@username
end
end
def connect
@recv = Thread.new do
EventMachine::run {
http = EM::HttpRequest.new("http://my-card.in/servers.json").get
http.callback {
begin
self.servers.replace JSON.parse(http.response).collect {|data| Server.new(data['id'], data['name'], data['ip'], data['port'], data['auth'])}
self.filter[:servers] = self.servers.clone
rescue
Game_Event.push Game_Event::Error.new('ygocore', '读取服务器列表失败.1', true)
end
#EventMachine::connect "mycard-server.my-card.in", 9997, Client
ws = WebSocket::EventMachine::Client.connect(:host => "mycard-server.my-card.in", :port => 9998);
ws.onmessage do |msg, type|
$log.info('收到websocket消息'){msg.force_encoding("UTF-8")}
Game_Event.push Game_Event::RoomsUpdate.new JSON.parse(msg).collect { |room| Game_Event.parse_room(room) }
end
ws.onclose do
$log.info('websocket连接断开')
Game_Event.push Game_Event::Error.new('ygocore', '网络连接中断.1', true)
end
}
http.errback{
Game_Event.push Game_Event::Error.new('ygocore', '读取服务器列表失败', true)
}
}
end
end
def im_connect
Thread.new {
begin
@@im.allow_tls = false
@@im.use_ssl = true
#由于XMPP4r在windows
srv = []
Resolv::DNS.open { |dns|
# If ruby version is too old and SRV is unknown, this will raise a NameError
# which is caught below
Jabber::debuglog("RESOLVING:\n_xmpp-client._tcp.#{@@im.jid.domain} (SRV)")
srv = dns.getresources("_xmpp-client._tcp.#{@@im.jid.domain}", Resolv::DNS::Resource::IN::SRV)
}
# Sort SRV records: lowest priority first, highest weight first
srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
srv.each { |record|
begin
@@im.connect(record.target.to_s, 5223)
# Success
break
rescue SocketError, Errno::ECONNREFUSED
# Try next SRV record
end
}
begin
@@im.auth(@password)
rescue Jabber::ClientAuthenticationFailure
Game_Event.push Game_Event::Error.new('登录', '用户名或密码错误')
Thread.exit
end
Game_Event.push Game_Event::Login.new User.new(@@im.jid, @username, true)
@@im.send(Jabber::Presence.new.set_type(:available))
begin
nickname = nickname()
#@@im_room.join(Jabber::JID.new(I18n.t('lobby.room'), I18n.t('lobby.server'), nickname))
@@im_room.join(Jabber::JID.new('mycard', 'conference.my-card.in', nickname))
rescue Jabber::ServerError => exception
if exception.error.error == 'conflict'
@nickname_conflict << nickname
retry
end
end
Game_Event.push Game_Event::AllUsers.new @@im_room.roster.keys.collect { |nick| User.new(nick.to_sym, nick) } rescue p $!
rescue StandardError => exception
$log.error('聊天连接出错') { exception }
Game_Event.push(Game_Event::Chat.new(ChatMessage.new(User.new(:system, 'System'), '聊天服务器连接失败')))
end
}
end
def chat(chatmessage)
case chatmessage.channel
when :lobby
msg = Jabber::Message::new(nil, chatmessage.message)
@@im_room.send msg
when User
msg = Jabber::Message::new(nil, chatmessage.message)
@@im_room.send msg, chatmessage.channel.id
#send(:chat, channel: chatmessage.channel.id, message: chatmessage.message, time: chatmessage.time)
end
end
#def chat(chatmessage)
# case chatmessage.channel
# when :lobby
# send(:chat, channel: :lobby, message: chatmessage.message, time: chatmessage.time)
# when User
# send(:chat, channel: chatmessage.channel.id, message: chatmessage.message, time: chatmessage.time)
# end
#end
def host(room_name, room_config)
room = Room.new(0, room_name)
room.pvp = room_config[:pvp]
room.match = room_config[:match]
room.tag = room_config[:tag]
room.password = room_config[:password]
room.ot = room_config[:ot]
room.lp = room_config[:lp]
room.host_server
if $game.rooms.any? { |game_room| game_room.name == room_name }
Widget_Msgbox.new("建立房间", "房间名已存在", :ok => "确定")
else
Game_Event.push Game_Event::Join.new(room)
end
end
def watch(room)
Widget_Msgbox.new("加入房间", "游戏已经开始", :ok => "确定")
end
def join(room)
Game_Event.push Game_Event::Join.new(room)
end
def refresh
#send(:refresh)
end
def send(header, data=nil)
#$log.info('发送消息') { {header: header, data: data} }
#Client::MycardChannel.push header: header, data: data
end
def exit
@recv.exit if @recv
@recv = nil
end
def ygocore_path
"ygocore/ygopro_vs.exe"
end
def self.register
Dialog.web @@config['register']
end
def server
@@config['server']
end
def port
@@config['port']
end
def server=(server)
@@config['server'] = server
end
def port=(port)
@@config['port'] = port
end
def self.run_ygocore(option, image_downloading=false)
if !image_downloading and !Update.images.empty?
return Widget_Msgbox.new("加入房间", "卡图正在下载中,可能显示不出部分卡图", :ok => "确定") { run_ygocore(option, true) }
end
path = 'ygocore/ygopro_vs.exe'
Widget_Msgbox.new("ygocore", "正在启动ygocore") rescue nil
#写入配置文件并运行ygocore
Dir.chdir(File.dirname(path)) do
case option
when Room
room = option
room_name = if room.ot != 0 or room.lp != 8000
mode = case when room.match? then
1; when room.tag? then
2
else
0
end
room_name = "#{room.ot}#{mode}FFF#{room.lp},5,1,#{room.name}"
elsif room.tag?
"T#" + room.name
elsif room.pvp? and room.match?
"PM#" + room.name
elsif room.pvp?
"P#" + room.name
elsif room.match?
"M#" + room.name
else
room.name
end
if room.password and !room.password.empty?
room_name += "$" + room.password
end
system_conf = {}
begin
IO.readlines('system.conf').each do |line|
line.force_encoding "UTF-8"
next if line[0, 1] == '#'
field, contents = line.chomp.split(' = ', 2)
system_conf[field] = contents
end
rescue
system_conf['antialias'] = 2
system_conf['textfont'] = 'c:/windows/fonts/simsun.ttc 14'
system_conf['numfont'] = 'c:/windows/fonts/arialbd.ttf'
end
system_conf['nickname'] = $game.user.name
system_conf['nickname'] += '$' + $game.password if $game.password and !$game.password.empty? and room.server.auth
$log.info room
system_conf['lastip'] = room.server.ip
system_conf['lastport'] = room.server.port.to_s
system_conf['roompass'] = room_name
open('system.conf', 'w') { |file| file.write system_conf.collect { |key, value| "#{key} = #{value}" }.join("\n") }
args = '-j'
when :replay
args = '-r'
when :deck
args = '-d'
when String
system_conf = {}
begin
IO.readlines('system.conf').each do |line|
line.force_encoding "UTF-8"
next if line[0, 1] == '#'
field, contents = line.chomp.split(' = ', 2)
system_conf[field] = contents
end
rescue
system_conf['antialias'] = 2
system_conf['textfont'] = 'c:/windows/fonts/simsun.ttc 14'
system_conf['numfont'] = 'c:/windows/fonts/arialbd.ttf'
end
system_conf['lastdeck'] = option
open('system.conf', 'w') { |file| file.write system_conf.collect { |key, value| "#{key} = #{value}" }.join("\n") }
args = '-d'
end
IO.popen("ygopro_vs.exe #{args}")
WM.iconify rescue nil
end
Widget_Msgbox.destroy rescue nil
end
def self.replay(file, skip_image_downloading = false)
require 'fileutils'
FileUtils.mv Dir.glob('ygocore/replay/*.yrp'), 'replay/'
FileUtils.copy_file(file, "ygocore/replay/#{File.basename(file)}")
run_ygocore(:replay, skip_image_downloading)
end
private
def self.get_announcements
#公告
$config['ygocore'] ||= {}
$config['ygocore']['announcements'] ||= [Announcement.new("开放注册", nil, nil)]
#Thread.new do
# begin
# open(@@config['api']) do |file|
# file.set_encoding "GBK"
# announcements = []
# file.read.encode("UTF-8").scan(/<div style="color:red" >公告:(.*?)<\/div>/).each do |title, others|
# announcements << Announcement.new(title, @@config['index'], nil)
# end
# $config['ygocore']['announcements'].replace announcements
# Config.save
# end
# rescue Exception => exception
# $log.error('公告读取失败') { [exception.inspect, *exception.backtrace].collect { |str| str.encode("UTF-8") }.join("\n") }
# end
#end
end
#module Client
# MycardChannel = EM::Channel.new
# include EM::P::ObjectProtocol
#
# def post_init
# send_object header: :login, data: {name: $game.username, password: $game.password}
# MycardChannel.subscribe { |msg| send_object(msg) }
# end
#
# def receive_object obj
# $log.info('收到消息') { obj.inspect }
# Game_Event.push Game_Event.parse obj[:header], obj[:data]
# end
#
# def unbind
# Game_Event.push Game_Event::Error.new('ygocore', '网络连接中断', true)
# end
#end
get_announcements
end
# websocket, due to the author hasn't release separate gem yet
#https://github.com/imanel/websocket-ruby/issues/12
module WebSocket
module EventMachine
class Base < ::EventMachine::Connection
###########
### API ###
###########
def onopen(&blk)
; @onopen = blk;
end
# Called when connection is opened
def onclose(&blk)
; @onclose = blk;
end
# Called when connection is closed
def onerror(&blk)
; @onerror = blk;
end
# Called when error occurs
def onmessage(&blk)
; @onmessage = blk;
end
# Called when message is received from server
def onping(&blk)
; @onping = blk;
end
# Called when ping message is received from server
def onpong(&blk)
; @onpong = blk;
end
# Called when pond message is received from server
# Send data to client
# @param data [String] Data to send
# @param args [Hash] Arguments for send
# @option args [String] :type Type of frame to send - available types are "text", "binary", "ping", "pong" and "close"
# @return [Boolean] true if data was send, otherwise call on_error if needed
def send(data, args = {})
type = args[:type] || :text
unless type == :plain
frame = outgoing_frame.new(:version => @handshake.version, :data => data, :type => type)
if !frame.supported?
trigger_onerror("Frame type '#{type}' is not supported in protocol version #{@handshake.version}")
return false
elsif !frame.require_sending?
return false
end
data = frame.to_s
end
# debug "Sending raw: ", data
send_data(data)
true
end
# Close connection
# @return [Boolean] true if connection is closed immediately, false if waiting for server to close connection
def close
if @state == :open
@state = :closing
return false if send('', :type => :close)
else
send('', :type => :close) if @state == :closing
@state = :closed
end
close_connection_after_writing
true
end
# Send ping message to client
# @return [Boolean] false if protocol version is not supporting ping requests
def ping(data = '')
send(data, :type => :ping)
end
# Send pong message to client
# @return [Boolean] false if protocol version is not supporting pong requests
def pong(data = '')
send(data, :type => :pong)
end
############################
### EventMachine methods ###
############################
def receive_data(data)
# debug "Received raw: ", data
case @state
when :connecting then
handle_connecting(data)
when :open then
handle_open(data)
when :closing then
handle_closing(data)
end
end
def unbind
unless @state == :closed
@state = :closed
close
trigger_onclose('')
end
end
#######################
### Private methods ###
#######################
private
['onopen'].each do |m|
define_method "trigger_#{m}" do
callback = instance_variable_get("@#{m}")
callback.call if callback
end
end
['onerror', 'onping', 'onpong', 'onclose'].each do |m|
define_method "trigger_#{m}" do |data|
callback = instance_variable_get("@#{m}")
callback.call(data) if callback
end
end
def trigger_onmessage(data, type)
@onmessage.call(data, type) if @onmessage
end
def handle_connecting(data)
@handshake << data
return unless @handshake.finished?
if @handshake.valid?
send(@handshake.to_s, :type => :plain) if @handshake.should_respond?
@frame = incoming_frame.new(:version => @handshake.version)
@state = :open
trigger_onopen
handle_open(@handshake.leftovers) if @handshake.leftovers
else
trigger_onerror(@handshake.error)
close
end
end
def handle_open(data)
@frame << data
while frame = @frame.next
case frame.type
when :close
@state = :closing
close
trigger_onclose(frame.to_s)
when :ping
pong(frame.to_s)
trigger_onping(frame.to_s)
when :pong
trigger_onpong(frame.to_s)
when :text
trigger_onmessage(frame.to_s, :text)
when :binary
trigger_onmessage(frame.to_s, :binary)
end
end
unbind if @frame.error?
end
def handle_closing(data)
@state = :closed
close
trigger_onclose
end
def debug(description, data)
puts(description + data.bytes.to_a.collect { |b| '\x' + b.to_s(16).rjust(2, '0') }.join) unless @state == :connecting
end
end
end
end
# Example WebSocket Client (using EventMachine)
# @example
# ws = WebSocket::EventMachine::Client.connect(:host => "0.0.0.0", :port => 8080)
# ws.onmessage { |msg| ws.send "Pong: #{msg}" }
# ws.send "data"
module WebSocket
module EventMachine
class Client < Base
# Connect to websocket server
# @param args [Hash] The request arguments
# @option args [String] :host The host IP/DNS name
# @option args [Integer] :port The port to connect too(default = 80)
# @option args [Integer] :version Version of protocol to use(default = 13)
def self.connect(args = {})
host = nil
port = nil
if args[:uri]
uri = URI.parse(args[:uri])
host = uri.host
port = uri.port
end
host = args[:host] if args[:host]
port = args[:port] if args[:port]
port ||= 80
::EventMachine.connect host, port, self, args
end
# Initialize connection
# @param args [Hash] Arguments for connection
# @option args [String] :host The host IP/DNS name
# @option args [Integer] :port The port to connect too(default = 80)
# @option args [Integer] :version Version of protocol to use(default = 13)
def initialize(args)
@args = args
end
############################
### EventMachine methods ###
############################
# Called after initialize of connection, but before connecting to server
def post_init
@state = :connecting
@handshake = WebSocket::Handshake::Client.new(@args)
end
# Called by EventMachine after connecting.
# Sends handshake to server
def connection_completed
send(@handshake.to_s, :type => :plain)
end
private
def incoming_frame
WebSocket::Frame::Incoming::Client
end
def outgoing_frame
WebSocket::Frame::Outgoing::Client
end
end
end
end
class Room
attr_accessor :pvp
attr_accessor :match
attr_accessor :tag
attr_accessor :ot
attr_accessor :lp
attr_accessor :status
attr_accessor :server
alias pvp? pvp
alias match? match
alias tag? tag
def lp
@lp ||= 8000
end
def ot
@ot ||= 0
end
def full?
$game.is_a?(Ygocore) ? (@status == :start) : player2 #不规范修正iduel房间识别问题
end
def extra
result = {}
if pvp?
result["[竞技场]"] = [255,0,0]
end
if tag?
result["[TAG双打]"] = [128,0,255]
elsif match?
result["[三回决斗]"] = [0xff,0x72,0]
end
if ot == 1
result["[TCG]"] = [255,0,0]
elsif ot == 2
result["[O/T混]"] = [255,0,0]
end
if lp != 8000
result["[LP: #{lp}]"] = [255,0,0]
end
result
end
def host_server
servers = $game.servers
servers.select!{|server|server.auth} if @pvp
s = servers & $game.filter[:servers]
servers = s if !s.empty?
server = servers.min_by{|server|$game.rooms.select{|room|room.server == server}.size}
p server
server ||= Server.new(nil, "", $game.server, $game.port, true)
self.server = server
server
end
end
class Room
attr_accessor :pvp
attr_accessor :match
attr_accessor :tag
attr_accessor :ot
attr_accessor :lp
attr_accessor :status
attr_accessor :server
alias pvp? pvp
alias match? match
alias tag? tag
def lp
@lp ||= 8000
end
def ot
@ot ||= 0
end
def full?
$game.is_a?(Ygocore) ? (@status == :start) : player2 #不规范修正iduel房间识别问题
end
def extra
result = {}
if pvp?
result["[竞技场]"] = [255,0,0]
end
if tag?
result["[TAG双打]"] = [128,0,255]
elsif match?
result["[三回决斗]"] = [0xff,0x72,0]
end
if ot == 1
result["[TCG]"] = [255,0,0]
elsif ot == 2
result["[O/T混]"] = [255,0,0]
end
if lp != 8000
result["[LP: #{lp}]"] = [255,0,0]
end
result
end
def host_server
servers = $game.servers
servers.select!{|server|server.auth} if @pvp
s = servers & $game.filter[:servers]
servers = s if !s.empty?
server = servers.min_by{|server|$game.rooms.select{|room|room.server == server}.size}
p server
server ||= Server.new(nil, "", $game.server, $game.port, true)
self.server = server
server
end
end
ja:
ja:
name: "日本語"
\ No newline at end of file
zh-OS:
zh-OS:
name: "中文(OcgSoft)"
\ No newline at end of file
zh-TW:
zh-TW:
name: "中文(繁体)"
\ No newline at end of file
en:
lang: "中文"
chat:
room: "lobby.zh"
server: "conference.my-card.in"
login:
name: "用户名"
password: "密码"
login: "登录"
register: "注册"
remember: "记住密码"
replay: "录像"
lobby:
faq: "常见问题"
editdeck: "卡组编辑"
newroom: "建立房间"
lobby: "大厅"
filter: "房间筛选"
waiting_only: "仅等待中"
normal_only: "仅标准房"
en:
lang: "中文"
chat:
room: "lobby.zh"
server: "conference.my-card.in"
login:
name: "用户名"
password: "密码"
login: "登录"
register: "注册"
remember: "记住密码"
replay: "录像"
lobby:
faq: "常见问题"
editdeck: "卡组编辑"
newroom: "建立房间"
lobby: "大厅"
filter: "房间筛选"
waiting_only: "仅等待中"
normal_only: "仅标准房"
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