Commit a7073477 authored by nanahira's avatar nanahira

Merge branch 'master' into ai-play

parents 65a995d8 fd2bf6ea
Pipeline #41576 passed with stages
in 10 minutes and 11 seconds
......@@ -10,7 +10,7 @@ RUN apt update && \
# windbot
RUN git clone --depth=1 https://code.mycard.moe/nanahira/windbot /tmp/windbot && \
cd /tmp/windbot && \
xbuild /property:Configuration=Release /property:TargetFrameworkVersion="v4.0" && \
xbuild /property:Configuration=Release /property:TargetFrameworkVersion="v4.5" && \
mv /tmp/windbot/bin/Release /ygopro-server/windbot && \
cp -rf /ygopro-server/ygopro/cards.cdb /ygopro-server/windbot/ && \
rm -rf /tmp/windbot
......
......@@ -38,7 +38,7 @@ RUN git clone --branch=server --recursive --depth=1 https://code.mycard.moe/nana
strip ygopro && \
mkdir replay expansions && \
rm -rf .git* bin obj build ocgcore cmake lua premake* sound textures .travis.yml *.txt appveyor.yml LICENSE README.md *.lua strings.conf system.conf && \
ls gframe | sed '/game.cpp/d' | xargs -I {} rm -rf gframe/{}
ls gframe | sed '/config.h/d' | xargs -I {} rm -rf gframe/{}
# infos
WORKDIR /ygopro-server
......
......@@ -27,6 +27,7 @@
"stop": false,
"full": "服务器已爆满",
"max_rooms_count": 0,
"max_mem_percentage": 95,
"side_timeout": false,
"replay_delay": true,
"hide_name": false,
......@@ -40,6 +41,7 @@
"expansions_path": [
"./expansions"
],
"extra_script_path": [],
"i18n": {
"auto_pick": false,
"default": "zh-cn",
......@@ -79,7 +81,8 @@
"get": "https://sapi.moecube.com:444/biu-tips/tips.json",
"get_zh": false,
"interval": 30000,
"interval_ingame": 120000
"interval_ingame": 120000,
"prefix": "Tip: "
},
"dialogues": {
"enabled": true,
......@@ -118,7 +121,8 @@
"TMR": true
},
"ready_time": 20,
"hang_timeout": 90
"hang_timeout": 90,
"extra_modes": {}
},
"mysql": {
"enabled": false,
......@@ -245,8 +249,9 @@
"endpoint": "https://api.openai.com",
"token": "sk-xxxx",
"model": "gpt-4o-mini",
"system_prompt": "",
"comment": "{{player}} and {{opponent}} will be replaced in the prompt",
"system_prompt": "你是{{windbot}},一名与{{player}}实时互动的游戏对手。你的回复应简短、有趣、贴合当前情境,增强玩家沉浸感。避免冗长解释或重复内容,保持自然流畅,并且每次回复不能超过100个字。",
"comment": "{{player}} and {{windbot}} will be replaced in the prompt",
"token_limit": 12000,
"extra_opts": {}
},
"test_mode": {
......
......@@ -411,8 +411,8 @@
"bad_user_name": "请输入正确的用户名",
"server_full": "服务器已经爆满,请稍候再试",
"too_much_connection": "同时开启的客户端数量过多 ",
"banned_ip_login": "您的账号已被封禁",
"banned_user_login": "您的账号已被封禁",
"banned_ip_login": "您的账号已被封禁。如果您没有进行违规操作且用的是流量网络,可能过几小时就好。是IP撞了。",
"banned_user_login": "您的账号已被封禁。如果您没有进行违规操作且用的是流量网络,可能过几小时就好。是IP撞了。",
"bad_name_level3": "您的用户名存在不适当的内容",
"bad_name_level2": "您的用户名存在不适当的内容",
"bad_name_level1": "您的用户名存在不适当的内容,请注意更改",
......
......@@ -16,11 +16,13 @@
"deepmerge": "^4.2.2",
"formidable": "^1.2.6",
"geoip-country-lite": "^1.0.0",
"gpt-tokenizer": "^3.0.1",
"ip6addr": "^0.2.5",
"jszip": "^3.5.0",
"load-json-file": "^6.2.0",
"lzma": "^2.3.2",
"moment": "^2.29.1",
"mustache": "^4.2.0",
"mysql": "^2.18.1",
"node-os-utils": "^1.3.2",
"p-queue": "^6.6.2",
......@@ -34,7 +36,7 @@
"underscore": "^1.11.0",
"underscore.string": "^3.3.6",
"ws": "^8.9.0",
"ygopro-deck-encode": "^1.0.9"
"ygopro-deck-encode": "^1.0.14"
},
"devDependencies": {
"@types/bunyan": "^1.8.8",
......@@ -1357,6 +1359,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gpt-tokenizer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/gpt-tokenizer/-/gpt-tokenizer-3.0.1.tgz",
"integrity": "sha512-5jdaspBq/w4sWw322SvQj1Fku+CN4OAfYZeeEg8U7CWtxBz+zkxZ3h0YOHD43ee+nZYZ5Ud70HRN0ANcdIj4qg==",
"license": "MIT"
},
"node_modules/graceful-fs": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.1.14.tgz",
......@@ -1933,6 +1941,15 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"license": "MIT",
"bin": {
"mustache": "bin/mustache"
}
},
"node_modules/mv": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
......@@ -3687,9 +3704,9 @@
}
},
"node_modules/ygopro-deck-encode": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/ygopro-deck-encode/-/ygopro-deck-encode-1.0.9.tgz",
"integrity": "sha512-2aw/Lr8Sg4cPXKgq71Zk/GQPTZy5GhmviptVHWqMGEW0E2qTaxwpGmsQAN2Q4OWaK1lP+3g3bZt9BaqmWYZQSw==",
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/ygopro-deck-encode/-/ygopro-deck-encode-1.0.14.tgz",
"integrity": "sha512-Q64f8U+okLBDKHw02eRYsdDMpALhYa55k0BhFqZ5k4ntRpPKFNvM9sNEbBlg2bmyi6LCf3rlEmISmLtlx9uDeA==",
"license": "MIT"
}
},
......@@ -4678,6 +4695,11 @@
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
},
"gpt-tokenizer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/gpt-tokenizer/-/gpt-tokenizer-3.0.1.tgz",
"integrity": "sha512-5jdaspBq/w4sWw322SvQj1Fku+CN4OAfYZeeEg8U7CWtxBz+zkxZ3h0YOHD43ee+nZYZ5Ud70HRN0ANcdIj4qg=="
},
"graceful-fs": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.1.14.tgz",
......@@ -5111,6 +5133,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="
},
"mv": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
......@@ -6446,9 +6473,9 @@
}
},
"ygopro-deck-encode": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/ygopro-deck-encode/-/ygopro-deck-encode-1.0.9.tgz",
"integrity": "sha512-2aw/Lr8Sg4cPXKgq71Zk/GQPTZy5GhmviptVHWqMGEW0E2qTaxwpGmsQAN2Q4OWaK1lP+3g3bZt9BaqmWYZQSw=="
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/ygopro-deck-encode/-/ygopro-deck-encode-1.0.14.tgz",
"integrity": "sha512-Q64f8U+okLBDKHw02eRYsdDMpALhYa55k0BhFqZ5k4ntRpPKFNvM9sNEbBlg2bmyi6LCf3rlEmISmLtlx9uDeA=="
}
}
}
......@@ -18,11 +18,13 @@
"deepmerge": "^4.2.2",
"formidable": "^1.2.6",
"geoip-country-lite": "^1.0.0",
"gpt-tokenizer": "^3.0.1",
"ip6addr": "^0.2.5",
"jszip": "^3.5.0",
"load-json-file": "^6.2.0",
"lzma": "^2.3.2",
"moment": "^2.29.1",
"mustache": "^4.2.0",
"mysql": "^2.18.1",
"node-os-utils": "^1.3.2",
"p-queue": "^6.6.2",
......@@ -36,7 +38,7 @@
"underscore": "^1.11.0",
"underscore.string": "^3.3.6",
"ws": "^8.9.0",
"ygopro-deck-encode": "^1.0.9"
"ygopro-deck-encode": "^1.0.14"
},
"license": "AGPL-3.0",
"scripts": {
......
......@@ -25,6 +25,8 @@ qs = require "querystring"
zlib = require 'zlib'
axios = require 'axios'
osu = require 'node-os-utils'
mustache = require 'mustache'
gpt_tokenizer = require 'gpt-tokenizer/model/gpt-4o'
bunyan = require 'bunyan'
log = global.log = bunyan.createLogger name: "mycard"
......@@ -511,7 +513,7 @@ loadLFList = (path) ->
catch
try
log.info("Reading YGOPro version.")
cppversion = parseInt((await fs.promises.readFile(path.resolve(settings.modules.ygopro_path, 'gframe', 'game.cpp'), 'utf8')).match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16')
cppversion = parseInt((await fs.promises.readFile(path.resolve(settings.modules.ygopro_path, 'gframe', 'config.h'), 'utf8')).match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16')
await setting_change(settings, "version", cppversion)
log.info "ygopro version 0x"+settings.version.toString(16), "(from source code)"
catch
......@@ -852,11 +854,11 @@ ROOM_find_or_create_by_name = global.ROOM_find_or_create_by_name = (name, player
uname=name.toUpperCase()
if settings.modules.windbot.enabled and (uname[0...2] == 'AI' or (!settings.modules.random_duel.enabled and uname == ''))
return ROOM_find_or_create_ai(name)
if settings.modules.random_duel.enabled and (uname == '' or uname == 'S' or uname == 'M' or uname == 'T' or uname == 'TOR' or uname == 'TR' or uname == 'OOR' or uname == 'OR' or uname == 'TOMR' or uname == 'TMR' or uname == 'OOMR' or uname == 'OMR' or uname == 'CR' or uname == 'CMR')
if settings.modules.random_duel.enabled and (uname == '' or uname == 'S' or uname == 'M' or uname == 'T' or uname == 'TOR' or uname == 'TR' or uname == 'OOR' or uname == 'OR' or uname == 'TOMR' or uname == 'TMR' or uname == 'OOMR' or uname == 'OMR' or uname == 'CR' or uname == 'CMR' or settings.modules.random_duel.extra_modes[uname] != undefined)
return await ROOM_find_or_create_random(uname, player_ip)
if room = ROOM_find_by_name(name)
return room
else if memory_usage >= 95 or (settings.modules.max_rooms_count and rooms_count >= settings.modules.max_rooms_count)
else if memory_usage >= settings.modules.max_mem_percentage or (settings.modules.max_rooms_count and rooms_count >= settings.modules.max_rooms_count)
return null
else
room = new Room(name)
......@@ -910,8 +912,9 @@ ROOM_find_or_create_random = global.ROOM_find_or_create_random = (type, player_i
else
return null
if result.random_type=='S' then result.welcome2 = '${random_duel_enter_room_single}'
if result.random_type=='M' then result.welcome2 = '${random_duel_enter_room_match}'
if result.random_type=='T' then result.welcome2 = '${random_duel_enter_room_tag}'
else if result.random_type=='M' then result.welcome2 = '${random_duel_enter_room_match}'
else if result.random_type=='T' then result.welcome2 = '${random_duel_enter_room_tag}'
else result.welcome2 = settings.modules.random_duel.extra_modes[type]?.welcome ? ''
return result
ROOM_find_or_create_ai = global.ROOM_find_or_create_ai = (name)->
......@@ -1626,6 +1629,11 @@ class Room
path.resolve(settings.modules.ygopro_path, s)
)
.join(',')
YGOPRO_EXTRA_SCRIPT: settings.modules.extra_script_path
.map((s) ->
path.resolve(settings.modules.ygopro_path, s)
)
.join(',')
}
}
)
......@@ -1722,6 +1730,7 @@ class Room
form_data.append 'start', @start_time
form_data.append 'end', end_time
form_data.append 'arena', @arena
form_data.append 'nonce', Math.random().toString()
post_score_process = () ->
axios.post settings.modules.arena_mode.post_score, form_data,
......@@ -3369,7 +3378,7 @@ ygopro.stoc_send_random_tip = (client)->
if settings.modules.tips.split_zh and tips.tips_zh.length and client.lang == "zh-cn"
tip_type = "tips_zh"
if settings.modules.tips.enabled && tips.tips.length && !client.is_local && !client.closed
ygopro.stoc_send_chat(client, "Tip: " + tips[tip_type][Math.floor(Math.random() * tips[tip_type].length)])
ygopro.stoc_send_chat(client, settings.modules.tips.prefix + tips[tip_type][Math.floor(Math.random() * tips[tip_type].length)])
await return
ygopro.stoc_send_random_tip_to_room = (room)->
if settings.modules.tips.enabled && tips.tips.length
......@@ -3708,14 +3717,30 @@ ygopro.ctos_follow 'CHAT', true, (buffer, info, client, server, datas)->
cancel = true
if not cancel and settings.modules.chatgpt.enabled and room.windbot and not client.is_post_watcher and client.pos < 2 and not client.is_local
# session_key = "#{settings.modules.chatgpt.session}:#{settings.port}:#{CLIENT_get_authorize_key(client)}"
if room.is_requesting_chatgpt
return false
room.is_requesting_chatgpt = true
if not room.chatgpt_conversation
room.chatgpt_conversation = []
openai_req_body = {
messages: [
{ role: "user", content: msg }
],
messages: Array.from(room.chatgpt_conversation),
model: settings.modules.chatgpt.model
}
openai_req_body.messages.push { role: "user", content: msg }
shrink_index = 0
if settings.modules.chatgpt.system_prompt
openai_req_body.messages.unshift { role: "system", content: settings.modules.chatgpt.system_prompt.replace }
openai_req_body.messages.unshift { role: "system", content: mustache.render(settings.modules.chatgpt.system_prompt, {
player: client.name,
windbot: room.windbot.name,
}, undefined, { escape: (v) -> v }) }
shrink_index = 1
# trim conversation if too long
shrink_count = 0
while !gpt_tokenizer.isWithinTokenLimit(openai_req_body.messages, settings.modules.chatgpt.max_tokens)
if openai_req_body.messages.length <= (1 + shrink_index)
break
openai_req_body.messages.splice(shrink_index, 2) # remove the oldest user+assistant pair
shrink_count += 2
Object.assign(openai_req_body, settings.modules.chatgpt.extra_opts)
axios.post("#{settings.modules.chatgpt.endpoint}/v1/chat/completions", openai_req_body, {
timeout: 300000,
......@@ -3732,8 +3757,15 @@ ygopro.ctos_follow 'CHAT', true, (buffer, info, client, server, datas)->
ygopro.stoc_send_chat_to_room(room, chunk.join(''), 1 - client.pos)
else
ygopro.stoc_send_chat_to_room(room, ' ', 1 - client.pos)
# save text
if shrink_count > 0
room.chatgpt_conversation.splice(0, shrink_count)
room.chatgpt_conversation.push { role: "user", content: msg }
room.chatgpt_conversation.push { role: "assistant", content: text }
).catch((err) ->
log.error "CHATGPT ERROR", err
).finally(() ->
room.is_requesting_chatgpt = false
)
return false
if !(room and (room.random_type or room.arena)) and not settings.modules.mycard.enabled
......@@ -3899,14 +3931,10 @@ ygopro.ctos_follow 'UPDATE_DECK', true, (buffer, info, client, server, datas)->
else
log.warn("GET ATHLETIC FAIL", client.name, athleticCheckResult.message)
if settings.modules.tournament_mode.enabled and settings.modules.tournament_mode.deck_check
client_deck_obj = YGOProDeck.fromUpdateDeckPayload(buffer)
if settings.modules.challonge.enabled and client.challonge_info and client.challonge_info.deckbuf
trim_deckbuf = (buf) ->
mainc = buf.readUInt32LE(0)
sidec = buf.readUInt32LE(4)
# take first (2 + mainc + sidec) * 4 bytes
return buf.slice(0, (2 + mainc + sidec) * 4)
deckbuf_from_challonge = Buffer.from(client.challonge_info.deckbuf, "base64")
if trim_deckbuf(deckbuf_from_challonge).equals(trim_deckbuf(buffer))
deck_obj = YGOProDeck.fromUpdateDeckPayload(Buffer.from(client.challonge_info.deckbuf, "base64"))
if deck_obj.isEqual(client_deck_obj, { ignoreOrder: true })
#log.info("deck ok: " + client.name)
return deck_ok("${deck_correct_part1} #{client.challonge_info.name} ${deck_correct_part2}")
else
......@@ -3915,16 +3943,14 @@ ygopro.ctos_follow 'UPDATE_DECK', true, (buffer, info, client, server, datas)->
else
decks = await fs.promises.readdir(settings.modules.tournament_mode.deck_path)
if decks.length
found_deck=false
for deck in decks
if deck_name_match(deck, client.name)
found_deck=deck
found_deck = decks.find((deck) -> deck_name_match(deck, client.name))
if found_deck
deck_text = await fs.promises.readFile(settings.modules.tournament_mode.deck_path+found_deck,{encoding:"ASCII"})
deck_obj = YGOProDeck.fromYdkString(deck_text)
deck_main=deck_obj.main.concat(deck_obj.extra)
deck_side=deck_obj.side
if _.isEqual(buff_main, deck_main) and _.isEqual(buff_side, deck_side)
# put extra cards to main
deck_obj.main = deck_obj.main.concat(deck_obj.extra)
deck_obj.extra = []
if client_deck_obj.isEqual(deck_obj, { ignoreOrder: true })
#log.info("deck ok: " + client.name)
return deck_ok("${deck_correct_part1} #{found_deck} ${deck_correct_part2}")
else
......
// Generated by CoffeeScript 2.7.0
(function() {
// 标准库
var Aragami, CLIENT_check_vip, CLIENT_get_absolute_pos, CLIENT_get_authorize_key, CLIENT_get_kick_reconnect_target, CLIENT_get_partner, CLIENT_get_save_data, CLIENT_heartbeat_register, CLIENT_heartbeat_unregister, CLIENT_import_data, CLIENT_is_able_to_kick_reconnect, CLIENT_is_able_to_reconnect, CLIENT_is_banned_by_mc, CLIENT_is_player, CLIENT_kick, CLIENT_kick_reconnect, CLIENT_pre_reconnect, CLIENT_reconnect, CLIENT_reconnect_register, CLIENT_reconnect_unregister, CLIENT_send_pre_reconnect_info, CLIENT_send_reconnect_info, CLIENT_send_replays, CLIENT_send_replays_and_kick, CLIENT_send_vip_status, CLIENT_set_ip, CLIENT_use_cdkey, PQueue, Q, ROOM_all, ROOM_bad_ip, ROOM_ban_player, ROOM_clear_disconnect, ROOM_connected_ip, ROOM_find_by_name, ROOM_find_by_pid, ROOM_find_by_port, ROOM_find_by_title, ROOM_find_or_create_ai, ROOM_find_or_create_by_name, ROOM_find_or_create_random, ROOM_kick, ROOM_player_flee, ROOM_player_get_score, ROOM_player_lose, ROOM_player_win, ROOM_players_oppentlist, ROOM_unwelcome, ROOM_validate, ReplayParser, ResolveData, Room, SERVER_clear_disconnect, SERVER_kick, SOCKET_flush_data, VIP_generate_cdkeys, YGOProDeck, _, _async, addCallback, aragami, aragami_classes, athleticChecker, auth, axios, badwordR, badwords, ban_user, bunyan, challonge, checkFileExists, concat_name, createDirectoryIfNotExists, crypto, dataManager, deck_name_match, dialogues, disconnect_list, exec, execFile, extra_mode_list, fs, geoip, getDuelLogQueryFromQs, getRealIp, get_memory_usage, http, httpRequestListener, importOldConfig, import_datas, init, ip6addr, isTrustedProxy, lflists, loadJSON, loadJSONAsync, loadLFList, loadRemoteData, load_dialogues, load_dialogues_custom, load_tips, load_tips_zh, load_words, log, long_resolve_cards, memory_usage, merge, moment, moment_long_ago_string, moment_now, moment_now_string, msg_polyfill, neosRequestListener, net, netRequestHandler, os, osu, path, qs, real_windbot_server_ip, release_disconnect, report_to_big_brother, request, roomlist, rooms_count, setting_change, setting_get, setting_save, settings, spawn, spawnSync, spawn_windbot, tips, toIpv4, toIpv6, url, users_cache, util, utility, wait_room_start, wait_room_start_arena, windbot_looplimit, windbot_process, windbots, words, ygopro, zlib;
var Aragami, CLIENT_check_vip, CLIENT_get_absolute_pos, CLIENT_get_authorize_key, CLIENT_get_kick_reconnect_target, CLIENT_get_partner, CLIENT_get_save_data, CLIENT_heartbeat_register, CLIENT_heartbeat_unregister, CLIENT_import_data, CLIENT_is_able_to_kick_reconnect, CLIENT_is_able_to_reconnect, CLIENT_is_banned_by_mc, CLIENT_is_player, CLIENT_kick, CLIENT_kick_reconnect, CLIENT_pre_reconnect, CLIENT_reconnect, CLIENT_reconnect_register, CLIENT_reconnect_unregister, CLIENT_send_pre_reconnect_info, CLIENT_send_reconnect_info, CLIENT_send_replays, CLIENT_send_replays_and_kick, CLIENT_send_vip_status, CLIENT_set_ip, CLIENT_use_cdkey, PQueue, Q, ROOM_all, ROOM_bad_ip, ROOM_ban_player, ROOM_clear_disconnect, ROOM_connected_ip, ROOM_find_by_name, ROOM_find_by_pid, ROOM_find_by_port, ROOM_find_by_title, ROOM_find_or_create_ai, ROOM_find_or_create_by_name, ROOM_find_or_create_random, ROOM_kick, ROOM_player_flee, ROOM_player_get_score, ROOM_player_lose, ROOM_player_win, ROOM_players_oppentlist, ROOM_unwelcome, ROOM_validate, ReplayParser, ResolveData, Room, SERVER_clear_disconnect, SERVER_kick, SOCKET_flush_data, VIP_generate_cdkeys, YGOProDeck, _, _async, addCallback, aragami, aragami_classes, athleticChecker, auth, axios, badwordR, badwords, ban_user, bunyan, challonge, checkFileExists, concat_name, createDirectoryIfNotExists, crypto, dataManager, deck_name_match, dialogues, disconnect_list, exec, execFile, extra_mode_list, fs, geoip, getDuelLogQueryFromQs, getRealIp, get_memory_usage, gpt_tokenizer, http, httpRequestListener, importOldConfig, import_datas, init, ip6addr, isTrustedProxy, lflists, loadJSON, loadJSONAsync, loadLFList, loadRemoteData, load_dialogues, load_dialogues_custom, load_tips, load_tips_zh, load_words, log, long_resolve_cards, memory_usage, merge, moment, moment_long_ago_string, moment_now, moment_now_string, msg_polyfill, mustache, neosRequestListener, net, netRequestHandler, os, osu, path, qs, real_windbot_server_ip, release_disconnect, report_to_big_brother, request, roomlist, rooms_count, setting_change, setting_get, setting_save, settings, spawn, spawnSync, spawn_windbot, tips, toIpv4, toIpv6, url, users_cache, util, utility, wait_room_start, wait_room_start_arena, windbot_looplimit, windbot_process, windbots, words, ygopro, zlib;
net = require('net');
......@@ -47,6 +47,10 @@
osu = require('node-os-utils');
mustache = require('mustache');
gpt_tokenizer = require('gpt-tokenizer/model/gpt-4o');
bunyan = require('bunyan');
log = global.log = bunyan.createLogger({
......@@ -652,7 +656,7 @@
}
try {
log.info("Reading YGOPro version.");
cppversion = parseInt(((await fs.promises.readFile(path.resolve(settings.modules.ygopro_path, 'gframe', 'game.cpp'), 'utf8'))).match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16');
cppversion = parseInt(((await fs.promises.readFile(path.resolve(settings.modules.ygopro_path, 'gframe', 'config.h'), 'utf8'))).match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16');
await setting_change(settings, "version", cppversion);
log.info("ygopro version 0x" + settings.version.toString(16), "(from source code)");
} catch (error1) {
......@@ -1091,12 +1095,12 @@
if (settings.modules.windbot.enabled && (uname.slice(0, 2) === 'AI' || (!settings.modules.random_duel.enabled && uname === ''))) {
return ROOM_find_or_create_ai(name);
}
if (settings.modules.random_duel.enabled && (uname === '' || uname === 'S' || uname === 'M' || uname === 'T' || uname === 'TOR' || uname === 'TR' || uname === 'OOR' || uname === 'OR' || uname === 'TOMR' || uname === 'TMR' || uname === 'OOMR' || uname === 'OMR' || uname === 'CR' || uname === 'CMR')) {
if (settings.modules.random_duel.enabled && (uname === '' || uname === 'S' || uname === 'M' || uname === 'T' || uname === 'TOR' || uname === 'TR' || uname === 'OOR' || uname === 'OR' || uname === 'TOMR' || uname === 'TMR' || uname === 'OOMR' || uname === 'OMR' || uname === 'CR' || uname === 'CMR' || settings.modules.random_duel.extra_modes[uname] !== void 0)) {
return (await ROOM_find_or_create_random(uname, player_ip));
}
if (room = ROOM_find_by_name(name)) {
return room;
} else if (memory_usage >= 95 || (settings.modules.max_rooms_count && rooms_count >= settings.modules.max_rooms_count)) {
} else if (memory_usage >= settings.modules.max_mem_percentage || (settings.modules.max_rooms_count && rooms_count >= settings.modules.max_rooms_count)) {
return null;
} else {
room = new Room(name);
......@@ -1113,7 +1117,7 @@
};
ROOM_find_or_create_random = global.ROOM_find_or_create_random = async function(type, player_ip) {
var max_player, name, playerbanned, randomDuelBanRecord, result;
var max_player, name, playerbanned, randomDuelBanRecord, ref, ref1, result;
if (settings.modules.mysql.enabled) {
randomDuelBanRecord = (await dataManager.getRandomDuelBan(player_ip));
if (randomDuelBanRecord) {
......@@ -1163,12 +1167,12 @@
}
if (result.random_type === 'S') {
result.welcome2 = '${random_duel_enter_room_single}';
}
if (result.random_type === 'M') {
} else if (result.random_type === 'M') {
result.welcome2 = '${random_duel_enter_room_match}';
}
if (result.random_type === 'T') {
} else if (result.random_type === 'T') {
result.welcome2 = '${random_duel_enter_room_tag}';
} else {
result.welcome2 = (ref = (ref1 = settings.modules.random_duel.extra_modes[type]) != null ? ref1.welcome : void 0) != null ? ref : '';
}
return result;
};
......@@ -2081,6 +2085,9 @@
...process.env,
YGOPRO_EXPANSIONS: settings.modules.expansions_path.map(function(s) {
return path.resolve(settings.modules.ygopro_path, s);
}).join(','),
YGOPRO_EXTRA_SCRIPT: settings.modules.extra_script_path.map(function(s) {
return path.resolve(settings.modules.ygopro_path, s);
}).join(',')
}
});
......@@ -2227,6 +2234,7 @@
form_data.append('start', this.start_time);
form_data.append('end', end_time);
form_data.append('arena', this.arena);
form_data.append('nonce', Math.random().toString());
post_score_process = function() {
return axios.post(settings.modules.arena_mode.post_score, form_data, {
validateStatus: function(status) {
......@@ -4467,7 +4475,7 @@
tip_type = "tips_zh";
}
if (settings.modules.tips.enabled && tips.tips.length && !client.is_local && !client.closed) {
ygopro.stoc_send_chat(client, "Tip: " + tips[tip_type][Math.floor(Math.random() * tips[tip_type].length)]);
ygopro.stoc_send_chat(client, settings.modules.tips.prefix + tips[tip_type][Math.floor(Math.random() * tips[tip_type].length)]);
}
};
......@@ -4697,7 +4705,7 @@
//else
//log.info 'BIG BROTHER OK', response.statusCode, roomname, body
ygopro.ctos_follow('CHAT', true, async function(buffer, info, client, server, datas) {
var buy_result, cancel, ccolor, cip, cmd, cmsg, cname, code, color, cvalue, isVip, j, key, len, msg, name, oldmsg, openai_req_body, player, ref, ref1, room, struct, sur_player, uname, windbot, word;
var buy_result, cancel, ccolor, cip, cmd, cmsg, cname, code, color, cvalue, isVip, j, key, len, msg, name, oldmsg, openai_req_body, player, ref, ref1, room, shrink_count, shrink_index, struct, sur_player, uname, windbot, word;
room = ROOM_all[client.rid];
if (!room) {
return;
......@@ -4953,20 +4961,44 @@
}
if (!cancel && settings.modules.chatgpt.enabled && room.windbot && !client.is_post_watcher && client.pos < 2 && !client.is_local) {
// session_key = "#{settings.modules.chatgpt.session}:#{settings.port}:#{CLIENT_get_authorize_key(client)}"
if (room.is_requesting_chatgpt) {
return false;
}
room.is_requesting_chatgpt = true;
if (!room.chatgpt_conversation) {
room.chatgpt_conversation = [];
}
openai_req_body = {
messages: [
{
role: "user",
content: msg
}
],
messages: Array.from(room.chatgpt_conversation),
model: settings.modules.chatgpt.model
};
openai_req_body.messages.push({
role: "user",
content: msg
});
shrink_index = 0;
if (settings.modules.chatgpt.system_prompt) {
openai_req_body.messages.unshift({
role: "system",
content: settings.modules.chatgpt.system_prompt.replace
content: mustache.render(settings.modules.chatgpt.system_prompt, {
player: client.name,
windbot: room.windbot.name
}, void 0, {
escape: function(v) {
return v;
}
})
});
shrink_index = 1;
}
// trim conversation if too long
shrink_count = 0;
while (!gpt_tokenizer.isWithinTokenLimit(openai_req_body.messages, settings.modules.chatgpt.max_tokens)) {
if (openai_req_body.messages.length <= (1 + shrink_index)) {
break;
}
openai_req_body.messages.splice(shrink_index, 2); // remove the oldest user+assistant pair
shrink_count += 2;
}
Object.assign(openai_req_body, settings.modules.chatgpt.extra_opts);
axios.post(`${settings.modules.chatgpt.endpoint}/v1/chat/completions`, openai_req_body, {
......@@ -4975,30 +5007,37 @@
Authorization: `Bearer ${settings.modules.chatgpt.token}`
}
}).then(function(res) {
var chunk, chunks, l, len1, line, lines, results, text;
var chunk, chunks, l, len1, len2, line, lines, m, text;
text = res.data.choices[0].message.content;
lines = text.split("\n");
results = [];
for (l = 0, len1 = lines.length; l < len1; l++) {
line = lines[l];
if (line) {
chunks = _.chunk(line, 100);
results.push((function() {
var len2, m, results1;
results1 = [];
for (m = 0, len2 = chunks.length; m < len2; m++) {
chunk = chunks[m];
results1.push(ygopro.stoc_send_chat_to_room(room, chunk.join(''), 1 - client.pos));
}
return results1;
})());
for (m = 0, len2 = chunks.length; m < len2; m++) {
chunk = chunks[m];
ygopro.stoc_send_chat_to_room(room, chunk.join(''), 1 - client.pos);
}
} else {
results.push(ygopro.stoc_send_chat_to_room(room, ' ', 1 - client.pos));
ygopro.stoc_send_chat_to_room(room, ' ', 1 - client.pos);
}
}
return results;
// save text
if (shrink_count > 0) {
room.chatgpt_conversation.splice(0, shrink_count);
}
room.chatgpt_conversation.push({
role: "user",
content: msg
});
return room.chatgpt_conversation.push({
role: "assistant",
content: text
});
}).catch(function(err) {
return log.error("CHATGPT ERROR", err);
}).finally(function() {
return room.is_requesting_chatgpt = false;
});
return false;
}
......@@ -5087,7 +5126,7 @@
});
ygopro.ctos_follow('UPDATE_DECK', true, async function(buffer, info, client, server, datas) {
var athleticCheckResult, buff_main, buff_side, deck, deck_bad, deck_main, deck_obj, deck_ok, deck_side, deck_text, deckbuf_from_challonge, decks, found_deck, i, j, len, oppo_pos, recover_player_data, recoveredDeck, room, trim_deckbuf, win_pos;
var athleticCheckResult, buff_main, buff_side, client_deck_obj, deck_bad, deck_obj, deck_ok, deck_text, decks, found_deck, i, oppo_pos, recover_player_data, recoveredDeck, room, win_pos;
if (settings.modules.reconnect.enabled && client.pre_reconnecting) {
if (!CLIENT_is_able_to_reconnect(client) && !CLIENT_is_able_to_kick_reconnect(client)) {
ygopro.stoc_send_chat(client, "${reconnect_failed}", ygopro.constants.COLORS.RED);
......@@ -5220,16 +5259,12 @@
}
}
if (settings.modules.tournament_mode.enabled && settings.modules.tournament_mode.deck_check) {
client_deck_obj = YGOProDeck.fromUpdateDeckPayload(buffer);
if (settings.modules.challonge.enabled && client.challonge_info && client.challonge_info.deckbuf) {
trim_deckbuf = function(buf) {
var mainc, sidec;
mainc = buf.readUInt32LE(0);
sidec = buf.readUInt32LE(4);
// take first (2 + mainc + sidec) * 4 bytes
return buf.slice(0, (2 + mainc + sidec) * 4);
};
deckbuf_from_challonge = Buffer.from(client.challonge_info.deckbuf, "base64");
if (trim_deckbuf(deckbuf_from_challonge).equals(trim_deckbuf(buffer))) {
deck_obj = YGOProDeck.fromUpdateDeckPayload(Buffer.from(client.challonge_info.deckbuf, "base64"));
if (deck_obj.isEqual(client_deck_obj, {
ignoreOrder: true
})) {
//log.info("deck ok: " + client.name)
return deck_ok(`\${deck_correct_part1} ${client.challonge_info.name} \${deck_correct_part2}`);
} else {
......@@ -5239,21 +5274,20 @@
} else {
decks = (await fs.promises.readdir(settings.modules.tournament_mode.deck_path));
if (decks.length) {
found_deck = false;
for (j = 0, len = decks.length; j < len; j++) {
deck = decks[j];
if (deck_name_match(deck, client.name)) {
found_deck = deck;
}
}
found_deck = decks.find(function(deck) {
return deck_name_match(deck, client.name);
});
if (found_deck) {
deck_text = (await fs.promises.readFile(settings.modules.tournament_mode.deck_path + found_deck, {
encoding: "ASCII"
}));
deck_obj = YGOProDeck.fromYdkString(deck_text);
deck_main = deck_obj.main.concat(deck_obj.extra);
deck_side = deck_obj.side;
if (_.isEqual(buff_main, deck_main) && _.isEqual(buff_side, deck_side)) {
// put extra cards to main
deck_obj.main = deck_obj.main.concat(deck_obj.extra);
deck_obj.extra = [];
if (client_deck_obj.isEqual(deck_obj, {
ignoreOrder: true
})) {
//log.info("deck ok: " + client.name)
return deck_ok(`\${deck_correct_part1} ${found_deck} \${deck_correct_part2}`);
} else {
......
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